The design intent behind CSG is to make the processing as logically simple as possible. The underlying CSGSpatial is a standard jme3 Spatial and as such, is added into your display scene like any other Spatial. As a Spatial, it can have standard Physics applied to it as well. You start by creating one of the variants of CSGSpatial (CSGGeometry, CSGGeonode).

You then add/subtract/intersect some set of solids from this CSGSpatial. Every solid is represented by CSGShape, which is mainly a CSG-aware wrapper around some arbitrary Mesh. This Mesh can be provided by a jme3 primitive (Box, Sphere, ...), a CSG primitive (CSGBox, CSGSphere, ...), or anything else that defines Mesh.

Once the solids have been blended together into the CSGSpatial, then the CSGSpatial must be explicitly 'regenerated'. This can be done by a programmatic call to the .regenerate( ) method, or it is inherently done at the end of Savable input processing.

The final step is to add the Spatial to your scene, along with any control processing (like Physics) that may be required. At this point, the CSGSpatial should be no different than any other jme3 Spatial you are used to using.

CSGSpatial

CSGSpatial is an abstract interface that defines the standard CSG operations. It is implemented by:

  • CSGGeometry - a simple wrapper that supports a single common Material applied to all parts
  • CSGGeonode - a wrapper that maintains a set of Materials, each taken from the various shapes blended into this spatial
  • CSGLinkNode - a variant useful for Savable import processing that can provide a common environment and/or Material to other CSGSpatials created during the import process, and which can trigger the load of such assets. However, it does nothing with CSGShapes itself.

The common java entry points for CSGSpatial services are:

addShape( CSGShape pShape )
Add the given shape into the blend via the UNION operation.
subtractShape( CSGShape pShape )
Add the given shape into the blend via the DIFFERENCE operation..
intersectShape( CSGShape pShape )
Add the given shape into the blend via the INTERSECTION operation.
addShape( CSGShape pShape, CSGOperator pOperator )
Add the given shape into the blend via the explicitly given operation.
removeAllShapes( )
removeShape( CSGShape pShape )
Remove a previously added shape from the blending process.
NOTE that this is NOT subtract. Once removed, the shape plays no part.
regenerate( )
regenerate( CSGEnvironment pEnvironment )
Apply all the active shape processing and produce a Mesh. If no explicit CSGEnvironment is provided, then the system standard environment will be used.
The resultant CSGShape is returned by .regenerate() and can be blended back into another CSGSpatial.
isValid( )
After regeneration, determine if a valid Mesh was produced or not. If not valid, error information is available via .getError();
getShapeRegenerationNS( )
After regeneration, return a long count of nanoseconds it took to produce the final Mesh.

Some entry points defined by Geometry have been added to the CSGSpatial interface so that Materials and LevelOfDetail can be supported uniformly:

getMaterial()
setMaterial( Material pMaterial )
Accessors for controlling the Material that applies to this Spatial.
getLodLevel()
setLodLevel( int pLODLevel )
Accessors for controlling the Level-Of-Detail that applies to this Spatial.

The import XML definitions look something like:

<net.wcomohundro.jme3.csg.CSGLinkNode fname='CSGSamples'>
    <lights class='com.jme3.light.LightList'>
        <lights size='1'>
        	<com.jme3.light.AmbientLight name='ALight' enabled='true'>
        		<color class='com.jme3.math.ColorRGBA' r='1' g='1' b='1' a='1'/>
        	</com.jme3.light.AmbientLight>
        </lights>
    </lights>
    <children>
        <net.wcomohundro.jme3.csg.CSGGeonode name='BumpyCube'
        			materialName='Textures/Debug/Normals.xml'>
            <shapes>
                <net.wcomohundro.jme3.csg.CSGShape name='Box'>
                    <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                    		xExtent='1.0' yExtent='1.0' zExtent='1.0'/>
                </net.wcomohundro.jme3.csg.CSGShape>
                
                <net.wcomohundro.jme3.csg.CSGShape name='Sphere' operator='UNION'>
                    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSphere'
                    	 	axisSamples='64' radialSamples='64' radius='1.2'/>
                    <transform class='com.jme3.math.Transform'>
                        <translation class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
                    </transform>
                </net.wcomohundro.jme3.csg.CSGShape>
            </shapes>
        </net.wcomohundro.jme3.csg.CSGGeonode>
    </children>
</net.wcomohundro.jme3.csg.CSGLinkNode>

CSGShape

CSGShape provides the CSG-aware wrapper around a jme3 Mesh. Programmatically, the heavy lifting all occurs in the constructor, where a CSGShape is build with a name and a Mesh. The shape is then added to a CSGSpatial along with the blending operator.

From the XML import perspective, the operator is specified on the CSGShape itself. See the example above...

