Another CSG solution

In my experimentation with CSG for jMonkey, I have re-implemented two approaches within a single code framework. The first is based on Binary Space Partitioning, as provided by Evan Wallace (javascript) and fabsterpal (jMonkey java). My implementation eliminated some recursive overflow problems and supports extensive customization of tolerances and other run-time constraints.
Unfortunately, my experience and further research on the web seems to indicate that BSP is subject to artifacts when dealing with complex shapes with a large number of triangles. This is a precision problem inherent in the algorithm.
While looking for a different approach, I found the Unbboolean code written for Java3D. I ported this code into jMonkey and extended it to handle jMonkey Normals and TextureCoordinates. It produces reliable shapes from extremely complex elements without producing any artifacts. I have not yet found a test case that fails.
Another CSG thread mentioned Unbboolean but shied away due to its GPL license. I contacted the original author and he has graciously changed his license to be totally Public Domain with no restrictions.
My code is available at the jMonkeyCSG project on SourceForge and is freely available for anyone’s use. I have marked the entire project as BSD license matching the jMonkey standard.

The SourceForge jMonkeyCSG web site contains further information. The code is fully functional and ready for use. If you are interested in CSG, please give it try and report problems/issues/enhancements via the SourceForge ticketing system. I am happy to help out anyone who is interested.

16 Likes

…very useful…thank you very much…

Wow this is great :smile:

Many thanks to you for also keeping an eye onto the licenses :slight_smile: (Also many thanks to the original author, if you read this)

1 Like

having lots of fun with it thx! :smiley:

btw, I wonder if regenerate() could have as it’s first action this below, so we start from the previous iteration over the mesh?

CSGShape self = new CSGShape("Self", this);
this.addShape(self,CSGOperator.MERGE);

and as it’s last action, to not accumulate shapes?

mShapes.clear();

also a method to simply clear all shapes would be handy.

may be these extras could be optional or default (but still optional).

I am using the SVN, as I saw many improvements.

thx!

I am not 100% sure of exactly what you are looking for, but I have changed a couple of things.

  1. The method ‘removeAllShapes()’ is now supported to give you that explicit clear operator you are looking for. It cannot be built into the regenerate() step itself, since some usages want to regenerate on the same set of given shapes over and over again. @see CSGTestJ for an example of dynamic regeneration. Explicitly calling removeAllShapes() after you call regenerate() will allow you to start over from scratch.
  2. I have changed the method signature for regenerate() to return the final CSGShape it produces within itself. I do not have a test case for this, but I am assuming you want to take the final shape and then blend something more back into it as a secondary step.
  3. You already have the ability to regenerate the shape and display the result, and then add more shapes and regenerate again. The end result should be the same as if you never called regenerate in the middle.

I hope this addresses your needs. My latest changes are back in SVN, ready for use. Please keep posting back with suggestions and/or problems. Like all open-source, the more people who poke at it, the better the code becomes. And I am always curious as to what people are building.

1 Like
  1. cool thx!

  2. that is exactly that! I need the final result to be ready for the next interaction step. The original mesh, and all previous interactions, are to be lost and its final state is only what matters.
    With the new regenerate() I can collect the final shape and reuse it on the next interaction, so I think that time spent on merging at every interation now can be avoided, thx!

When I first tried using it, I instantiated the CSGGeometry using the initial mesh structure. Then, i thought that just by adding a CSGShape new operation (like subtract) and calling regenerate() it would apply that new shape operation into the currently configured/stored mesh, and that puzzled me for a few hours :slight_smile:
Until I found out that I had to initially add the self CSGGeometry current mesh as a CSGShape with CSGOperator.MERGE for it to work as I was expecting, a progressive operation workflow :).
Do you mean this usage is already possible in some way without re-adding the self mesh initially as CSGShape?

EDIT: (@3) may be, an option like “setProgressiveMode()” could do internally at CSGGeometry, what I am doing externally, but that may be a very specific case where may be only I will use, so I should just keep implementing that externally :slight_smile:

