Fix and Improve TerrainPicking (#1049)

* Fix TerrainPicking by not adding "ghost" collisions to the CollisionResults (which weren't removed).

Improve TerrainPicking by allowing to do more ray tracing (previously, it stopped on the first hit, now it will stop on the first hit within range).

* Upgraded TerrainTestCollision to support multiple collisions and print the collision results detailed. MultiCollision can easily be turned on/off in simpleInitApp().
During testing, I noticed a bug where in very rare cases the first collision isn't what is expected but the back side of the clicked mountain. It has to be validated if this is due to the following changes or was already present.

* Added Basic Unit Tests for Collision

* TerrainPicker: Change API to return int to conform with collideWith

* TerrainQuad: Conform with Picker now returning the number of collisions and allow to set multipleCollisions true or false.

* TerrainPicking: Fixed a bug where the perpendicular collision always returned true, no matter the result of checkTriangles.

Also add support for multiple collisions (which is toggleable to the old behavior, because the picker can early out then).

* Try to fix Travis Build

* Fixed a Regression which occurred due to Multi Collision Handling:
The method used to provide duplicate results, which is why I commented it out. This lead to corner-cases not colliding at all anymore, thus I added a unit-test and removed the commented code and instead made addCollision de-duplicate entries.
fix-openal-soft-deadlink
MeFisto94 5 years ago committed by Stephen Gold
parent 1ffd11fae4
commit 3ec89ce499
  1. 67
      jme3-examples/src/main/java/jme3test/terrain/TerrainTestCollision.java
  2. 4
      jme3-terrain/build.gradle
  3. 35
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java
  4. 198
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java
  5. 5
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java
  6. 38
      jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseAWTTest.java
  7. 28
      jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseTest.java
  8. 87
      jme3-terrain/src/test/java/com/jme3/terrain/collision/TerrainCollisionTest.java

@ -64,6 +64,9 @@ import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture.WrapMode;
import java.util.ArrayList;
import java.util.List;
/** /**
* Creates a terrain object and a collision node to go with it. Then * Creates a terrain object and a collision node to go with it. Then
* drops several balls from the sky that collide with the terrain * drops several balls from the sky that collide with the terrain
@ -83,7 +86,7 @@ public class TerrainTestCollision extends SimpleApplication {
protected BitmapText hintText; protected BitmapText hintText;
PointLight pl; PointLight pl;
Geometry lightMdl; Geometry lightMdl;
Geometry collisionMarker; List<Geometry> collisionMarkers;
private BulletAppState bulletAppState; private BulletAppState bulletAppState;
Geometry collisionSphere; Geometry collisionSphere;
Geometry collisionBox; Geometry collisionBox;
@ -103,6 +106,7 @@ public class TerrainTestCollision extends SimpleApplication {
@Override @Override
public void simpleInitApp() { public void simpleInitApp() {
collisionMarkers = new ArrayList<>();
bulletAppState = new BulletAppState(); bulletAppState = new BulletAppState();
bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
stateManager.attach(bulletAppState); stateManager.attach(bulletAppState);
@ -142,6 +146,8 @@ public class TerrainTestCollision extends SimpleApplication {
terrain.setLocked(false); // unlock it so we can edit the height terrain.setLocked(false); // unlock it so we can edit the height
rootNode.attachChild(terrain); rootNode.attachChild(terrain);
// if set to false, only the first collision is returned and collision is slightly faster.
terrain.setSupportMultipleCollisions(true);
/** /**
* Create PhysicsRigidBodyControl for collision * Create PhysicsRigidBodyControl for collision
@ -227,15 +233,19 @@ public class TerrainTestCollision extends SimpleApplication {
super.update(); super.update();
} }
private void createCollisionMarker() { private void createCollisionMarkers(int num) {
Sphere s = new Sphere(6, 6, 1); for (int i = 0; i < num; i++) {
collisionMarker = new Geometry("collisionMarker"); Sphere s = new Sphere(6, 6, 1);
collisionMarker.setMesh(s); Geometry collisionMarker = new Geometry("collisionMarker");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); collisionMarker.setMesh(s);
mat.setColor("Color", ColorRGBA.Orange); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
collisionMarker.setMaterial(mat); mat.setColor("Color", i == 0 ? ColorRGBA.Orange : ColorRGBA.Blue);
rootNode.attachChild(collisionMarker); collisionMarker.setMaterial(mat);
rootNode.attachChild(collisionMarker);
collisionMarkers.add(collisionMarker);
}
} }
private ActionListener actionListener = new ActionListener() { private ActionListener actionListener = new ActionListener() {
public void onAction(String binding, boolean keyPressed, float tpf) { public void onAction(String binding, boolean keyPressed, float tpf) {
@ -247,24 +257,35 @@ public class TerrainTestCollision extends SimpleApplication {
terrain.setMaterial(matRock); terrain.setMaterial(matRock);
} }
} else if (binding.equals("shoot") && !keyPressed) { } else if (binding.equals("shoot") && !keyPressed) {
Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f); Vector3f origin = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.0f);
Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f); Vector3f direction = cam.getWorldCoordinates(new Vector2f(settings.getWidth() / 2, settings.getHeight() / 2), 0.3f);
direction.subtractLocal(origin).normalizeLocal(); direction.subtractLocal(origin).normalizeLocal();
Ray ray = new Ray(origin, direction); Ray ray = new Ray(origin, direction);
CollisionResults results = new CollisionResults(); CollisionResults results = new CollisionResults();
int numCollisions = terrain.collideWith(ray, results);
if (numCollisions > 0) { if (terrain.collideWith(ray, results) > 0) {
CollisionResult hit = results.getClosestCollision(); CollisionResult hit = results.getClosestCollision(); // sorts the collection before printing
if (collisionMarker == null) { printCollisions(results);
createCollisionMarker();
// Remove old markers.
for (Geometry g: collisionMarkers) {
g.removeFromParent();
} }
collisionMarkers.clear();
createCollisionMarkers(results.size());
// Position Closest Collision
Vector2f loc = new Vector2f(hit.getContactPoint().x, hit.getContactPoint().z); Vector2f loc = new Vector2f(hit.getContactPoint().x, hit.getContactPoint().z);
float height = terrain.getHeight(loc); float height = terrain.getHeight(loc);
System.out.println("collide " + hit.getContactPoint() + ", height: " + height + ", distance: " + hit.getDistance()); System.out.println("Closest Collision: " + hit.getContactPoint() + ", height: " + height + ", distance: " + hit.getDistance());
collisionMarker.setLocalTranslation(new Vector3f(hit.getContactPoint().x, height, hit.getContactPoint().z)); collisionMarkers.get(0).setLocalTranslation(new Vector3f(hit.getContactPoint().x, height, hit.getContactPoint().z));
// Position Rest: When getClosestCollision has been called, the results are sorted, and thus 0 is closest.
for (int i = 1; i < results.size(); i++) {
collisionMarkers.get(i).setLocalTranslation(results.getCollision(i).getContactPoint());
}
} }
} else if (binding.equals("cameraDown") && !keyPressed) { } else if (binding.equals("cameraDown") && !keyPressed) {
getCamera().lookAtDirection(new Vector3f(0, -1, 0), Vector3f.UNIT_Y); getCamera().lookAtDirection(new Vector3f(0, -1, 0), Vector3f.UNIT_Y);
@ -302,4 +323,14 @@ public class TerrainTestCollision extends SimpleApplication {
selectedCollisionObject.setLocalTranslation(oldLoc); selectedCollisionObject.setLocalTranslation(oldLoc);
} }
} }
private void printCollisions(CollisionResults cr) {
System.out.println("================ Collision Results ================");
for (int i = 0; i < cr.size(); i++) {
CollisionResult res = cr.getCollision(i);
System.out.println("Result " + i);
System.out.println("\t\t" + res.toString());
}
System.out.println("================ END Collision Results ================");
}
} }

@ -4,4 +4,8 @@ if (!hasProperty('mainClass')) {
dependencies { dependencies {
compile project(':jme3-core') compile project(':jme3-core')
testCompile project(':jme3-core')
testCompile project(':jme3-desktop')
testCompile project(':jme3-core').sourceSets.test.output
testCompile project(':jme3-testdata')
} }

@ -123,7 +123,7 @@ public class TerrainQuad extends Node implements Terrain {
private int maxLod = -1; private int maxLod = -1;
private BoundingBox affectedAreaBBox; // only set in the root quad private BoundingBox affectedAreaBBox; // only set in the root quad
private TerrainPicker picker; private TerrainPicker picker = new BresenhamTerrainPicker(this);
private Vector3f lastScale = Vector3f.UNIT_XYZ; private Vector3f lastScale = Vector3f.UNIT_XYZ;
protected NeighbourFinder neighbourFinder; protected NeighbourFinder neighbourFinder;
@ -274,20 +274,7 @@ public class TerrainQuad extends Node implements Terrain {
} }
private int collideWithRay(Ray ray, CollisionResults results) { private int collideWithRay(Ray ray, CollisionResults results) {
if (picker == null) return picker.getTerrainIntersection(ray, results);
picker = new BresenhamTerrainPicker(this);
Vector3f intersection = picker.getTerrainIntersection(ray, results);
if (intersection != null) {
if (ray.getLimit() < Float.POSITIVE_INFINITY) {
if (results.getClosestCollision().getDistance() <= ray.getLimit())
return 1; // in range
else
return 0; // out of range
} else
return 1;
} else
return 0;
} }
/** /**
@ -1915,5 +1902,23 @@ public class TerrainQuad extends Node implements Terrain {
return hm; return hm;
} }
/**
* When colliding with this terrain, is a report of all collisions wanted or only the closest collision?<br>
* If only the closest collision is required, the collision calculation will be faster.<br>
* Note: If no collision happens, it takes as long as a collision with multipleCollisions on would take.
*
* @param set Whether to support multiple collisions or not
*/
public void setSupportMultipleCollisions(boolean set) {
if (picker == null) {
// This is so that it doesn't fail at the IllegalStateException because null !instanceof Anything
throw new NullPointerException("TerrainPicker is null");
} else if (picker instanceof BresenhamTerrainPicker) {
((BresenhamTerrainPicker)picker).setSupportMultipleCollisions(set);
} else {
throw new IllegalStateException("The underlying picking implementation does not support multiple collisions");
}
}
} }