You will frequently use a Transform within the CSGShape definition to position, rotate, and/or scale the item before it is included within the blend. The translation and scaling vectors are quite easy to understand and adjust. But the rotation Quaternion XML is based on the internal x/y/z/w values which are rather meaningless to human eyes. To make the XML more meaningful, you can use CSGTransform and CSGQuaternion. CSGTransform will accept a rotation as either a 'com.jme3.math.Quaternion' definition or a 'net.wcomohundro.jme3.math.CSGQuaternion' definition. With the CSGQuaternion, you specify pitch/yawl/roll in radian degrees. You can also use the PI construct and write pitch="PI/2" to pitch the shape 90 degrees around the X-axis.

The import XML definition for the CSGShape transform can look like:

        <csgtransform class='net.wcomohundro.jme3.math.CSGTransform'>
            <translation class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
            <scale class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
            <rot class='net.wcomohundro.jme3.math.CSGQuaternion' yawl='PI/2' pitch='PI/32' roll='PI'/>
        </csgtransform>

Within the XML import defintion, you may find it more logically meaningful to operate with a grouping of subelements. For example, you plan to create a corridor which is an elongated box, from which you will subtract an arch. This could be done by starting with a Box, subtracting a half-height Box to get the lower cutout portion of the arch, then subtracting out a Cylinder to cut out the higher arched portion of the hole. But you have to be very careful to match the sizing/scaling/tranform of the smaller box and cylinder to keep everything in alignment.

A better approach is to create a single entity that represents the top arch blended with the square bottom. You can work in a simple unit environment to get everything aligned with appropriate textures. Then scale that entity appropriately and subtract it from the bigger box. You have this ability via the <shapes> definition within CSGShape. Rather than provide a simple Mesh, define a set of CSGShapes (along with their corresponding boolean operator) within a parent CSGShape. The subelement interior processing takes place before the parent shape is blended into the whole. This allows you to apply a transform at the parent level to get the scale and position you desire.

The import XML definition for the CSGShape subelements can look like:

    <net.wcomohundro.jme3.csg.CSGGeonode name='CSGGeometry' materialName='Textures/BrickWall/BrickWallRpt.xml' >
        <shapes>
            <net.wcomohundro.jme3.csg.CSGShape name='OuterBox'>
                <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                    	... outer box definition here ...
            </net.wcomohundro.jme3.csg.CSGShape>
                
            <net.wcomohundro.jme3.csg.CSGShape name='InteriorArch' operator='DIFFERENCE' >
                <shapes>
                    <net.wcomohundro.jme3.csg.CSGShape name='SquareBottom' operator='UNION'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGAxialBox' 
                                    xExtent='0.5' yExtent='0.25' zExtent='0.5'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1' scaleY='0.5'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='1' scaleY='0.5'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='BOTTOM'
                                    materialName='Textures/Rock/Rock1NormalRpt.xml'/>
                            </faceProperties>
                        </mesh>
                        <transform class='com.jme3.math.Transform'>
                            <translation class='com.jme3.math.Vector3f' x='0' y='-0.25' z='0'/>
                        </transform>
                    </net.wcomohundro.jme3.csg.CSGShape>
                    <net.wcomohundro.jme3.csg.CSGShape name='ArchedRoof' operator='UNION'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGCylinder' 
                                axisSamples='34' closed='true' zExtent='0.5' 
                                radialSamples='32' radius='0.5' textureMode='ROLLER'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='SIDES' scaleX='1' scaleY='PI'/>
                            </faceProperties>
                        </mesh>
                    </net.wcomohundro.jme3.csg.CSGShape>
                </shapes>
                ... now you adjust the texture scaling to match the desired cutout size ...
                <faceProperties>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1.9' scaleY='1.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='19.9' scaleY='1.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1.9' scaleY='19.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='SIDES' scaleX='19.9' scaleY='1.0'/>
                </faceProperties>
                <transform class='com.jme3.math.Transform'>
                    <scale class='com.jme3.math.Vector3f' x='1.90' y='1.90' z='19.9'/>
                </transform>
            </net.wcomohundro.jme3.csg.CSGShape> 
            ... other blending operations here ...
        </shapes>
    </net.wcomohundro.jme3.csg.CSGGeonode>

CSGEnvironment

CSGEnviroment defines the overall configuration parameters that control the CSG process. It allows you to set various tolerances and options which affect the inner workings of the shape generation. Unless you are planning on delving very deeply into the CSG code itself, the default setting should work for you.

By default, the IOB processor (working in double precision) is what is used. You can flip the entire system to use the BSP processor by including the following in your initialization code (before using any CSG service):

CSGEnvironment.resetEnvironment( new CSGEnvironmentBSP() );

CSGExternal

CSGExternal is a special extension of CSGShape that supports a Savable import option that loads its Mesh via the AssetManager.loadModel() function. There is no programmatic use, it only applies during import.

The import XML definitions look something like:

    <net.wcomohundro.jme3.csg.CSGExternal name='Teapot' operator='UNION' 
        model='Models/Teapot/Teapot.obj' />