EDIT: (@2) I was making some tests, the returned CSGShape from regenerate() seems to come without a mesh? when I tried re-adding it with CSGOperator.MERGE at the next interaction, the mesh vanished. It seems to contain mesh information as a toMesh() from it produced something at regenerate() to uset at CSGGeometry.setMesh(), I just cant figure how to apply the mesh information it contains at self CSGShape.setMesh(), any tip?

Sorry for the confusion with point (3). What I was trying to say was that the underlying CSGGeometry retains its list of shapes blended into it. A regenerate produces a mesh without any loss of those shapes. You could then blend in another shape to the original list, and a regenerate would start over from scratch, processing the now N+1 count of shapes. This could be useful for some semi-dynamic processing.

There was no way to take the final result of a prior regenerate and use it as a single element and then blend more shapes on top of it. That is what I think you want, and what I was trying to give you with the return value of the regenerate() method. But if you are having problems, I probably just screwed up.

Do you have an example I could turn into a test case? I would then add it back into the SVN repository. That is how CSGTestJ was created. I would rather work on something that will give you exactly what you want. Of course, the simpler, the better.

As I remember long ago, when I was playing with UnBBoolean, and found the way it works, that the shapes were always required to be re-added/set again, right?

I also read that there are some kinds of code that should not go into the library to not lose its focus requiring useless maintenance, better keep it strong/stable and simple. I think it was about BetterCharaterControl here at JME where I was requesting legs behavior hehe… but that is something specific to my game, not something exactly useful to everyone else, and… would be troubling to implement and maintain, so better I do it alone.

So, as I can actually workaround it without any trouble, to my needs, I can just extend CSGGeometry and implement whatever I want the way I want, without creating trouble in the library you implemented possibly making it lose focus.

Dumb me… I saw you implemented mShapes as protected, so… that clear shapes method I could have implemented here :blush:

The best thing for me is how stable, after several interactions, jMonkeyCSG is, not a single problem til now! Keep it strong bro! Better not add these specific requirements to not mess it…

thx! :smiley:

Check out CSGTestL - which implements what I belive is what you want for ‘progressive’ style processing. It is similar to CSGTestJ which implements a dynamic display. TestL starts with a box, and has a rotating cylinder. The box is displayed, the cylinder is not visible. When you press SPACE, the cylinder is blended into what the last resultant shape was. As you press SPACE, the shape gets more and more complex. But it is the result of a single Union between the old shape and the rotating cylinder.
This test case allowed me to find and fix a couple of initialization/refresh bugs, which would certainly be getting in your way. Hopefully, you now have everything you need to get your game working. You are certainly free to subclass any of the CSG code. But when I get a reasonable request for something I just did not think of, I have no problem trying to implement it in the base library.

1 Like

wow, TestJ is… impressive! I would call it Morphing (the letters confuse be a bit hehe).

yes cool! TestL is exactly the Progressive mode thx!
My sole concern about I implementing it outside, was that by not knowing the internals, it could not get optimized, but that is just a guess.

EDIT: just a thought, so basically CSGGeometry.mesh is just a holder of the result, but not an actor when applying operations right? Initially, I thought that mesh would be promptly used as the initial shape/stage. Basically, this thought is the Progressive mode :slight_smile:

PS.: Btw, this file seems broken, but I just rename it to .broken and the remainder works fine: src/com/jme3/bullet/objects/PhysicsRigidBody.java

In refining the progressive testcase CSGTestL, I wanted to invoke my updates and regeneration in a background thread, to keep it from delaying the rendering process. And I immediately ran into the JME restriction that all scene changes must be applied within the single supplied Update thread.
So I have made CSGGeometry and CSGGeonode thread aware. This means that the regeneration has been broken into two internal steps. The heavy lifting of all the face construction/blending is done first, and the lighter weight scene changes are done second.
You can call the method .deferSceneChanges(true) to set a flag to suppress the second step. In that case, the regenerate changes are retained internally. Then an explicit call to .applySceneChanges() will apply those saved changes to the scene.
The processing is properly thread synchronized so that you can move all your updates/regenerations into a background thread. Within your .update() method, just include the call to .applySceneChanges(). It knows if any changes are pending and applies them to the scene when ready. If there are no changes, or if the background thread is still processing, it just returns and waits for the next iteration.