@ -67,31 +67,41 @@ public class BresenhamTerrainPicker implements TerrainPicker {
private final TerrainQuad root; private final TerrainQuad root;
private final BresenhamYUpGridTracer tracer = new BresenhamYUpGridTracer(); private final BresenhamYUpGridTracer tracer = new BresenhamYUpGridTracer();
private boolean multipleCollisions = true;
public BresenhamTerrainPicker(TerrainQuad root) { public BresenhamTerrainPicker(TerrainQuad root) {
this.root = root; this.root = root;
} }
public Vector3f getTerrainIntersection(Ray worldPick, CollisionResults results) { public void setSupportMultipleCollisions(boolean multipleCollisions) {
this.multipleCollisions = multipleCollisions;
}
public boolean isSupportingMultipleCollisions() {
return multipleCollisions;
}
public int getTerrainIntersection(Ray worldPick, CollisionResults results) {
int numCollisions = 0;
worldPickRay.set(worldPick); worldPickRay.set(worldPick);
List<TerrainPickData> pickData = new ArrayList<TerrainPickData>(); List<TerrainPickData> pickData = new ArrayList<>();
root.findPick(worldPick.clone(), pickData); root.findPick(worldPick.clone(), pickData);
Collections.sort(pickData); Collections.sort(pickData);
if (pickData.isEmpty()) if (pickData.isEmpty()) {
return null; return 0;
}
workRay.set(worldPick); workRay.set(worldPick);
for (TerrainPickData pd : pickData) { for (TerrainPickData pd : pickData) {
TerrainPatch patch = pd.targetPatch; TerrainPatch patch = pd.targetPatch;
tracer.getGridSpacing().set(patch.getWorldScale()); tracer.getGridSpacing().set(patch.getWorldScale());
tracer.setGridOrigin(patch.getWorldTranslation()); tracer.setGridOrigin(patch.getWorldTranslation());
workRay.getOrigin().set(worldPick.getDirection()).multLocal(pd.cr.getDistance()-.1f).addLocal(worldPick.getOrigin()); workRay.getOrigin().set(worldPick.getDirection()).multLocal(pd.cr.getDistance() - .1f).addLocal(worldPick.getOrigin());
tracer.startWalk(workRay); tracer.startWalk(workRay);
@ -99,67 +109,141 @@ public class BresenhamTerrainPicker implements TerrainPicker {
final Vector2f loc = tracer.getGridLocation(); final Vector2f loc = tracer.getGridLocation();
if (tracer.isRayPerpendicularToGrid()) { if (tracer.isRayPerpendicularToGrid()) {
Triangle hit = new Triangle();
checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit);
float distance = worldPickRay.origin.distance(intersection);
CollisionResult cr = new CollisionResult(intersection, distance);
cr.setGeometry(patch);
cr.setContactNormal(hit.getNormal());
results.addCollision(cr);
return intersection;
}
while (loc.x >= -1 && loc.x <= patch.getSize() &&
loc.y >= -1 && loc.y <= patch.getSize()) {
//System.out.print(loc.x+","+loc.y+" : ");
// check the triangles of main square for intersection.
Triangle hit = new Triangle(); Triangle hit = new Triangle();
if (checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit)) { if (checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit)) {
// we found an intersection, so return that!
float distance = worldPickRay.origin.distance(intersection); float distance = worldPickRay.origin.distance(intersection);
CollisionResult cr = new CollisionResult(intersection, distance);
cr.setGeometry(patch);
results.addCollision(cr);
cr.setContactNormal(hit.getNormal());
return intersection;
}
// because of how we get our height coords, we will //@TODO: Verify if it's even possible to have a ray hit multiple "PickData"s when being perpendicular at all.
// sometimes be off by a grid spot, so we check the next // because otherwise, we could always return 1 here.
// grid space up. if (worldPick.getLimit() < Float.POSITIVE_INFINITY) {
int dx = 0, dz = 0; if (distance <= worldPick.getLimit()) {
Direction d = tracer.getLastStepDirection(); if (addCollision(results, patch, intersection, hit, distance)) {
switch (d) { if (!multipleCollisions) {
case PositiveX: return 1;
case NegativeX: } else {
dx = 0; numCollisions++;
dz = 1; }
break; }
case PositiveZ: } // else return 0; // < this is the old behavior, since the code checked for the range afterwards.
case NegativeZ: } else { // unlimited range
dx = 1; if (addCollision(results, patch, intersection, hit, distance)) {
dz = 0; if (!multipleCollisions) {
break; return 1;
} else {
numCollisions++;
}
}
}
} // else no collision
} else { // If the ray is perpendicular, tracer.next() would never advance loc, leading to an infinite loop.
while (loc.x >= -1 && loc.x <= patch.getSize() &&
loc.y >= -1 && loc.y <= patch.getSize()) {
//System.out.print(loc.x + "," + loc.y + " : ");
// check the triangles of main square for intersection.
Triangle hit = new Triangle();
if (checkTriangles(loc.x, loc.y, workRay, intersection, patch, hit)) {
// we found an intersection, so return that!
float distance = worldPickRay.origin.distance(intersection);
if (worldPick.getLimit() < Float.POSITIVE_INFINITY) {
if (distance <= worldPick.getLimit()) {
if (addCollision(results, patch, intersection, hit, distance)) {
if (!multipleCollisions) {
return 1;
} else {
numCollisions++;
}
}
}// else return 0; // < this is the old behavior, since the code checked for the range afterwards.
} else { // unlimited range
if (addCollision(results, patch, intersection, hit, distance)) {
if (!multipleCollisions) {
return 1;
} else {
numCollisions++;
}
}
}
}
// because of how we get our height coords, we will
// sometimes be off by a grid spot, so we check the next
// grid space up.
int dx = 0, dz = 0;
Direction d = tracer.getLastStepDirection();
switch (d) {
case PositiveX:
case NegativeX:
dx = 0;
dz = 1;
break;
case PositiveZ:
case NegativeZ:
dx = 1;
dz = 0;
break;
}
if (checkTriangles(loc.x + dx, loc.y + dz, workRay, intersection, patch, hit)) {
// we found an intersection, so return that!
float distance = worldPickRay.origin.distance(intersection);
if (worldPick.getLimit() < Float.POSITIVE_INFINITY) {
if (distance <= worldPick.getLimit()) {
if (addCollision(results, patch, intersection, hit, distance)) {
if (!multipleCollisions) {
return 1;
} else {
numCollisions++;
}
}
} // else return null; // < this is the old behavior, since the code checked for the range afterwards.
} else { // unlimited range
if (addCollision(results, patch, intersection, hit, distance)) {
if (!multipleCollisions) {
return 1;
} else {
numCollisions++;
}
}
}
}
tracer.next();
} }
}
}
if (checkTriangles(loc.x + dx, loc.y + dz, workRay, intersection, patch, hit)) { return numCollisions;
// we found an intersection, so return that! }
float distance = worldPickRay.origin.distance(intersection);
CollisionResult cr = new CollisionResult(intersection, distance);
results.addCollision(cr);
cr.setGeometry(patch);
cr.setContactNormal(hit.getNormal());
return intersection;
}
tracer.next(); /**
* This method adds the found Collision to an existing collisionResult.
* @param results The results to add this collision to
* @param patch The TerrainPatch which collided
* @param intersection The actual intersection position
* @param hit The hit triangle
* @param distance The distance at which the hit occurred
* @return Whether the collision was accepted to the list or whether it has been deduplicated
*/
private boolean addCollision(CollisionResults results, TerrainPatch patch, Vector3f intersection, Triangle hit, float distance) {
CollisionResult cr = new CollisionResult(intersection.clone(), distance);
cr.setGeometry(patch);
cr.setContactNormal(hit.getNormal());
cr.setTriangleIndex(hit.getIndex()); // this will probably always be 0
for (int i = 0; i < results.size(); i++) {
CollisionResult compare = results.getCollision(i);
if (compare.getDistance() == cr.getDistance() && compare.getGeometry() == cr.getGeometry() &&
compare.getContactPoint().equals(cr.getContactPoint()) &&
compare.getContactNormal().equals(cr.getContactNormal())) {
return false; // Collision already available, deduplicate.
} }
} }
return null; results.addCollision(cr);
return true;
} }
protected boolean checkTriangles(float gridX, float gridY, Ray pick, Vector3f intersection, TerrainPatch patch, Triangle store) { protected boolean checkTriangles(float gridX, float gridY, Ray pick, Vector3f intersection, TerrainPatch patch, Triangle store) {

@ -47,9 +47,8 @@ public interface TerrainPicker {
* *
* @param worldPick * @param worldPick
* our pick ray, in world space. * our pick ray, in world space.
* @return null if no pick is found. Otherwise it returns a Vector3f populated with the pick * @return The number of collisions found
* coordinates.
*/ */
public Vector3f getTerrainIntersection(final Ray worldPick, CollisionResults results); public int getTerrainIntersection(final Ray worldPick, CollisionResults results);
} }

@ -0,0 +1,38 @@
package com.jme3.terrain.collision;
import com.jme3.asset.AssetManager;
import com.jme3.system.JmeDesktopSystem;
import com.jme3.system.JmeSystem;
/**
* This class provides some utility functions to properly test the jMonkeyEngine.<br>
* Thus it contains simple methods to get and create a headless assetManager amongst other things.<br>
* In comparison to {@link BaseTest} it provides a DesktopAssetManager capable of loading image formats using AWT, which
* however makes those tests unsuitable for headless ci testing. This requires jme3-desktop to be a testRuntime dependency.
*
* @author MeFisto94
*/
public abstract class BaseAWTTest {
private AssetManager assetManager;
static {
//JmeSystem.setSystemDelegate(new JmeDesktopSystem());
}
public AssetManager getAssetManager() {
if (assetManager == null) {
assetManager = createAssetManager();
}
return assetManager;
}
private AssetManager createAssetManager() {
/* Desktop.cfg supports the following additional file formats at the time of writing:
LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg
LOADER com.jme3.audio.plugins.OGGLoader : ogg
*/
return JmeSystem.newAssetManager(BaseTest.class.getResource("/com/jme3/asset/Desktop.cfg"));
}
}

@ -0,0 +1,28 @@
package com.jme3.terrain.collision;
import com.jme3.asset.AssetManager;
import com.jme3.system.TestUtil;
/**
* This class provides some utility functions to properly test the jMonkeyEngine.<br>
* Thus it contains simple methods to get and create a headless assetManager amongst other things.<br>
* If you need support for image/texture formats (png, tga, jpg, ...) see {@link BaseAWTTest}
*
* @author MeFisto94
*/
public abstract class BaseTest {
private AssetManager assetManager;
public AssetManager getAssetManager() {
if (assetManager == null) {
assetManager = createAssetManager();
}
return assetManager;
}
private AssetManager createAssetManager() {
return TestUtil.createAssetManager();
}
}

@ -0,0 +1,87 @@
package com.jme3.terrain.collision;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TerrainCollisionTest extends BaseAWTTest {
TerrainQuad quad;
@Before
public void initQuad() {
Texture heightMapImage = getAssetManager().loadTexture("Textures/Terrain/splat/mountains512.png");
AbstractHeightMap map = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
map.load();
quad = new TerrainQuad("terrain", 65, 513, map.getHeightMap());
}
/**
* Due to a previous bug, when no collision should happen, the CollisionResults struct was still populated, leading
* to an incoherency of data and ghost collisions when passing a non-empty CR.
*/
@Test
public void testNoCollision() {
Ray r = new Ray(new Vector3f(0f, 40f, 0f), Vector3f.UNIT_Y.negate());
r.setLimit(0.1f);
CollisionResults cr = new CollisionResults();
long l = System.nanoTime();
int cw = quad.collideWith(r, cr);
System.out.println((System.nanoTime() - l) + " ns");
Assert.assertEquals(0, cw);
Assert.assertEquals(0, cr.size());
Assert.assertEquals(null, cr.getClosestCollision());
Assert.assertEquals(null, cr.getFarthestCollision());
}
@Test
public void testPerpendicularCollision() {
Ray r = new Ray(new Vector3f(0f, 40f, 0f), Vector3f.UNIT_Y.negate());
CollisionResults cr = new CollisionResults();
int cw = quad.collideWith(r, cr);
Assert.assertEquals(1, cw);
Assert.assertEquals(1, cr.size());
Assert.assertEquals(new Vector3f(0f, 28f, 0f), cr.getClosestCollision().getContactPoint());
Assert.assertEquals(new Vector3f(-0.5144958f, 0.6859944f, 0.5144958f), cr.getClosestCollision().getContactNormal());
Assert.assertEquals(12, cr.getClosestCollision().getDistance(), 0.01d);
Assert.assertEquals(0, cr.getClosestCollision().getTriangleIndex());
}
@Test
public void testMultiCollision() {
// Ray parameters obtained by using TerrainTestCollision (manual inspection of a feasible ray and commenting out setLocalScale(2)
Ray r = new Ray(new Vector3f(-38.689114f, 35.622643f, -40.222355f), new Vector3f(0.68958646f, 0.0980845f, 0.7175304f));
CollisionResults cr = new CollisionResults();
long l = System.nanoTime();
int cw = quad.collideWith(r, cr);
System.out.println((System.nanoTime() - l) + " ns");
Assert.assertEquals(6, cw);
Assert.assertEquals(6, cr.size());
}
@Test
public void testPreventRegression() {
// This test is as the multi collision changes lead to a regression where sometimes a collision was ignored
// Ray parameters obtained by using TerrainTestCollision (manual inspection of a feasible ray and commenting out setLocalScale(2))
Ray r = new Ray(new Vector3f(101.61858f, 78.35965f, 17.645157f), new Vector3f(-0.4188528f, -0.56462675f, 0.71116734f));
CollisionResults cr = new CollisionResults();
quad.collideWith(r, cr);
Assert.assertEquals(3, cr.size());
Assert.assertEquals(68.1499f, cr.getClosestCollision().getDistance(), 0.01f);
Assert.assertEquals(new Vector3f(73.07381f, 39.88039f, 66.11114f), cr.getClosestCollision().getContactPoint());
Assert.assertEquals(new Vector3f(0.9103665f, 0.33104235f, -0.24828176f), cr.getClosestCollision().getContactNormal());
}
}
Loading…
Cancel
Save