Triangle Collision detection fails for SharedMeshes that have the same target

Hey guys, theres a problem with the way shared meshes do Triangle collision detection.



Heres the problem code:

    public void findTriangleCollision(TriMesh toCheck, int batchIndex1, int batchIndex2,
            ArrayList<Integer> thisIndex, ArrayList<Integer> otherIndex) {
       
       CollisionTree myTree = CollisionTreeManager.getInstance().getCollisionTree(getBatch(batchIndex1));
       CollisionTree otherTree = CollisionTreeManager.getInstance().getCollisionTree(toCheck.getBatch(batchIndex2));



This is in TriMesh. When a shared mesh does triangle collision, it passes the call off to its target TriMesh.
The target TriMesh then uses the Tree Manager to get its collision tree, and the collision tree of the TriMesh its colliding against.

However, if that TriMesh is also sharing the same target, then "otherTree" comes back as equal to "myTree."

Then, of course, the tree is collided against itself and always detects a collision.

I'm just looking into a few possible solutions now. It comes down to needing two unique CollisionTree instances for each target mesh, in case this situation comes up.



One solution would be for the TriMesh to detect when it is intersecting two SharedMeshes with the same target, and instantiate a new CollisionTree right there to deal with it. This would, however, defeat the purpose of CollisionTreeManager, at least for SharedMeshes.



A better option would be to do the above, but then add that newly created tree to the CollisionTreeManager in such a way that it can recall it from the cache later.

The CollisionTreeManager uses as its cache LinkedHashMap<TriangleBatch,CollisionTree>(). This cache already has an entry for the triangle batch of this target mesh (and, to clarify, when passed a SharedTriangleBatch, the CollisionTreeManager detects that and gets the Tree mapped not to the shared batch, but to the target's TriangleBatch).



So: My proposal is, TriMesh.findTriangleCollision() is modified to detect the collision of two SharedMeshes that have the same target mesh, and in that situation lazily instantiate a new CollisionTree; then it should create a fake TriangleBatch instance that it can use to put the new CollisionTree into the CollisionTreeManager's cache, and retrieve it later. This way, SharedMeshes get the same performance benefits as non-shared meshes.






Following the above line of reasoning, heres my patch. To do it this way, four classes have to be modified.

The plan is this: In TriMesh.findTriangleCollisions(), detect when two shared meshes with the same target are colliding. If that ever happens, instantiate a new private field: Map<TriangleBatch,TriangleBatch> triangleMap.

Manually construct a new collision tree, and add it to the CollisionTreeManager, but keyed not to the original triangle batch that it was produced from, but from a new, empty TriangleBatch that will serve as a key. Finally, put that new key batch into the triangleMap created earlier, so that it can be recovered if needed again.



Heres the patch for TriMesh that accomplishes that:


Index: TriMesh.java
===================================================================
RCS file: /cvs/jme/src/com/jme/scene/TriMesh.java,v
retrieving revision 1.69
diff -u -r1.69 TriMesh.java
--- TriMesh.java   2 Aug 2007 21:54:36 -0000   1.69
+++ TriMesh.java   6 Jun 2008 05:19:24 -0000
@@ -36,6 +36,8 @@
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Logger;
 
 import com.jme.bounding.CollisionTree;
@@ -382,6 +384,8 @@
         return thisCT.intersect(checkCT);       
     }
 
+    private transient Map<TriangleBatch,TriangleBatch> triangleMap = null;
+   
     /**
      * This function finds all intersections between this trimesh and the
      * checking one. The intersections are stored as Integer objects of Triangle
@@ -397,9 +401,44 @@
     public void findTriangleCollision(TriMesh toCheck, int batchIndex1, int batchIndex2,
             ArrayList<Integer> thisIndex, ArrayList<Integer> otherIndex) {
        
-       CollisionTree myTree = CollisionTreeManager.getInstance().getCollisionTree(getBatch(batchIndex1));
-       CollisionTree otherTree = CollisionTreeManager.getInstance().getCollisionTree(toCheck.getBatch(batchIndex2));
+        CollisionTree otherTree = CollisionTreeManager.getInstance().getCollisionTree(toCheck.getBatch(batchIndex2));
 
+       
+       CollisionTree myTree = null;
+       if (toCheck instanceof SharedMesh && (((SharedMesh)toCheck).getTarget() == this))
+       {
+          if (triangleMap == null)
+             triangleMap = new HashMap<TriangleBatch, TriangleBatch>();
+          TriangleBatch original = getBatch(batchIndex1);
+          //this is an intersection of the target with a mesh that shares it.
+          //First check if we already have a secondary collision tree for this
+          TriangleBatch keyBatch = triangleMap.get(original);
+          if (keyBatch != null)
+          {
+             boolean generatesTrees = CollisionTreeManager.getInstance().isGenerateTrees();
+             CollisionTreeManager.getInstance().setGenerateTrees(false);//disable tree generating for this call (because it won't work properly, as this is a fake triangle batch)   
+             myTree = CollisionTreeManager.getInstance().getCollisionTree(keyBatch);
+             CollisionTreeManager.getInstance().setGenerateTrees(generatesTrees);//return tree generation to its old value
+          }
+          
+          if (myTree == null)
+          {
+             myTree = new CollisionTree(CollisionTreeManager.getInstance().getTreeType());
+             myTree.construct(getBatch(batchIndex1), this, CollisionTreeManager.getInstance().isDoSort());
+             keyBatch = new TriangleBatch();//create a fake triangle batch to use as a key
+             CollisionTreeManager.getInstance().putTree(keyBatch, myTree);//cache the tree so we can find it later
+             triangleMap.put(original, keyBatch);
+          }else if (otherTree.isChanged())//if the other tree has been updated since the last collision, then we may need to update this tree also.
+          {
+             otherTree.setChanged(false);
+             myTree.construct(original, (TriMesh) original.getParentGeom(), CollisionTreeManager.getInstance().isDoSort());
+             
+          }
+       }else
+          myTree= CollisionTreeManager.getInstance().getCollisionTree(getBatch(batchIndex1));
+       
+   
+   
         if (myTree == null || otherTree == null) {
            return;
         }



To facilitate this, CollisionTreeManager needs a new method that allows for trees to be put directly into its cache, so that we can map the tree to an arbitrary triangle batch.
Secondly, CollisionTrees need to keep track of whether they have been modified. This way we know to update the collision tree we created here anytime the original collision tree was updated (because CollisionTreeManager will not know to update our secondary tree when the original gets updated).

Index: CollisionTreeManager.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/CollisionTreeManager.java,v
retrieving revision 1.4
diff -u -r1.4 CollisionTreeManager.java
--- CollisionTreeManager.java   22 Sep 2007 19:56:57 -0000   1.4
+++ CollisionTreeManager.java   6 Jun 2008 05:13:49 -0000
@@ -336,7 +336,7 @@
          for (int i = n.getQuantity() - 1; i >= 0; i--) {
             updateCollisionTree(n.getChild(i));
          }
-      } else if (object instanceof TriMesh) {
+      }else if (object instanceof TriMesh) {
          TriMesh t = (TriMesh) object;
          for (int i = 0; i < t.getBatchCount(); i++) {
             updateCollisionTree(t.getBatch(i));
@@ -415,4 +415,9 @@
       this.maxTrisPerLeaf = maxTrisPerLeaf;
    }
 
+   public void putTree(TriangleBatch key, CollisionTree tree)
+   {
+      cache.put(key, tree);
+   }
+   
 }
Index: CollisionTree.java
===================================================================
RCS file: /cvs/jme/src/com/jme/bounding/CollisionTree.java,v
retrieving revision 1.4
diff -u -r1.4 CollisionTree.java
--- CollisionTree.java   23 Dec 2007 03:57:49 -0000   1.4
+++ CollisionTree.java   6 Jun 2008 05:13:49 -0000
@@ -141,7 +141,7 @@
    public CollisionTree(int type) {
       this.type = type;
    }
-
+   boolean changed = false;
    /**
     * Recreate this Collision Tree for the given TriMesh and batch
     * index.
@@ -152,7 +152,6 @@
     * @param doSort true to sort triangles during creation, false otherwise
     */
    public void construct(int batchIndex, Geometry parent, boolean doSort) {
-      
       GeomBatch gb = parent.getBatch(batchIndex);
       if(gb instanceof TriangleBatch) {
          batch = (TriangleBatch)gb;
@@ -190,6 +189,7 @@
     *          false otherwise.
     */
    public void createTree(int start, int end, boolean doSort) {
+      changed = true;
       this.start = start;
       this.end = end;
       
@@ -581,4 +581,13 @@
       comparator.setBatch(batch);
       SortUtil.qsort(triIndex, start, end - 1, comparator);
    }
+
+   public boolean isChanged() {
+      return changed;
+   }
+   
+   public void setChanged(boolean changed)
+   {
+      this.changed = changed;
+   }
 }



(edit: I had listed a minor patch to SharedMesh.findTriangleCollision(), but it turned out not to be neccesary so I've removed it).

Unfortunately, I'm stuck with JME 1.0 until jme-Physics supports it.

As for the patch above, if anyone is planning to use it, please note that I've modified the patches since their original post.

As well, the patch is for Triangle Collision detection only; there may also be a problem regarding findPick() for shared meshes, but this does not address it.

Has this patch been taken into consideration  :?

I think there's a better way to do this than to clone collision trees… Also, this is for jME1.0 which is no longer supported, make a patch for jME2.

Momoko_Fan said:

Also, this is for jME1.0 which is no longer supported, make a patch for jME2.

Wait, what? jME 1 is no longer supported? I thought jME 2 wasn't even stable yet?

We'll still make most bug fixes to it, but no new feature development or enhancements.  Although to be honest, and imho… since most of us are using 2.0 now, it's more likely to be or become more stable (memory wise, driver support wise, etc.) than 1.0.

I have faced a similar problem with jME2 : 2 SharedMesh on one Sphere ( sm and sm2 taken from TestObjectWalking.java), if both mesh are added to the rootnode, node.findCollisions(rootNode, triangleCollisionsResult) fail to find collision with the first one. If I do not add the second SharedMesh, findCollisions works fine with the first one. (not sure my explanation is really clear).



As I'm working on something else, I switched back to 'real' Sphere instead of SharedMesh to solve this.

hevee said:

Momoko_Fan said:

Also, this is for jME1.0 which is no longer supported, make a patch for jME2.

Wait, what? jME 1 is no longer supported? I thought jME 2 wasn't even stable yet?
renanse said:

We'll still make most bug fixes to it, but no new feature development or enhancements.  Although to be honest, and imho... since most of us are using 2.0 now, it's more likely to be or become more stable (memory wise, driver support wise, etc.) than 1.0.


Anyone care to start up a new thread to discuss the merits of quality control on software releases.