These changes are available in the latest SVN code.

1 Like

Works! btw, do you think applySceneChanges() could return false if mMeshManager is not set, so when it is able to actually apply the changes, we would know and do other things like apply physics and many other things related to it? As I think the alternative would be to check if its mesh changed :slight_smile:

EDIT: btw, the returned shape from regenerate() is working great too! I set it to the very geometry with setUserData()

btw, I got an exception (it is in a comment in the code below) for Progressive subtraction, below is the test case.
It will not fail by using a more complex sphere ex: new CSGSphere( 15, 15, 0.3f ).
Any idea what could be the good minimum mesh (maximum simplicity)?

public class TestCSGsubtraction extends SimpleApplication{
	public static void main(String[] 	pArgs) {
	    SimpleApplication app = new TestCSGsubtraction();		    
	    app.start();
	}
	
	private CSGGeometry	geom;
	private CSGShape	shapePrev;
	private CSGShape	aSphere;

	public TestCSGsubtraction() {
		super( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
	}

	ArrayList<Vector3f> apos = new ArrayList<Vector3f>();
	
	@Override
	public void simpleInitApp() {
  	geom = new CSGGeometry("hi");
  	
  	CSGShape aCube = new CSGShape("Box", new CSGBox(1,1,1));
  	geom.addShape(aCube);
  	geom.regenerate();
  	
    Material mat_csg = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md" );
  	geom.setMaterial( mat_csg );
  	
  	aSphere = new CSGShape( "Sphere", new CSGSphere( 5, 5, 0.3f ) );
  	
   	rootNode.attachChild( geom );
   	
   	apos.add(new Vector3f(0.80005103f,0.06314170f,0.37440395f));
   	apos.add(new Vector3f(0.28343803f,0.43108004f,0.48746461f));
   	apos.add(new Vector3f(0.12868416f,0.01397777f,0.45756876f));
   	apos.add(new Vector3f(0.77755153f,0.57811391f,0.31153154f));
   	apos.add(new Vector3f(0.54848605f,0.43463808f,0.08887690f));
   	apos.add(new Vector3f(0.44293296f,0.26147729f,0.59465259f));
   	apos.add(new Vector3f(0.49685276f,0.70028597f,0.25030762f));
   	apos.add(new Vector3f(0.69402820f,0.70010322f,0.17900819f));
   	apos.add(new Vector3f(0.60632563f,0.76545787f,0.00868839f));
   	apos.add(new Vector3f(0.07231724f,0.82769102f,0.52386624f));
   	apos.add(new Vector3f(0.57993245f,0.11181229f,0.35487765f));
   	apos.add(new Vector3f(0.76333338f,0.57130849f,0.41553390f));
   	apos.add(new Vector3f(0.90540063f,0.15399516f,0.31880611f));
   	apos.add(new Vector3f(0.48946434f,0.56392515f,0.92612267f));
   	apos.add(new Vector3f(0.20164603f,0.28323406f,0.33062303f));
   	apos.add(new Vector3f(0.48186016f,0.83080268f,0.67133898f));
   	apos.add(new Vector3f(0.46120077f,0.96177906f,0.63590240f));
   	apos.add(new Vector3f(0.24107927f,0.82240766f,0.69494921f));
   	apos.add(new Vector3f(0.80022192f,0.86946529f,0.77864534f));
   	apos.add(new Vector3f(0.21218884f,0.17488194f,0.89337271f));
   	apos.add(new Vector3f(0.97576815f,0.74024606f,0.29970086f));
   	apos.add(new Vector3f(0.17829001f,0.22013688f,0.41068947f));
   	apos.add(new Vector3f(0.86896533f,0.60720319f,0.40294641f));
   	apos.add(new Vector3f(0.60520381f,0.94561607f,0.30677772f));
   	apos.add(new Vector3f(0.79588902f,0.16585821f,0.37771285f));
   	apos.add(new Vector3f(0.74539816f,0.76406616f,0.57494253f));
   	apos.add(new Vector3f(0.27781421f,0.94732559f,0.99107915f));
   	apos.add(new Vector3f(0.55746430f,0.05100375f,0.81786209f));
   	apos.add(new Vector3f(0.19038093f,0.98681915f,0.40325123f));
   	apos.add(new Vector3f(0.64273006f,0.25286716f,0.04005140f));
   	/**
   	 * the above iterations, freezes 15 seconds after the last one, lead to this exception:
   	 * 
   	java.lang.IllegalArgumentException: Invalid new Face: Face:	Vtx(0.82303453942848, 0.43463826179504395, -0.006109714508056641)
   		Vtx(0.82303453942848, 0.4346381425857544, -0.006109714508056641)
   		Vtx(0.82303453942848, 0.43463802337646484, -0.006109714508056641)
   		 INVALID PLANE
   		at net.wcomohundro.jme3.csg.iob.CSGSolid.addFace(CSGSolid.java:145)
   		at net.wcomohundro.jme3.csg.iob.CSGSolid.breakFaceInThree(CSGSolid.java:611)
   		at net.wcomohundro.jme3.csg.iob.CSGSolid.splitFace(CSGSolid.java:416)
   		at net.wcomohundro.jme3.csg.iob.CSGSolid.splitFaces(CSGSolid.java:239)
   		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.composeSolid(CSGShapeIOB.java:615)
   		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.difference(CSGShapeIOB.java:281)
   		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.difference(CSGShapeIOB.java:1)
   		at net.wcomohundro.jme3.csg.CSGShape.difference(CSGShape.java:658)
   		at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:375)
   		at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:332)
   	 */
   	
   	//apos.clear(); //uncomment to have a fresh test, iterations and frozen time will vary
  }
	
  @Override
  public void simpleUpdate(float tpf) {
  	if(shapePrev==null)shapePrev = new CSGShape("Box", new CSGBox(1,1,1));
  	geom.addShape(shapePrev);
  	
  	if(apos.size()>0){
  		aSphere.setLocalTranslation(apos.remove(0));
  	}else{
  		aSphere.setLocalTranslation(geom.getLocalTranslation());
    	aSphere.move(new Vector3f(FastMath.rand.nextFloat(),FastMath.rand.nextFloat(),FastMath.rand.nextFloat()).multLocal(1f));
  	}
  	System.out.println("apos.add(new Vector3f"+dump(aSphere.getLocalTranslation())+");");
  	geom.addShape( aSphere, CSGOperator.DIFFERENCE );
  	
  	try{
    	shapePrev = geom.regenerate();
  	}catch(Exception e){
  		e.printStackTrace();
  		System.exit(1);
  	}
  	
  	geom.removeAllShapes();
  }

	private String dump(Vector3f pos) {
		return String.format("(%01.8ff,%01.8ff,%01.8ff)",pos.x,pos.y,pos.z);
	}
  
}

I have grabbed your sample and reworked it a bit to become CSGTestM (all checked into SVN) I changed the blend to operate multi-threaded (so the UI remains active during regeneration) and trigger the blend on key-click rather than continuous.

The shape is definitely being corrupted at some point, and I am guessing that corruption is causing the infinite loop. I am currently working a rather simple test case that results in an obviously missing triangle. Perhaps that test will reveal an underlying algorithm flaw that fixes it all. In any case, thanks for the code – I will be poking at it until CSG starts behaving itself.

1 Like

A bit of progress – TestM has allowed me to look into some calculation conflicts when very small triangles are produced. Floating point precision and near-zero issues can result in an intersection point calculated between two planes subsequently not testing true as being on the plane. The trick is to detect such conditions properly and to discard the triangles that cause the problem. If the triangle is that small as to cause such a problem, it should not really influence the visual.

I believe I have solved the infinite loop problem. TestM now always seems to produce something. But I am still seeing some missing triangles. The infinite loop solution has been checked into SVN.

1 Like

cool,

btw, the low poly sphere new CSGSphere( 10, 10, 0.3f ) is causing glitches:

but the a bit more complex one new CSGSphere( 15, 15, 0.3f ) (despite a bit slower computations), seems to provide consistently good results:

EDIT: mmm, it doesnt seem about mesh complexity, but about “magic numbers”, this gives great results also new CSGSphere(7, 7, 0.3f )

You are seeing the same missing-triangle problem that I mentioned and have been digging for. Luckily, your sample code (now CSGTestM) gave me a reasonably simple test scenario. I was able to determine that a triangle went missing when it was incorrectly categorized as INSIDE versus OUTSIDE the solid. In my example, triangles all around the culprit were properly marked OUTSIDE (and included in the final solid) but the guilty party was marked INSIDE. This was due to a point-in-triangle calculation.
I am an old-time, business logic, dbms, java developer. I make no claims about understanding the math behind the 3D processing of jMonkey. The interior CSG processing logic is a rework of other open source. I can clean up the logic, match jMonkey conventions, and tidy up code, but I have to trust the original author when it comes to the math. In this case, there was a method checking if a point cast by a ray intersected within a given triangle. And in rare conditions, it looked to me like the answer was wrong. It happened when the y and z conditions were very, very close and it was strictly x that determined the hit or miss. I had no idea of how to repair the original code, but the jMonkey Ray class has a method that does this. I was able to copy the code into the ‘double’ based processing of my CSG system. And that method seems to give me better results.

My changes have been checked back into SVN, ready for more testing. The results are much, much better. You can watch the TestM block of cheese being slowly nibbled away by the spherical mouse. (did you notice the update to TestM that runs continuously if you hold down the space bar?)

I am still seeing a missing triangle on occasion, but I am also seeing a warning in the log when that happens. I will keep investigating, but overall, things are looking better. Thank you so much for you work on this. Your extremely clear test code made all the difference.

3 Likes

Yes, I’ve been holding space since 3rd time I ran it :).

