Another CSG solution

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

Btw, some time ago, I was also having issues with INTERSECTION (that created big meshes while tiny were expected), so now I prepared a test case to check if it was still present, but the issue seems gone! Probably in function of the many changes/fixed you provided :smile:

But, while testing with this new code, I went into this exception:

	java.lang.IllegalArgumentException: CSGSolid.splitFaces - too many splits:629502
		at net.wcomohundro.jme3.csg.iob.CSGSolid.splitFaces(CSGSolid.java:206)
		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.composeSolid(CSGShapeIOB.java:614)
		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.intersection(CSGShapeIOB.java:311)
		at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.intersection(CSGShapeIOB.java:1)
		at net.wcomohundro.jme3.csg.CSGShape.intersection(CSGShape.java:670)
		at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:398)
		at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:345)

The actual problem with this is that regenerate() may take too long to end with exception. The minimum here took about 30s, but I saw it last like 5min (the most I waited anyway).

So, I would like to suggest this patch at CSGSolid.java (the throw InterruptedException line), but I dont know if it can cause any trouble elsewhere:

public void splitFaces(...){
  ...
  loop1:	for( int i = 0, j = initialLimit; i < (j = mFaces.size()); i += 1 ) {
    if(Thread.currentThread().isInterrupted())throw new InterruptedException();

With that, we can monitor the thread and interrupt it safely/cleanly at that spot (as just below it has another throw).

The actual point on interrupting is that small changes in the location of shapes may simply allow it to work as expected. Or alternatively, we can skip the regenerate() to release CPU if it is taking too long.

This new (a bit messy) test case below can be run differently by setting bThreaded = true, what demonstrates the InterruptedException “quick recover functionality” (if false it will simply pop the splitFaces() exception). Also, it will work without the InterruptedException patch, so the time these threads take to complete, the exceptions and the CPU usage will be noticeable:

public class TestCSGintersection extends SimpleApplication{
	public static void main(String[] 	pArgs) {
	    SimpleApplication app = new TestCSGintersection();		    
	    app.start();
	}
	
	private boolean	bThreaded = false; // set to true to skip the exceptions and retry
	private int	iMaxSluggishThreads = Runtime.getRuntime().availableProcessors();
	
	private CSGGeometry	geom;
	private CSGShape	shapePrev;
	private CSGShape	shapeSphere;
	private CSGShape	shapeSphereI;
	private int iInteractionCount;
	private boolean bDancing=false;
	ArrayList<RThread> aSluggishThreads = new ArrayList<RThread>();
	ArrayList<Vector3f> apos = new ArrayList<Vector3f>();
	ArrayList<Vector3f> aposI = new ArrayList<Vector3f>();
	private Material	mat;
	private long	ltimep;
	private CSGGeometry	geomI;
	private BoundingBox	bbPrev = new BoundingBox();
	
	public TestCSGintersection() {
		super( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
	}
	
	@Override
	public void simpleInitApp() {
  	geom = new CSGGeometry("hi");
  	
  	CSGShape aCube = new CSGShape("Box", new CSGBox(1,1,1));
  	geom.addShape(aCube);
  	geom.regenerate();
  	
    mat = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md" );
  	geom.setMaterial( mat );
  	
  	shapeSphere	= new CSGShape( "Sphere", new CSGSphere( 5, 5, 0.3f ) );
  	shapeSphereI = new CSGShape( "Sphere", new CSGSphere( 5, 5, 0.3f ) );
  	
   	rootNode.attachChild( geom );
   		
  	geomI = new CSGGeometry("hiI");
  	geomI.setMaterial(mat);
  	geomI.move(new Vector3f(2,0,0));
  	rootNode.attachChild(geomI);
   	
  	testErrSplits();
  	
  	System.out.println("private void testErrSplits(){");
  }
	
	/**
	 * intersection pos is predicted
	 */
	private void testErrSplits(){
		apos.add(new Vector3f(0.83990216f,0.01416303f,0.41397852f)); //1, [0:1454110976715ms][0:(0.0, 0.0, 0.0);0.0]
		apos.add(new Vector3f(0.14809741f,0.30853847f,0.32987046f)); //2, [1:58ms][1:(0.1648531, 0.2834636, 0.3);0.4444414]
		apos.add(new Vector3f(0.54515183f,0.29772508f,0.26086247f)); //3, [2:11ms][2:(0.2713526, 0.28531697, 0.3);0.49501315]
		apos.add(new Vector3f(0.50555390f,0.28294140f,0.73018849f)); //4, [3:8ms][3:(0.2713526, 0.27231753, 0.3);0.4876362]
		apos.add(new Vector3f(0.67798418f,0.78858733f,0.27823582f)); //5, [4:3ms][4:(0.27135253, 0.28531694, 0.29456878);0.4917405]
		apos.add(new Vector3f(0.00424497f,0.13861489f,0.73975629f)); //6, [5:5ms][5:(0.23324549, 0.24334115, 0.28711078);0.44277644]
		apos.add(new Vector3f(0.37070101f,0.58056056f,0.03278502f)); //7, [6:4ms][6:(0.24722548, 0.28531694, 0.24818724);0.45179987]
		//errDelay:29.85s
		/*
		java.lang.IllegalArgumentException: CSGSolid.splitFaces - too many splits:629502
			at net.wcomohundro.jme3.csg.iob.CSGSolid.splitFaces(CSGSolid.java:206)
			at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.composeSolid(CSGShapeIOB.java:614)
			at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.intersection(CSGShapeIOB.java:311)
			at net.wcomohundro.jme3.csg.iob.CSGShapeIOB.intersection(CSGShapeIOB.java:1)
			at net.wcomohundro.jme3.csg.CSGShape.intersection(CSGShape.java:670)
			at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:398)
			at net.wcomohundro.jme3.csg.CSGGeometry.regenerate(CSGGeometry.java:345)
		*/
	}
	
  @Override
  public void simpleUpdate(float tpf) {
  	org.lwjgl.input.Mouse.setGrabbed(false);
  	
  	if(shapePrev==null)shapePrev = new CSGShape("Box", new CSGBox(1,1,1));
  	geom.addShape(shapePrev);
  	
  	String fromArray="";
  	if(apos.size()>0){
  		shapeSphere.setLocalTranslation(apos.remove(0));
  		fromArray="A:";
  	}else{
  		shapeSphere.setLocalTranslation(geom.getLocalTranslation());
    	shapeSphere.move(randPos(null).multLocal(0.9f));
  	}
  	
  	System.out.println("apos.add(new Vector3f"+dump(shapeSphere.getLocalTranslation())+"); //"
  			+fromArray+(++iInteractionCount)+", "
  			+"["+(iInteractionCount-1)+":"+(System.currentTimeMillis()-ltimep)+"ms]"
  			+"["+(iInteractionCount-1)+":"+bbPrev.getExtent(null)+";"+bbPrev.getExtent(null).length()+"]"
  	);
  	ltimep = System.currentTimeMillis();
  	
  	geom.addShape( shapeSphere, CSGOperator.DIFFERENCE );
  	
  	try{
    	shapePrev = geom.regenerate();
    	if(bThreaded ){
      	while(!threadedDance()){
      		System.err.println("//Too many threads "+aSluggishThreads.size()+", unable to start a new."+System.currentTimeMillis()+"...");
      		Thread.sleep(3000);
      	}
    	}else{
    		intersectionDance();
    	}
  	}catch(Exception ex){
  		System.err.println("//errDelay:"+(System.currentTimeMillis()-ltimep)/1000f+"s");
  		System.err.println("/*");
  		ex.printStackTrace();
  		System.err.println("*/");
  		exit1();
  	}
  	
  	geom.removeAllShapes();
  }
  
  private class RThread extends Thread{
  	RRun r;
  	public RThread(RRun r,int i){
  		super(r);
  		this.r=r;
    	setName("thread:"+i);
  	}
  	public RRun r(){
  		return r;
  	}
  }
  private class RRun implements Runnable{
  	long lTime=System.currentTimeMillis();
		boolean bSuccess=false;
		int i;
		public RRun(int i){
			this.i=i;
		}
		@Override
		public void run() {
			try{
				intersectionDance();
				bSuccess=true;
	  	}catch(Exception ex){
	  		System.err.println("// EXCEPTION: thread:"+i+", "+getLifeTime()+", "+ex.getMessage());
	  		return; //would still be dancing... 
	  	}
			bDancing=false;
		}
		public String getLifeTime(){
			return ""+(System.currentTimeMillis()-lTime)/1000f+"s";
		}
  }
  
  private boolean threadedDance(){
  	boolean bStartedNewThread=false;
  	// avoid clog cpu
  	if(aSluggishThreads.size() < iMaxSluggishThreads ){
  		bDancing=true;
    	RThread t = new RThread(new RRun(iInteractionCount),iInteractionCount);
    	t.start();
    	bStartedNewThread=true;
    	
    	aSluggishThreads.add(t);
  	
	//  	int iHundredths=10;//100;
	  	int iHundredths=500;
	  	try {
	  		int iSleepCount=0;
	  		while(++iSleepCount<iHundredths){
	    		Thread.sleep(10);
	  			if(!t.isAlive()){
	//      		System.err.println("// THREAD-ENDED "+t.getName());
	  				aSluggishThreads.remove(t); //ended properly and in time
	  				break;
	  			}
	  		}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	  	
	  	if(bDancing){
	  		System.err.println("// THREAD-INTERRUPTING "+t.getName()+" intersection calc totThreads="+aSluggishThreads.size());
	  		t.setPriority(Thread.MIN_PRIORITY);
	  		t.interrupt();
	  	}
  	}
  	
		for(RThread t2 : aSluggishThreads.toArray(new RThread[0])){
			if(!t2.isAlive()){
				if(t2.r().bSuccess){
      		System.err.println("// THREAD actually ended properly: "+t2.getName()+", "+t2.r().getLifeTime());
				}//else was the exception log message
				aSluggishThreads.remove(t2);
			}
		}
		
		return bStartedNewThread;
  }
  
  private void intersectionDance(){
  	geomI.removeAllShapes(); //begin cleaning
  	
  	CSGShape shapePrevToI = new CSGShape("cloneToI",geom.getMesh().deepClone());
  	geomI.addShape(shapePrevToI);
  	
  	Vector3f pos = shapeSphere.getLocalTranslation();
  	shapeSphereI.setLocalTranslation(pos);
  	shapeSphereI.move(randPos(pos.length()).multLocal(0.1f));
  	geomI.addShape(shapeSphereI, CSGOperator.INTERSECTION);
  	
 		CSGShape shapeI = geomI.regenerate(); // <---<<
  	boundsCheck();
  }
  
  private void boundsCheck(){
	//	geomI.updateGeometricState();
		geomI.updateModelBound();
		BoundingBox bb = ((BoundingBox)geomI.getModelBound());
		bbPrev = bb;
		if(bb.getExtent(null).length() > 2f){
			System.err.println("//"+bb.toString());
			exit1();
		}
  }
  
  /**
   * 
   * @param fSeed grants predictable randoms for predefined positions array
   * @return
   */
  private Vector3f randPos(Float fSeed){
  	Random rnd = fSeed==null ? FastMath.rand : new Random(fSeed.longValue());
  	//return new Vector3f(rnd.nextFloat()*2f-1f, rnd.nextFloat()*2f-1f, rnd.nextFloat()*2f-1f);
  	return new Vector3f(rnd.nextFloat(), rnd.nextFloat(), rnd.nextFloat());
  }
  
	private String dump(Vector3f pos) {
		return String.format("(%01.8ff,%01.8ff,%01.8ff)",pos.x,pos.y,pos.z);
	}
	
	private void exit1(){
		System.err.println("}");
		System.exit(1);
	}
  
}

PS.: oh also another subject, I think GeometryBatchFactory can be helpful with CSGGeonode to provide a single mesh with many materials mixed and overall lightweight.

The bogus triangle example you posted in [20] above was very helpful in unearthing a couple of low level bugs that kicked in when triangles were very to close to each other. These have been fixed and the new code is available in SVN. The [20] example now seems to work properly.
The new processing looks pretty good to me (but that is what I said last time) Your eyes seem much better atuned at spotting such anomalies than mine, so please keep reporting on anything you may find.

I have implemented the interrupted thread processing you mentioned, but have not yet run your threading test case. I am working through a more comprehensive error reporting mechanism, but it is still in its early stages. Feel free to tinker and post back any comments.

1 Like

I have had a chance to work with your IntersectionTest and the infinite split problem. I tuned up the face-to-face scan in the split processor and optimized it to eliminate unnecessary checks after a face split occurs. And with the optimization in place, your test case no longer causes infinite splits.

I am thinking that the split problem was caused by something like this. Face7 in SolidA overlaps with Face10 in SolidB and must be split. After the optimization, the subfaces of 7 can start their checks with Face11 in SolidB. After all, we broke 7 apart to eliminate the overlap, so faces 1 thru 10 in B have already been handled. But before the optimization, we ran a full check on the newly added subfaces against all the SolidB faces. My guess is that because of rounding/near zero problems, one of the subfaces of 7A collided with 10B again, and then we split that subface. And then that subsubface of 7 collided with 10B and again and again and again…

If you find another example of the infinite split problem, please post it. I would like to eliminate all such hassles if possible.

FYI – the CSGTestSceneLoader now interrupts its background action thread on KEY_DELETE. The interrupted processing seems to work just fine. The thread monitor is checked in both the heavy load loops: split faces and raytrace.

1 Like

If anyone out there wants to use the jme3 SDK, I have rebuilt the CSG jar and posted it to SourceForge. It includes all the fixes mentioned in this thread.

3 Likes

I have just checked a new fix into SVN. It eliminates bogus overlapping triangles from two blended solids that start with a common surface.

2 Likes

I encountered another bogus triangle test case, which I have tracked down and fixed.
The changes are in SVN, and I have rebuilt the jars and posted everything back to SourceForge. The sample Windows executable now contains various applications, which you select via a dialog on startup

2 Likes

Nice, very glad to see that you are keeping this working :smile:

Can’t await to finally reach the destruction parts for my ships and use this more

1 Like

hey!

I tested a git clone, and it is working with jme3.1-beta1! but only the net.wcomohundro.jme3 code tree.

I would like to suggest, when releasing it, to put that core code tree as a separate jar to be an easy project plugin :slight_smile:

I am using this build.xml file:

<?xml version="1.0" ?>
<!-- Configuration of the Ant build system to generate a Jar file --> 
<project name="WCOmoCSG" default="CreateJar">
  <target name="CreateJar" description="Create Jar file">
    <jar jarfile="WCOmoCSG.jar" basedir="bin" includes="**/*.class" />
  </target>
  <target name="CreateSourcesJar" description="Create Sources Jar file">
    <jar jarfile="WCOmoCSG-sources.jar" basedir="src" includes="**/*.java" />
  </target>
</project>

and as it seems you are using eclipse too, here is to the point instructions http://stackoverflow.com/a/12200309/1422630 (make sure to read the comments as they improved that answer).

PS.: I actually had to remove the other code tree, so I did not specify the net path yet.

Good suggestion, I will try to break the code apart in a future release. Thanks for the sample Ant build.

1 Like

How do I compile that with ant? I tried build.xml above but this creates empty .jar file with META-INF only

Sorry, but I never really followed up with the Ant build script, so I honestly do not know what might be going wrong.
FYI - while the CSG code continues to work for me, I am building with older jMonkey code. I can not say what might happen with the newest revisions of jMonkey. Upgrading to the latest release of jMonkey is on my todo list, but it is not yet done (and I have no target date in mind)

How did you pack your code to jar? I wanted to upgrade this to work with new versions but there was no sources inside jar. I think I could just drop all the files in my project tree and use them

P.S. What version of jme you’re using?

  1. I run everything through Eclipse and do not use the jMonkey IDE at all.
  2. Full CSG source is available via SourceForge
    (jMonkeyCSG / Code / [r280]). It has a ‘snapshot’
    option that will load all the source into a zip for you.
  3. When I was actively developing, I was pulling the latest jme3 source
    directly from GitHub. The last time I pulled, jMonkey was at version 5454,
    which was before they did the 3.1 release.
  4. I leverage the XML loader extensively to do my testing. But that means I had
    to patch several places in the jme3 core where the existing XML read mechanism
    would corrupt the target entity. You can see which jme3 core sources I changed
    by looking in the source code com.jme3 package. All the CSG source is isolated
    in its own package.
  5. CSG might possibly run without any of the jme3 core changes, but not via XML
    loading. I have never really tested that, so don’t hold your breath.

I have completed changes necessary to get the CSG code back in line with the jme3 releases. My latest version is now completely compatible with the jme3 3.2.1 SDK. Everything is checked back into SourceForge, and the ReadMe there describes the changes to the jar structure. You can now easily incorporate CSG into your jme3 IDE just by adding a single jar into your project.

I hope this helps. Please post any issues via the SourceForge Tickets.

3 Likes

Seriously, this is so cool!