The test with new CSGSphere( 10, 10, 0.3f ) now seems to work great indeed! I think that is a good looking minimum in mesh complexity for sphere at least, I think I will stick with it.

Also, if it is related to floating (double) precision, may be we are hitting a hardware limitation here and a workaround/fix may not be doable.

Anyway, just out of curiosity I did some tests with 5,5 and 7,7 spheres, the glitches seem to fit on your description. Also, some faces are placed outside of the box, like they are considered as being part of the mesh that must remain, while they actually should have been dropped, that could really be a bug.

But that low poly spheres doesn’t give good looking results anyway.

I have been hitting TestM pretty hard, tweaking the various tolerances and rendering parameters, and trying to chase down the spurious/missing triangles.

The 5,5 sphere really stresses things, since so many of the surfaces are close to parallel (which is where it often hits the fan). After a lot of experiments, I have checked-in my last set of changes. To me, these are looking good. With 5,5 7,7 and 12,12 spheres, I have not yet noticed a bogus triangle. And that is running TestM up to hundreds of iterations.

So give it another try and let me know what you find. If you see a bad triangle and can reproduce the steps, please post it and I will try to incorporate it into TestM.
Thanks again for all the feedback.

1 Like

It is much harder to get any bug now, 12,12 7,7 even 5,5 seems to provide very consistent results!

Basically, I didnt manage to get a single problem with 7,7.

And indeed, 5,5 seems a critical region, around out of 4 attempts (restarts of testM), one provides a bug face after many interactions:

the bug triangle seen from below

nothing seen from above

btw, around step 96, it already shows something:

	sPositions.add( new Vector3f(0.27770352f,0.46122688f,0.51517385f)); // 1
	sPositions.add( new Vector3f(0.27608168f,0.34551549f,0.06539202f)); // 2
	sPositions.add( new Vector3f(0.52630138f,0.03579539f,0.02474916f)); // 3
	sPositions.add( new Vector3f(0.20595354f,0.66981322f,0.42971194f)); // 4
	sPositions.add( new Vector3f(0.18904364f,0.52505225f,0.93140197f)); // 5
	sPositions.add( new Vector3f(0.97935265f,0.68711704f,0.48158187f)); // 6
	sPositions.add( new Vector3f(0.48596126f,0.23843467f,0.93357599f)); // 7
	sPositions.add( new Vector3f(0.01421112f,0.27380580f,0.19773597f)); // 8
	sPositions.add( new Vector3f(0.71298790f,0.87922210f,0.51476270f)); // 9
	sPositions.add( new Vector3f(0.79485250f,0.75485551f,0.99515438f)); // 10
	sPositions.add( new Vector3f(0.77157128f,0.77839476f,0.05931848f)); // 11
	sPositions.add( new Vector3f(0.33765650f,0.29436308f,0.59538013f)); // 12
	sPositions.add( new Vector3f(0.64518267f,0.11867213f,0.96176815f)); // 13
	sPositions.add( new Vector3f(0.24848801f,0.15109754f,0.61993992f)); // 14
	sPositions.add( new Vector3f(0.45582271f,0.61759496f,0.86084294f)); // 15
	sPositions.add( new Vector3f(0.80071312f,0.78237444f,0.29684871f)); // 16
	sPositions.add( new Vector3f(0.32384932f,0.78567892f,0.68176097f)); // 17
	sPositions.add( new Vector3f(0.88186044f,0.89698583f,0.38646924f)); // 18
	sPositions.add( new Vector3f(0.31246120f,0.99689889f,0.60366225f)); // 19
	sPositions.add( new Vector3f(0.69722843f,0.17010564f,0.40289277f)); // 20
	sPositions.add( new Vector3f(0.56737870f,0.51288295f,0.00321507f)); // 21
	sPositions.add( new Vector3f(0.82534564f,0.57062274f,0.28261465f)); // 22
	sPositions.add( new Vector3f(0.37348580f,0.81682342f,0.67368442f)); // 23
	sPositions.add( new Vector3f(0.66826415f,0.99113899f,0.97949696f)); // 24
	sPositions.add( new Vector3f(0.22438675f,0.61881346f,0.43422037f)); // 25
	sPositions.add( new Vector3f(0.25544399f,0.43739003f,0.49165052f)); // 26
	sPositions.add( new Vector3f(0.87480676f,0.11029208f,0.22472137f)); // 27
	sPositions.add( new Vector3f(0.89608353f,0.91551888f,0.23170120f)); // 28
	sPositions.add( new Vector3f(0.33723837f,0.05473995f,0.15784162f)); // 29
	sPositions.add( new Vector3f(0.60865152f,0.61520189f,0.97737426f)); // 30
	sPositions.add( new Vector3f(0.14142460f,0.39914387f,0.24599236f)); // 31
	sPositions.add( new Vector3f(0.22566748f,0.41332263f,0.00950694f)); // 32
	sPositions.add( new Vector3f(0.15485024f,0.57639825f,0.70119578f)); // 33
	sPositions.add( new Vector3f(0.05290598f,0.79258788f,0.22659695f)); // 34
	sPositions.add( new Vector3f(0.10861605f,0.91088176f,0.68035346f)); // 35
	sPositions.add( new Vector3f(0.04377919f,0.44823188f,0.15094322f)); // 36
	sPositions.add( new Vector3f(0.68920767f,0.66604137f,0.94466478f)); // 37
	sPositions.add( new Vector3f(0.04464799f,0.50060058f,0.37275547f)); // 38
	sPositions.add( new Vector3f(0.62931901f,0.45162231f,0.35753506f)); // 39
	sPositions.add( new Vector3f(0.65202111f,0.51006013f,0.78109592f)); // 40
	sPositions.add( new Vector3f(0.68563026f,0.31424040f,0.61688083f)); // 41
	sPositions.add( new Vector3f(0.08739573f,0.56442362f,0.07361543f)); // 42
	sPositions.add( new Vector3f(0.02775884f,0.40120864f,0.25368088f)); // 43
	sPositions.add( new Vector3f(0.42561126f,0.08508533f,0.52417850f)); // 44
	sPositions.add( new Vector3f(0.93444425f,0.77615744f,0.98109519f)); // 45
	sPositions.add( new Vector3f(0.92854166f,0.68288749f,0.46841764f)); // 46
	sPositions.add( new Vector3f(0.37007278f,0.39217353f,0.14524925f)); // 47
	sPositions.add( new Vector3f(0.14926779f,0.44578153f,0.17686743f)); // 48
	sPositions.add( new Vector3f(0.87827176f,0.81727195f,0.18523437f)); // 49
	sPositions.add( new Vector3f(0.11627805f,0.33141774f,0.59711856f)); // 50
	sPositions.add( new Vector3f(0.02587420f,0.94024926f,0.71002156f)); // 51
	sPositions.add( new Vector3f(0.44550377f,0.82438296f,0.62283421f)); // 52
	sPositions.add( new Vector3f(0.72342467f,0.46676254f,0.39422596f)); // 53
	sPositions.add( new Vector3f(0.92897040f,0.50566000f,0.61273122f)); // 54
	sPositions.add( new Vector3f(0.47882491f,0.65437132f,0.83060253f)); // 55
	sPositions.add( new Vector3f(0.27306068f,0.57173127f,0.13774878f)); // 56
	sPositions.add( new Vector3f(0.42655313f,0.59532160f,0.31875283f)); // 57
	sPositions.add( new Vector3f(0.38511860f,0.54043925f,0.47768301f)); // 58
	sPositions.add( new Vector3f(0.24471194f,0.82192814f,0.48830944f)); // 59
	sPositions.add( new Vector3f(0.67278528f,0.54504466f,0.09745085f)); // 60
	sPositions.add( new Vector3f(0.35128516f,0.65485710f,0.61607915f)); // 61
	sPositions.add( new Vector3f(0.15284234f,0.09497422f,0.11146510f)); // 62
	sPositions.add( new Vector3f(0.70446914f,0.65306264f,0.58151633f)); // 63
	sPositions.add( new Vector3f(0.60797894f,0.51344681f,0.92530870f)); // 64
	sPositions.add( new Vector3f(0.34852356f,0.07145715f,0.17945975f)); // 65
	sPositions.add( new Vector3f(0.37254894f,0.80542964f,0.88890177f)); // 66
	sPositions.add( new Vector3f(0.73616028f,0.39821291f,0.06801569f)); // 67
	sPositions.add( new Vector3f(0.20351452f,0.91303581f,0.72320229f)); // 68
	sPositions.add( new Vector3f(0.95461959f,0.21565855f,0.91324383f)); // 69
	sPositions.add( new Vector3f(0.46340734f,0.40685672f,0.19976103f)); // 70
	sPositions.add( new Vector3f(0.11011094f,0.90722120f,0.49331629f)); // 71
	sPositions.add( new Vector3f(0.48457295f,0.46555865f,0.85865045f)); // 72
	sPositions.add( new Vector3f(0.19199139f,0.71535790f,0.86834544f)); // 73
	sPositions.add( new Vector3f(0.04115498f,0.82713747f,0.42915702f)); // 74
	sPositions.add( new Vector3f(0.61800349f,0.06707484f,0.62543017f)); // 75
	sPositions.add( new Vector3f(0.88783878f,0.43486172f,0.12229562f)); // 76
	sPositions.add( new Vector3f(0.63693637f,0.49088907f,0.79137754f)); // 77
	sPositions.add( new Vector3f(0.87867749f,0.78637511f,0.14664972f)); // 78
	sPositions.add( new Vector3f(0.05955619f,0.38885713f,0.14290255f)); // 79
	sPositions.add( new Vector3f(0.13591242f,0.11856502f,0.32858527f)); // 80
	sPositions.add( new Vector3f(0.45130950f,0.12749821f,0.22140574f)); // 81
	sPositions.add( new Vector3f(0.99816906f,0.39879960f,0.67688763f)); // 82
	sPositions.add( new Vector3f(0.27599066f,0.87235457f,0.10976708f)); // 83
	sPositions.add( new Vector3f(0.28443813f,0.34108049f,0.17074001f)); // 84
	sPositions.add( new Vector3f(0.59220099f,0.64068037f,0.29883301f)); // 85
	sPositions.add( new Vector3f(0.21032435f,0.69509870f,0.22718686f)); // 86
	sPositions.add( new Vector3f(0.96133840f,0.07793272f,0.98381025f)); // 87
	sPositions.add( new Vector3f(0.86025840f,0.55727190f,0.13104689f)); // 88
	sPositions.add( new Vector3f(0.47240525f,0.90199143f,0.77609187f)); // 89
	sPositions.add( new Vector3f(0.78712440f,0.55742973f,0.76311892f)); // 90
	sPositions.add( new Vector3f(0.61108696f,0.10808033f,0.72990811f)); // 91
	sPositions.add( new Vector3f(0.48142982f,0.81970024f,0.52060682f)); // 92
	sPositions.add( new Vector3f(0.67186862f,0.77876258f,0.23121035f)); // 93
	sPositions.add( new Vector3f(0.15493429f,0.40537888f,0.23185605f)); // 94
	sPositions.add( new Vector3f(0.58995610f,0.33311659f,0.00705606f)); // 95
	sPositions.add( new Vector3f(0.39417428f,0.47937870f,0.61971760f)); // 96
	sPositions.add( new Vector3f(0.54585254f,0.68676496f,0.87139761f)); // 97
	sPositions.add( new Vector3f(0.06776696f,0.87885374f,0.14896846f)); // 98
	sPositions.add( new Vector3f(0.37081677f,0.18404460f,0.54669964f)); // 99
	sPositions.add( new Vector3f(0.17017645f,0.54999292f,0.11128503f)); // 100
	sPositions.add( new Vector3f(0.56053740f,0.78079951f,0.30992448f)); // 101
	sPositions.add( new Vector3f(0.32001537f,0.44523436f,0.08620554f)); // 102
	sPositions.add( new Vector3f(0.76590687f,0.03684270f,0.59364378f)); // 103
	sPositions.add( new Vector3f(0.80683553f,0.18522841f,0.85459214f)); // 104
	sPositions.add( new Vector3f(0.15438688f,0.41930842f,0.33797449f)); // 105
	sPositions.add( new Vector3f(0.69640076f,0.19685644f,0.05853683f)); // 106
	sPositions.add( new Vector3f(0.73739821f,0.09245175f,0.41357023f)); // 107
	sPositions.add( new Vector3f(0.57426214f,0.51166886f,0.70436966f)); // 108
	sPositions.add( new Vector3f(0.78882986f,0.07467782f,0.57291675f)); // 109
	sPositions.add( new Vector3f(0.15640312f,0.40672356f,0.02575749f)); // 110
	sPositions.add( new Vector3f(0.73176700f,0.54052198f,0.32932806f)); // 111
	sPositions.add( new Vector3f(0.80149478f,0.98603159f,0.81871510f)); // 112
	sPositions.add( new Vector3f(0.45161510f,0.24455494f,0.32157898f)); // 113
	sPositions.add( new Vector3f(0.69247639f,0.65278059f,0.74903792f)); // 114
	sPositions.add( new Vector3f(0.57290655f,0.11720842f,0.89983869f)); // 115
	sPositions.add( new Vector3f(0.02047265f,0.66252160f,0.35670352f)); // 116
	sPositions.add( new Vector3f(0.21933514f,0.60925192f,0.47442764f)); // 117
	sPositions.add( new Vector3f(0.47672379f,0.40354192f,0.61204731f)); // 118
	sPositions.add( new Vector3f(0.71098053f,0.22188783f,0.74071270f)); // 119
	sPositions.add( new Vector3f(0.35400301f,0.96791077f,0.88958502f)); // 120
	sPositions.add( new Vector3f(0.00277239f,0.11852777f,0.46382076f)); // 121
	sPositions.add( new Vector3f(0.76961392f,0.95164806f,0.00021791f)); // 122
	sPositions.add( new Vector3f(0.04299396f,0.94793499f,0.06643200f)); // 123