From 4b8b7237951680c679def3037a6aec2413c1ee85 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Wed, 24 Feb 2021 21:14:37 +0100 Subject: [PATCH] [DEV] end port of c++ ephysics engine --- .classpath | 29 + .gitignore | 3 + .project | 18 + src/module-info.java | 21 + src/org/atriasoft/ephysics/Configuration.java | 54 + .../atriasoft/ephysics/RaycastCallback.java | 20 + src/org/atriasoft/ephysics/RaycastInfo.java | 16 + src/org/atriasoft/ephysics/RaycastTest.java | 28 + src/org/atriasoft/ephysics/body/Body.java | 154 +++ src/org/atriasoft/ephysics/body/BodyType.java | 7 + .../ephysics/body/CollisionBody.java | 404 ++++++ .../atriasoft/ephysics/body/RigidBody.java | 574 +++++++++ .../collision/CollisionDetection.java | 497 ++++++++ .../collision/CollisionShapeInfo.java | 30 + .../ephysics/collision/ContactManifold.java | 374 ++++++ .../collision/ContactManifoldListElement.java | 14 + .../collision/ContactManifoldSet.java | 217 ++++ .../ephysics/collision/ProxyShape.java | 201 +++ .../TestCollisionBetweenShapesCallback.java | 22 + .../ephysics/collision/Triangle.java | 12 + .../ephysics/collision/TriangleMesh.java | 36 + .../collision/TriangleVertexArray.java | 84 ++ .../broadphase/BroadPhaseAlgorithm.java | 256 ++++ .../broadphase/CallbackOverlapping.java | 5 + .../collision/broadphase/CallbackRaycast.java | 7 + .../ephysics/collision/broadphase/DTree.java | 28 + .../collision/broadphase/DTreeLeafData.java | 15 + .../collision/broadphase/DTreeLeafInt.java | 17 + .../collision/broadphase/DTreeNode.java | 6 + .../collision/broadphase/DynamicAABBTree.java | 576 +++++++++ .../collision/broadphase/PairDTree.java | 73 ++ .../narrowphase/CollisionDispatch.java | 21 + .../narrowphase/ConcaveVsConvexAlgorithm.java | 355 ++++++ .../narrowphase/DefaultCollisionDispatch.java | 46 + .../narrowphase/EPA/EPAAlgorithm.java | 423 +++++++ .../collision/narrowphase/EPA/EdgeEPA.java | 138 ++ .../narrowphase/EPA/TriangleEPA.java | 223 ++++ .../narrowphase/EPA/TrianglesStore.java | 70 ++ .../narrowphase/GJK/GJKAlgorithm.java | 435 +++++++ .../collision/narrowphase/GJK/Simplex.java | 444 +++++++ .../narrowphase/NarrowPhaseAlgorithm.java | 32 + .../narrowphase/NarrowPhaseCallback.java | 14 + .../narrowphase/SphereVsSphereAlgorithm.java | 50 + .../ephysics/collision/shapes/AABB.java | 322 +++++ .../ephysics/collision/shapes/BoxShape.java | 170 +++ .../ephysics/collision/shapes/CacheData.java | 10 + .../collision/shapes/CapsuleShape.java | 284 +++++ .../collision/shapes/CollisionShape.java | 126 ++ .../collision/shapes/CollisionShapeType.java | 58 + .../collision/shapes/ConcaveMeshShape.java | 210 ++++ .../collision/shapes/ConcaveShape.java | 88 ++ .../ephysics/collision/shapes/ConeShape.java | 255 ++++ .../collision/shapes/ConvexMeshShape.java | 343 +++++ .../collision/shapes/ConvexShape.java | 56 + .../collision/shapes/CylinderShape.java | 297 +++++ .../collision/shapes/HeightFieldShape.java | 305 +++++ .../collision/shapes/SphereShape.java | 142 +++ .../collision/shapes/TriangleShape.java | 195 +++ .../ephysics/configuration/Defaults.java | 86 ++ .../constraint/BallAndSocketJoint.java | 215 ++++ .../constraint/BallAndSocketJointInfo.java | 26 + .../ephysics/constraint/ContactPoint.java | 174 +++ .../ephysics/constraint/ContactPointInfo.java | 54 + .../ContactsPositionCorrectionTechnique.java | 12 + .../ephysics/constraint/FixedJoint.java | 313 +++++ .../ephysics/constraint/FixedJointInfo.java | 25 + .../ephysics/constraint/HingeJoint.java | 862 +++++++++++++ .../ephysics/constraint/HingeJointInfo.java | 89 ++ .../atriasoft/ephysics/constraint/Joint.java | 80 ++ .../ephysics/constraint/JointInfo.java | 37 + .../ephysics/constraint/JointListElement.java | 16 + .../ephysics/constraint/JointType.java | 9 + .../JointsPositionCorrectionTechnique.java | 9 + .../ephysics/constraint/SliderJoint.java | 853 +++++++++++++ .../ephysics/constraint/SliderJointInfo.java | 89 ++ .../ephysics/engine/CollisionCallback.java | 17 + .../ephysics/engine/CollisionWorld.java | 254 ++++ .../ephysics/engine/ConstraintSolver.java | 163 +++ .../ephysics/engine/ConstraintSolverData.java | 28 + .../engine/ContactManifoldSolver.java | 53 + .../ephysics/engine/ContactPointSolver.java | 37 + .../ephysics/engine/ContactSolver.java | 1109 +++++++++++++++++ .../ephysics/engine/DynamicsWorld.java | 1042 ++++++++++++++++ .../ephysics/engine/EventListener.java | 40 + .../atriasoft/ephysics/engine/Impulse.java | 28 + src/org/atriasoft/ephysics/engine/Island.java | 111 ++ .../atriasoft/ephysics/engine/Material.java | 91 ++ .../ephysics/engine/OverlappingPair.java | 88 ++ src/org/atriasoft/ephysics/internal/Log.java | 68 + .../ephysics/mathematics/Mathematics.java | 96 ++ .../ephysics/mathematics/Matrix2f.java | 249 ++++ .../ephysics/mathematics/PairInt.java | 67 + .../ephysics/mathematics/PairIntVector3f.java | 14 + .../atriasoft/ephysics/mathematics/Ray.java | 52 + .../atriasoft/ephysics/mathematics/Set.java | 325 +++++ .../ephysics/mathematics/SetInteger.java | 212 ++++ .../ephysics/mathematics/SetMultiple.java | 41 + 97 files changed, 16044 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 src/module-info.java create mode 100644 src/org/atriasoft/ephysics/Configuration.java create mode 100644 src/org/atriasoft/ephysics/RaycastCallback.java create mode 100644 src/org/atriasoft/ephysics/RaycastInfo.java create mode 100644 src/org/atriasoft/ephysics/RaycastTest.java create mode 100644 src/org/atriasoft/ephysics/body/Body.java create mode 100644 src/org/atriasoft/ephysics/body/BodyType.java create mode 100644 src/org/atriasoft/ephysics/body/CollisionBody.java create mode 100644 src/org/atriasoft/ephysics/body/RigidBody.java create mode 100644 src/org/atriasoft/ephysics/collision/CollisionDetection.java create mode 100644 src/org/atriasoft/ephysics/collision/CollisionShapeInfo.java create mode 100644 src/org/atriasoft/ephysics/collision/ContactManifold.java create mode 100644 src/org/atriasoft/ephysics/collision/ContactManifoldListElement.java create mode 100644 src/org/atriasoft/ephysics/collision/ContactManifoldSet.java create mode 100644 src/org/atriasoft/ephysics/collision/ProxyShape.java create mode 100644 src/org/atriasoft/ephysics/collision/TestCollisionBetweenShapesCallback.java create mode 100644 src/org/atriasoft/ephysics/collision/Triangle.java create mode 100644 src/org/atriasoft/ephysics/collision/TriangleMesh.java create mode 100644 src/org/atriasoft/ephysics/collision/TriangleVertexArray.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/CallbackOverlapping.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/CallbackRaycast.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/DTree.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafData.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafInt.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/DTreeNode.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/DynamicAABBTree.java create mode 100644 src/org/atriasoft/ephysics/collision/broadphase/PairDTree.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/CollisionDispatch.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/EPA/EdgeEPA.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/EPA/TriangleEPA.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/EPA/TrianglesStore.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/GJK/Simplex.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseCallback.java create mode 100644 src/org/atriasoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/AABB.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/BoxShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/CacheData.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/CapsuleShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/CollisionShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/CollisionShapeType.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/ConcaveMeshShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/ConcaveShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/ConeShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/ConvexMeshShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/ConvexShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/CylinderShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/HeightFieldShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/SphereShape.java create mode 100644 src/org/atriasoft/ephysics/collision/shapes/TriangleShape.java create mode 100644 src/org/atriasoft/ephysics/configuration/Defaults.java create mode 100644 src/org/atriasoft/ephysics/constraint/BallAndSocketJoint.java create mode 100644 src/org/atriasoft/ephysics/constraint/BallAndSocketJointInfo.java create mode 100644 src/org/atriasoft/ephysics/constraint/ContactPoint.java create mode 100644 src/org/atriasoft/ephysics/constraint/ContactPointInfo.java create mode 100644 src/org/atriasoft/ephysics/constraint/ContactsPositionCorrectionTechnique.java create mode 100644 src/org/atriasoft/ephysics/constraint/FixedJoint.java create mode 100644 src/org/atriasoft/ephysics/constraint/FixedJointInfo.java create mode 100644 src/org/atriasoft/ephysics/constraint/HingeJoint.java create mode 100644 src/org/atriasoft/ephysics/constraint/HingeJointInfo.java create mode 100644 src/org/atriasoft/ephysics/constraint/Joint.java create mode 100644 src/org/atriasoft/ephysics/constraint/JointInfo.java create mode 100644 src/org/atriasoft/ephysics/constraint/JointListElement.java create mode 100644 src/org/atriasoft/ephysics/constraint/JointType.java create mode 100644 src/org/atriasoft/ephysics/constraint/JointsPositionCorrectionTechnique.java create mode 100644 src/org/atriasoft/ephysics/constraint/SliderJoint.java create mode 100644 src/org/atriasoft/ephysics/constraint/SliderJointInfo.java create mode 100644 src/org/atriasoft/ephysics/engine/CollisionCallback.java create mode 100644 src/org/atriasoft/ephysics/engine/CollisionWorld.java create mode 100644 src/org/atriasoft/ephysics/engine/ConstraintSolver.java create mode 100644 src/org/atriasoft/ephysics/engine/ConstraintSolverData.java create mode 100644 src/org/atriasoft/ephysics/engine/ContactManifoldSolver.java create mode 100644 src/org/atriasoft/ephysics/engine/ContactPointSolver.java create mode 100644 src/org/atriasoft/ephysics/engine/ContactSolver.java create mode 100644 src/org/atriasoft/ephysics/engine/DynamicsWorld.java create mode 100644 src/org/atriasoft/ephysics/engine/EventListener.java create mode 100644 src/org/atriasoft/ephysics/engine/Impulse.java create mode 100644 src/org/atriasoft/ephysics/engine/Island.java create mode 100644 src/org/atriasoft/ephysics/engine/Material.java create mode 100644 src/org/atriasoft/ephysics/engine/OverlappingPair.java create mode 100644 src/org/atriasoft/ephysics/internal/Log.java create mode 100644 src/org/atriasoft/ephysics/mathematics/Mathematics.java create mode 100644 src/org/atriasoft/ephysics/mathematics/Matrix2f.java create mode 100644 src/org/atriasoft/ephysics/mathematics/PairInt.java create mode 100644 src/org/atriasoft/ephysics/mathematics/PairIntVector3f.java create mode 100644 src/org/atriasoft/ephysics/mathematics/Ray.java create mode 100644 src/org/atriasoft/ephysics/mathematics/Set.java create mode 100644 src/org/atriasoft/ephysics/mathematics/SetInteger.java create mode 100644 src/org/atriasoft/ephysics/mathematics/SetMultiple.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7bbb68c --- /dev/null +++ b/.classpath @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8bdd50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/examples/target/ +/examples/framework.log.* +/target/ \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..a324e0f --- /dev/null +++ b/.project @@ -0,0 +1,18 @@ + + + atriasoft-ephysics + + + atriasoft-ephysics + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/module-info.java b/src/module-info.java new file mode 100644 index 0000000..537600f --- /dev/null +++ b/src/module-info.java @@ -0,0 +1,21 @@ +/** Basic module interface. + * + * @author Edouard DUPIN */ + +open module org.atriasoft.ephysics { + exports org.atriasoft.ephysics.body; + exports org.atriasoft.ephysics.collision; + exports org.atriasoft.ephysics.collision.broadphase; + exports org.atriasoft.ephysics.collision.narrowphase; + exports org.atriasoft.ephysics.collision.narrowphase.EPA; + exports org.atriasoft.ephysics.collision.narrowphase.GJK; + exports org.atriasoft.ephysics.collision.shapes; + exports org.atriasoft.ephysics.configuration; + exports org.atriasoft.ephysics.constraint; + exports org.atriasoft.ephysics.engine; + exports org.atriasoft.ephysics.mathematics; + exports org.atriasoft.ephysics; + + requires transitive org.atriasoft.etk; + requires javafx.base; +} diff --git a/src/org/atriasoft/ephysics/Configuration.java b/src/org/atriasoft/ephysics/Configuration.java new file mode 100644 index 0000000..f159a05 --- /dev/null +++ b/src/org/atriasoft/ephysics/Configuration.java @@ -0,0 +1,54 @@ +package org.atriasoft.ephysics; + +import org.atriasoft.etk.math.Constant; + +public class Configuration { + + /// In the broad-phase collision detection (dynamic AABB tree), the AABBs are + /// inflated with a ant gap to allow the collision shape to move a little bit + /// without triggering a large modification of the tree which can be costly + public static final float DYNAMIC_TREE_AABB_GAP = 0.1f; + + /// In the broad-phase collision detection (dynamic AABB tree), the AABBs are + /// also inflated in direction of the linear motion of the body by mutliplying the + /// followin ant with the linear velocity and the elapsed time between two frames. + public static final float DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER = 1.7f; + + /// Distance threshold for two contact points for a valid persistent contact (in meters) + public static final float PERSISTENT_CONTACT_DIST_THRESHOLD = 0.03f; + + /// Default friction coefficient for a rigid body + public static final float DEFAULT_FRICTION_COEFFICIENT = 0.3f; + + /// Default bounciness factor for a rigid body + public static final float DEFAULT_BOUNCINESS = 0.5f; + + /// Default rolling resistance + public static final float DEFAULT_ROLLING_RESISTANCE = 0.0f; + + /// True if the spleeping technique is enabled + public static final boolean SPLEEPING_ENABLED = true; + + /// Object margin for collision detection in meters (for the GJK-EPA Algorithm) + public static final float OBJECT_MARGIN = 0.04f; + + /// Velocity threshold for contact velocity restitution + public static final float RESTITUTION_VELOCITY_THRESHOLD = 1.0f; + + /// Number of iterations when solving the velocity raints of the Sequential Impulse technique + public static final int DEFAULT_VELOCITY_SOLVER_NB_ITERATIONS = 10; + + /// Number of iterations when solving the position raints of the Sequential Impulse technique + public static final int DEFAULT_POSITION_SOLVER_NB_ITERATIONS = 5; + + /// Time (in seconds) that a body must stay still to be idered sleeping + public static final float DEFAULT_TIME_BEFORE_SLEEP = 0.3f; //(old default : 3.0) + + /// A body with a linear velocity smaller than the sleep linear velocity (in m/s) + /// might enter sleeping mode. + public static final float DEFAULT_SLEEP_LINEAR_VELOCITY = 0.3f; //(old default : 0.01) + + /// A body with angular velocity smaller than the sleep angular velocity (in rad/s) + /// might enter sleeping mode + public static final float DEFAULT_SLEEP_ANGULAR_VELOCITY = 5.0f * (Constant.PI / 180.0f); //(old default : 0.3) +} diff --git a/src/org/atriasoft/ephysics/RaycastCallback.java b/src/org/atriasoft/ephysics/RaycastCallback.java new file mode 100644 index 0000000..e746983 --- /dev/null +++ b/src/org/atriasoft/ephysics/RaycastCallback.java @@ -0,0 +1,20 @@ +package org.atriasoft.ephysics; + +public interface RaycastCallback { + /** + * @brief This method will be called for each ProxyShape that is hit by the + * ray. You cannot make any assumptions about the order of the + * calls. You should use the return value to control the continuation + * of the ray. The returned value is the next maxFraction value to use. + * If you return a fraction of 0.0, it means that the raycast should + * terminate. If you return a fraction of 1.0, it indicates that the + * ray is not clipped and the ray cast should continue as if no hit + * occurred. If you return the fraction in the parameter (hitFraction + * value in the RaycastInfo object), the current ray will be clipped + * to this fraction in the next queries. If you return -1.0, it will + * ignore this ProxyShape and continue the ray cast. + * @param[in] _raycastInfo Information about the raycast hit + * @return Value that controls the continuation of the ray after a hit + */ + float notifyRaycastHit(RaycastInfo _raycastInfo); +} diff --git a/src/org/atriasoft/ephysics/RaycastInfo.java b/src/org/atriasoft/ephysics/RaycastInfo.java new file mode 100644 index 0000000..d54ea2f --- /dev/null +++ b/src/org/atriasoft/ephysics/RaycastInfo.java @@ -0,0 +1,16 @@ +package org.atriasoft.ephysics; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.collision.ProxyShape; + +public class RaycastInfo { + public Vector3f worldPoint = new Vector3f(); //!< Hit point in world-space coordinates + public Vector3f worldNormal = new Vector3f(); //!< Surface normal at hit point in world-space coordinates + public float hitFraction = 0.0f; //!< Fraction distance of the hit point between point1 and point2 of the ray. The hit point "p" is such that p = point1 + hitFraction * (point2 - point1) + public long meshSubpart = -1; //!< Mesh subpart index that has been hit (only used for triangles mesh and -1 otherwise) + public long triangleIndex = -1; //!< Hit triangle index (only used for triangles mesh and -1 otherwise) + public CollisionBody body; //!< Pointer to the hit collision body; + public ProxyShape proxyShape; //!< Pointer to the hit proxy collision shape +} diff --git a/src/org/atriasoft/ephysics/RaycastTest.java b/src/org/atriasoft/ephysics/RaycastTest.java new file mode 100644 index 0000000..bee1b57 --- /dev/null +++ b/src/org/atriasoft/ephysics/RaycastTest.java @@ -0,0 +1,28 @@ +package org.atriasoft.ephysics; + +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.mathematics.Ray; + +public class RaycastTest { + + public RaycastCallback userCallback; //!< User callback class + /// Constructor + + public RaycastTest(final RaycastCallback _callback) { + this.userCallback = _callback; + } + + /// Ray cast test against a proxy shape + public float raycastAgainstShape(final ProxyShape _shape, final Ray _ray) { + // Ray casting test against the collision shape + final RaycastInfo raycastInfo = new RaycastInfo(); + final boolean isHit = _shape.raycast(_ray, raycastInfo); + // If the ray hit the collision shape + if (isHit) { + // Report the hit to the user and return the + // user hit fraction value + return this.userCallback.notifyRaycastHit(raycastInfo); + } + return _ray.maxFraction; + } +} diff --git a/src/org/atriasoft/ephysics/body/Body.java b/src/org/atriasoft/ephysics/body/Body.java new file mode 100644 index 0000000..3f49ad7 --- /dev/null +++ b/src/org/atriasoft/ephysics/body/Body.java @@ -0,0 +1,154 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.body; + +/** + * This class is an abstract class to represent a body of the physics engine. + * + * @author Jason Sorensen + */ +public abstract class Body { + // Unique ID of the body + protected int id; // TODO check where it came from ... + // True if the body has already been added in an island (for sleeping technique) + public boolean isAlreadyInIsland = false; + // True if the body is allowed to go to sleep for better efficiency + protected boolean isAllowedToSleep = true; + /** + * True if the body is active. + * + * An inactive body does not participate in collision detection, is not simulated and will not be hit in a ray casting query. + * A body is active by default. If you set this value to "false", all the proxy shapes of this body will be removed from the broad-phase. + * If you set this value to "true", all the proxy shapes will be added to the broad-phase. + * A joint connected to an inactive body will also be inactive. + */ + protected boolean isActive = true; + // True if the body is sleeping (for sleeping technique) + protected boolean isSleeping = false; + // Elapsed time since the body velocity was bellow the sleep velocity + public float sleepTime = 0; + // user data + protected Object userData = null; + + /** + * @brief Constructor + * @param[in] _id ID of the new body + */ + public Body(final int bodyID) { + this.id = bodyID; + } + + // Return the id of the body + public int getID() { + return this.id; + } + + public float getSleepTime() { + return this.sleepTime; + } + + /** + * @brief Return a pointer to the user data attached to this body + * @return A pointer to the user data you have attached to the body + */ + Object getUserData() { + return this.userData; + } + + /** + * @brief Return the id of the body + * @return The ID of the body + */ + public boolean isActive() { + return this.isActive; + } + + /** + * @brief Set whether or not the body is allowed to go to sleep + * @param[in] _isAllowedToSleep True if the body is allowed to sleep + */ + public boolean isAllowedToSleep() { + return this.isAllowedToSleep; + } + + public boolean isAlreadyInIsland() { + return this.isAlreadyInIsland; + } + + /** + * @brief Return whether or not the body is sleeping + * @return True if the body is currently sleeping and false otherwise + */ + public boolean isSleeping() { + return this.isSleeping; + } + + /** + * @brief Set whether or not the body is active + * @param[in] _isActive True if you want to activate the body + */ + void setIsActive(final boolean isActive) { + this.isActive = isActive; + } + + // Set whether or not the body is allowed to go to sleep + public void setIsAllowedToSleep(final boolean isAllowedToSleep) { + this.isAllowedToSleep = isAllowedToSleep; + + if (!this.isAllowedToSleep) { + setIsSleeping(false); + } + } + + public void setIsAlreadyInIsland(final boolean isAlreadyInIsland) { + this.isAlreadyInIsland = isAlreadyInIsland; + } + + /** + * @brief Set the variable to know whether or not the body is sleeping + * @param[in] _isSleeping Set the new status + */ + public void setIsSleeping(final boolean isSleeping) { + if (isSleeping) { + this.sleepTime = 0.0f; + } else if (this.isSleeping) { + this.sleepTime = 0.0f; + } + this.isSleeping = isSleeping; + } + + public void setSleepTime(final float sleepTime) { + this.sleepTime = sleepTime; + } + + /** + * @brief Attach user data to this body + * @param[in] _userData A pointer to the user data you want to attach to the body + */ + public void setUserData(final Object userData) { + this.userData = userData; + } + +} diff --git a/src/org/atriasoft/ephysics/body/BodyType.java b/src/org/atriasoft/ephysics/body/BodyType.java new file mode 100644 index 0000000..c142fd8 --- /dev/null +++ b/src/org/atriasoft/ephysics/body/BodyType.java @@ -0,0 +1,7 @@ +package org.atriasoft.ephysics.body; + +public enum BodyType { + STATIC, //!< A static body has infinite mass, zero velocity but the position can be changed manually. A static body does not collide with other static or kinematic bodies. + KINEMATIC, //!< A kinematic body has infinite mass, the velocity can be changed manually and its position is computed by the physics engine. A kinematic body does not collide with other static or kinematic bodies. + DYNAMIC //!< A dynamic body has non-zero mass, non-zero velocity determined by forces and its position is determined by the physics engine. A dynamic body can collide with other dynamic, static or kinematic bodies. +} diff --git a/src/org/atriasoft/ephysics/body/CollisionBody.java b/src/org/atriasoft/ephysics/body/CollisionBody.java new file mode 100644 index 0000000..81a694e --- /dev/null +++ b/src/org/atriasoft/ephysics/body/CollisionBody.java @@ -0,0 +1,404 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.body; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ContactManifoldListElement; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.engine.CollisionWorld; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a body that is able to collide with others bodies. This class inherits from the Body class. + * + * @author Jason Sorensen + */ +public class CollisionBody extends Body { + // Type of body (static, kinematic or dynamic) + protected BodyType type; + // Position and orientation of the body + protected Transform3D transform; + // First element of the linked list of proxy collision shapes of this body + protected ProxyShape proxyCollisionShapes; + // Number of collision shapes + protected long numberCollisionShapes; + // First element of the linked list of contact manifolds involving this body + public ContactManifoldListElement contactManifoldsList; + // Reference to the world the body belongs to + protected CollisionWorld world; + + /** + * @brief Constructor + * @param[in] _transform The transform of the body + * @param[in] _world The physics world where the body is created + * @param[in] _id ID of the body + */ + public CollisionBody(final Transform3D _transform, final CollisionWorld _world, final int _id) { + super(_id); + this.type = BodyType.DYNAMIC; + this.transform = _transform; + this.proxyCollisionShapes = null; + this.numberCollisionShapes = 0; + this.contactManifoldsList = null; + this.world = _world; + + //Log.debug(" set transform: " + _transform); + if (Float.isNaN(_transform.getPosition().x)) { + Log.critical(" set transform: " + _transform); + } + if (Float.isInfinite(_transform.getOrientation().z)) { + Log.critical(" set transform: " + _transform); + } + } + + /** + * @brief Add a collision shape to the body. Note that you can share a collision shape between several bodies using the same collision shape instance to + * when you add the shape to the different bodies. Do not forget to delete the collision shape you have created at the end of your program. + * + * This method will return a pointer to a new proxy shape. A proxy shape is an object that links a collision shape and a given body. You can use the + * returned proxy shape to get and set information about the corresponding collision shape for that body. + * @param[in] collisionShape A pointer to the collision shape you want to add to the body + * @param[in] transform The transformation of the collision shape that transforms the local-space of the collision shape int32_to the local-space of the body + * @return A pointer to the proxy shape that has been created to link the body to the new collision shape you have added. + */ + public ProxyShape addCollisionShape(final CollisionShape _collisionShape, final Transform3D _transform) { + // Create a proxy collision shape to attach the collision shape to the body + final ProxyShape proxyShape = new ProxyShape(this, _collisionShape, _transform, 1.0f); + // Add it to the list of proxy collision shapes of the body + if (this.proxyCollisionShapes == null) { + this.proxyCollisionShapes = proxyShape; + } else { + proxyShape.next = this.proxyCollisionShapes; + this.proxyCollisionShapes = proxyShape; + } + final AABB aabb = new AABB(); + _collisionShape.computeAABB(aabb, this.transform.multiplyNew(_transform)); + this.world.collisionDetection.addProxyCollisionShape(proxyShape, aabb); + this.numberCollisionShapes++; + return proxyShape; + } + + /** + * @brief Ask the broad-phase to test again the collision shapes of the body for collision (as if the body has moved). + */ + protected void askForBroadPhaseCollisionCheck() { + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.getNext()) { + this.world.getCollisionDetection().askForBroadPhaseCollisionCheck(shape); + } + } + + /** + * @brief Compute and return the AABB of the body by merging all proxy shapes AABBs + * @return The axis-aligned bounding box (AABB) of the body in world-space coordinates + */ + public AABB getAABB() { + final AABB bodyAABB = new AABB(); + if (this.proxyCollisionShapes == null) { + return bodyAABB; + } + //Log.info("tmp this.transform : " + this.transform); + //Log.info("tmp this.proxyCollisionShapes.getLocalToBodyTransform() : " + this.proxyCollisionShapes.getLocalToBodyTransform()); + //Log.info("tmp this.transform.multiplyNew(this.proxyCollisionShapes.getLocalToBodyTransform()) : " + this.transform.multiplyNew(this.proxyCollisionShapes.getLocalToBodyTransform())); + this.proxyCollisionShapes.getCollisionShape().computeAABB(bodyAABB, this.transform.multiplyNew(this.proxyCollisionShapes.getLocalToBodyTransform())); + for (ProxyShape shape = this.proxyCollisionShapes.next; shape != null; shape = shape.next) { + final AABB aabb = new AABB(); + shape.getCollisionShape().computeAABB(aabb, this.transform.multiplyNew(shape.getLocalToBodyTransform())); + bodyAABB.mergeWithAABB(aabb); + } + //Log.info("tmp aabb : " + bodyAABB); + //Log.info("tmp aabb : " + bodyAABB.getMax().lessNew(bodyAABB.getMin())); + return bodyAABB; + } + + /** + * @brief Get the first element of the linked list of contact manifolds involving this body + * @return A pointer to the first element of the linked-list with the contact manifolds of this body + */ + public ContactManifoldListElement getContactManifoldsList() { + return this.contactManifoldsList; + } + + /** + * @brief Get the body local-space coordinates of a point given in the world-space coordinates + * @param[in] _worldPoint A point in world-space coordinates + * @return The point in the local-space coordinates of the body + */ + public Vector3f getLocalPoint(final Vector3f _worldPoint) { + return this.transform.inverseNew().multiply(_worldPoint); + } + + /** + * @brief Get the body local-space coordinates of a vector given in the world-space coordinates + * @param[in] _worldVector A vector in world-space coordinates + * @return The vector in the local-space coordinates of the body + */ + public Vector3f getLocalVector(final Vector3f _worldVector) { + return this.transform.getOrientation().inverseNew().multiply(_worldVector); + } + + /** + * @brief Get the linked list of proxy shapes of that body + * @return The pointer of the first proxy shape of the linked-list of all the + * proxy shapes of the body + */ + public ProxyShape getProxyShapesList() { + return this.proxyCollisionShapes; + } + + /** + * @brief Return the current position and orientation + * @return The current transformation of the body that transforms the local-space of the body int32_to world-space + */ + public Transform3D getTransform() { + return this.transform; + } + + /** + * @brief Return the type of the body + * @return the type of the body (STATIC, KINEMATIC, DYNAMIC) + */ + public BodyType getType() { + return this.type; + } + + public CollisionWorld getWorld() { + return this.world; + } + + /** + * @brief Get the world-space coordinates of a point given the local-space coordinates of the body + * @param[in] _localPoint A point in the local-space coordinates of the body + * @return The point in world-space coordinates + */ + public Vector3f getWorldPoint(final Vector3f _localPoint) { + return this.transform.multiplyNew(_localPoint); + } + + /** + * @brief Get the world-space vector of a vector given in local-space coordinates of the body + * @param[in] _localVector A vector in the local-space coordinates of the body + * @return The vector in world-space coordinates + */ + public Vector3f getWorldVector(final Vector3f _localVector) { + return this.transform.getOrientation().multiply(_localVector); + } + + /** + * @brief Raycast method with feedback information + * The method returns the closest hit among all the collision shapes of the body + * @param[in] _ray The ray used to raycast agains the body + * @param[out] _raycastInfo Structure that contains the result of the raycasting (valid only if the method returned true) + * @return True if the ray hit the body and false otherwise + */ + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo) { + if (this.isActive == false) { + return false; + } + boolean isHit = false; + + final Ray rayTemp = new Ray(_ray); + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + // Test if the ray hits the collision shape + if (shape.raycast(rayTemp, _raycastInfo)) { + rayTemp.maxFraction = _raycastInfo.hitFraction; + isHit = true; + } + } + return isHit; + } + + /** + * @brief Remove all the collision shapes + */ + public void removeAllCollisionShapes() { + ProxyShape current = this.proxyCollisionShapes; + // Look for the proxy shape that contains the collision shape in parameter + while (current != null) { + // Remove the proxy collision shape + final ProxyShape nextElement = current.next; + if (this.isActive) { + this.world.collisionDetection.removeProxyCollisionShape(current); + } + // Get the next element in the list + current = nextElement; + } + this.proxyCollisionShapes = null; + } + + /** + * @brief Remove a collision shape from the body + * To remove a collision shape, you need to specify the pointer to the proxy shape that has been returned when you have added the collision shape to the body + * @param[in] _proxyShape The pointer of the proxy shape you want to remove + */ + public void removeCollisionShape(final ProxyShape _proxyShape) { + ProxyShape current = this.proxyCollisionShapes; + // If the the first proxy shape is the one to remove + if (current == _proxyShape) { + this.proxyCollisionShapes = current.next; + if (this.isActive) { + this.world.collisionDetection.removeProxyCollisionShape(current); + } + current = null; + this.numberCollisionShapes--; + return; + } + // Look for the proxy shape that contains the collision shape in parameter + while (current.next != null) { + // If we have found the collision shape to remove + if (current.next == _proxyShape) { + // Remove the proxy collision shape + ProxyShape elementToRemove = current.next; + current.next = elementToRemove.next; + if (this.isActive) { + this.world.collisionDetection.removeProxyCollisionShape(elementToRemove); + } + elementToRemove = null; + this.numberCollisionShapes--; + return; + } + // Get the next element in the list + current = current.next; + } + } + + /** + * @brief Reset the contact manifold lists + */ + public void resetContactManifoldsList() { + // Delete the linked list of contact manifolds of that body + this.contactManifoldsList = null; + } + + /** + * @brief Reset the this.isAlreadyInIsland variable of the body and contact manifolds. + * This method also returns the number of contact manifolds of the body. + */ + public int resetIsAlreadyInIslandAndCountManifolds() { + this.isAlreadyInIsland = false; + int nbManifolds = 0; + // Reset the this.isAlreadyInIsland variable of the contact manifolds for this body + ContactManifoldListElement currentElement = this.contactManifoldsList; + while (currentElement != null) { + currentElement.contactManifold.isAlreadyInIsland = false; + currentElement = currentElement.next; + nbManifolds++; + } + return nbManifolds; + } + + /** + * @brief Set whether or not the body is active + * @param[in] _isActive True if you want to activate the body + */ + @Override + public void setIsActive(final boolean _isActive) { + // If the state does not change + if (this.isActive == _isActive) { + return; + } + super.setIsActive(_isActive); + // If we have to activate the body + if (_isActive == true) { + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + final AABB aabb = new AABB(); + shape.getCollisionShape().computeAABB(aabb, this.transform.multiplyNew(shape.localToBodyTransform)); + this.world.collisionDetection.addProxyCollisionShape(shape, aabb); + } + } else { + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + this.world.collisionDetection.removeProxyCollisionShape(shape); + } + resetContactManifoldsList(); + } + } + + /** + * @brief Set the current position and orientation + * @param transform The transformation of the body that transforms the local-space of the body int32_to world-space + */ + public void setTransform(final Transform3D _transform) { + //Log.info(" set transform: " + this.transform + " ==> " + _transform); + if (Float.isNaN(_transform.getPosition().x)) { + Log.critical(" set transform: " + this.transform + " ==> " + _transform); + } + if (Float.isInfinite(_transform.getOrientation().z)) { + Log.critical(" set transform: " + this.transform + " ==> " + _transform); + } + this.transform = _transform; + updateBroadPhaseState(); + } + + /** + * @brief Set the type of the body + * @param[in] type The type of the body (STATIC, KINEMATIC, DYNAMIC) + */ + public void setType(final BodyType _type) { + this.type = _type; + if (this.type == BodyType.STATIC) { + // Update the broad-phase state of the body + updateBroadPhaseState(); + } + } + + /** + * @brief Return true if a point is inside the collision body + * This method returns true if a point is inside any collision shape of the body + * @param[in] _worldPoint The point to test (in world-space coordinates) + * @return True if the point is inside the body + */ + public boolean testPointInside(final Vector3f _worldPoint) { + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + if (shape.testPointInside(_worldPoint)) { + return true; + } + } + return false; + } + + /** + * @brief Update the broad-phase state for this body (because it has moved for instance) + */ + protected void updateBroadPhaseState() { + // For all the proxy collision shapes of the body + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.getNext()) { + // Update the proxy + updateProxyShapeInBroadPhase(shape, false); + } + } + + /** + * @brief Update the broad-phase state of a proxy collision shape of the body + */ + public void updateProxyShapeInBroadPhase(final ProxyShape _proxyShape, final boolean _forceReinsert /* false */) { + final AABB aabb = new AABB(); + _proxyShape.getCollisionShape().computeAABB(aabb, this.transform.multiplyNew(_proxyShape.getLocalToBodyTransform())); + this.world.getCollisionDetection().updateProxyCollisionShape(_proxyShape, aabb, new Vector3f(0, 0, 0), _forceReinsert); + } +} diff --git a/src/org/atriasoft/ephysics/body/RigidBody.java b/src/org/atriasoft/ephysics/body/RigidBody.java new file mode 100644 index 0000000..683b5e9 --- /dev/null +++ b/src/org/atriasoft/ephysics/body/RigidBody.java @@ -0,0 +1,574 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.body; + +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.constraint.Joint; +import org.atriasoft.ephysics.constraint.JointListElement; +import org.atriasoft.ephysics.engine.CollisionWorld; +import org.atriasoft.ephysics.engine.DynamicsWorld; +import org.atriasoft.ephysics.engine.Material; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a rigid body of the physics + * engine. A rigid body is a non-deformable body that + * has a ant mass. This class inherits from the + * CollisionBody class. + */ +public class RigidBody extends CollisionBody { + + protected float initMass = 1.0f; //!< Intial mass of the body + protected Vector3f centerOfMassLocal = new Vector3f(0, 0, 0); //!< Center of mass of the body in local-space coordinates. The center of mass can therefore be different from the body origin + public Vector3f centerOfMassWorld = new Vector3f(); //!< Center of mass of the body in world-space coordinates + public Vector3f linearVelocity = new Vector3f(); //!< Linear velocity of the body + public Vector3f angularVelocity = new Vector3f(); //!< Angular velocity of the body + public Vector3f externalForce = new Vector3f(); //!< Current external force on the body + public Vector3f externalTorque = new Vector3f(); //!< Current external torque on the body + public Matrix3f inertiaTensorLocal = new Matrix3f(); //!< Local inertia tensor of the body (in local-space) with respect to the center of mass of the body + public Matrix3f inertiaTensorLocalInverse = new Matrix3f(); //!< Inverse of the inertia tensor of the body + public float massInverse; //!< Inverse of the mass of the body + protected boolean isGravityEnabled = true; //!< True if the gravity needs to be applied to this rigid body + protected Material material = new Material(); //!< Material properties of the rigid body + protected float linearDamping = 0.0f; //!< Linear velocity damping factor + protected float angularDamping = 0.0f; //!< Angular velocity damping factor + public JointListElement jointsList = null; //!< First element of the linked list of joints involving this body + protected boolean angularReactionEnable = true; + + /** + * Constructor + * @param transform The transformation of the body + * @param world The world where the body has been added + * @param id The ID of the body + */ + public RigidBody(final Transform3D transform, final CollisionWorld world, final int id) { + super(transform, world, id); + this.centerOfMassWorld = new Vector3f(transform.getPosition()); + this.massInverse = 1.0f / this.initMass; + } + + /** + * Add a collision shape to the body. + * When you add a collision shape to the body, an intternal copy of this collision shape will be created internally. + * Therefore, you can delete it right after calling this method or use it later to add it to another body. + * This method will return a pointer to a new proxy shape. A proxy shape is an object that links a collision shape and a given body. + * You can use the returned proxy shape to get and set information about the corresponding collision shape for that body. + * @param _collisionShape The collision shape you want to add to the body + * @param _transform The transformation of the collision shape that transforms the local-space of the collision shape into the local-space of the body + * @param _mass Mass (in kilograms) of the collision shape you want to add + * @return A pointer to the proxy shape that has been created to link the body to the new collision shape you have added. + */ + public ProxyShape addCollisionShape(final CollisionShape _collisionShape, final Transform3D _transform, final float _mass) { + assert (_mass > 0.0f); + // Create a new proxy collision shape to attach the collision shape to the body + final ProxyShape proxyShape = new ProxyShape(this, _collisionShape, _transform, _mass); + // Add it to the list of proxy collision shapes of the body + if (this.proxyCollisionShapes == null) { + this.proxyCollisionShapes = proxyShape; + } else { + proxyShape.next = this.proxyCollisionShapes; + this.proxyCollisionShapes = proxyShape; + } + // Compute the world-space AABB of the new collision shape + final AABB aabb = new AABB(); + _collisionShape.computeAABB(aabb, this.transform.multiplyNew(_transform)); + // Notify the collision detection about this new collision shape + this.world.collisionDetection.addProxyCollisionShape(proxyShape, aabb); + this.numberCollisionShapes++; + recomputeMassInformation(); + return proxyShape; + } + + /** + * Apply an external force to the body at a given point (in world-space coordinates). + * If the point is not at the center of mass of the body, it will also + * generate some torque and therefore, change the angular velocity of the body. + * If the body is sleeping, calling this method will wake it up. Note that the + * force will we added to the sum of the applied forces and that this sum will be + * reset to zero at the end of each call of the DynamicsWorld::update() method. + * You can only apply a force to a dynamic body otherwise, this method will do nothing. + * @param _force The force to apply on the body + * @param _point The point where the force is applied (in world-space coordinates) + */ + public void applyForce(final Vector3f _force, final Vector3f _point) { + if (this.type != BodyType.DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalForce.add(_force); + this.externalTorque.add(_point.lessNew(this.centerOfMassWorld).cross(_force)); + } + + /** + * Apply an external force to the body at its center of mass. + * If the body is sleeping, calling this method will wake it up. Note that the + * force will we added to the sum of the applied forces and that this sum will be + * reset to zero at the end of each call of the DynamicsWorld::update() method. + * You can only apply a force to a dynamic body otherwise, this method will do nothing. + * @param _force The external force to apply on the center of mass of the body + */ + public void applyForceToCenterOfMass(final Vector3f _force) { + if (this.type != BodyType.DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalForce.add(_force); + } + + /** + * Apply an external torque to the body. + * If the body is sleeping, calling this method will wake it up. Note that the + * force will we added to the sum of the applied torques and that this sum will be + * reset to zero at the end of each call of the DynamicsWorld::update() method. + * You can only apply a force to a dynamic body otherwise, this method will do nothing. + * @param _torque The external torque to apply on the body + */ + public void applyTorque(final Vector3f _torque) { + if (this.type != BodyType.DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalTorque.add(_torque); + } + + /** + * Set the variable to know if the gravity is applied to this rigid body + * @param _isEnabled True if you want the gravity to be applied to this body + */ + public void enableGravity(final boolean _isEnabled) { + this.isGravityEnabled = _isEnabled; + } + + /** + * Get the angular velocity damping factor + * @return The angular damping factor of this body + */ + public float getAngularDamping() { + return this.angularDamping; + } + + /** + * Get the angular velocity of the body + * @return The angular velocity vector of the body + */ + public Vector3f getAngularVelocity() { + return this.angularVelocity; + } + + /** + * Get the inverse of the inertia tensor in world coordinates. + * The inertia tensor I_w in world coordinates is computed with the + * local inverse inertia tensor I_b^-1 in body coordinates + * by I_w = R * I_b^-1 * R^T + * where R is the rotation matrix (and R^T its transpose) of the + * current orientation quaternion of the body + * @return The 3x3 inverse inertia tensor matrix of the body in world-space coordinates + */ + public Matrix3f getInertiaTensorInverseWorld() { + // TODO : DO NOT RECOMPUTE THE MATRIX MULTIPLICATION EVERY TIME. WE NEED TO STORE THE + // INVERSE WORLD TENSOR IN THE CLASS AND UPLDATE IT WHEN THE ORIENTATION OF THE BODY CHANGES + // Compute and return the inertia tensor in world coordinates + //Log.error("}}} this.transform=" + this.transform); + //Log.error("}}} this.inertiaTensorLocalInverse=" + this.inertiaTensorLocalInverse); + //Log.error("}}} this.transform.getOrientation().getMatrix().transposeNew()=" + this.transform.getOrientation().getMatrix().transposeNew()); + //Log.error("}}} this.transform.getOrientation().getMatrix()=" + this.transform.getOrientation().getMatrix()); + final Matrix3f tmp = this.transform.getOrientation().getMatrix().multiplyNew(this.inertiaTensorLocalInverse).multiply(this.transform.getOrientation().getMatrix().transposeNew()); + //Log.error("}}} tmp=" + tmp); + return tmp; + } + + /** + * Get the local inertia tensor of the body (in local-space coordinates) + * @return The 3x3 inertia tensor matrix of the body + */ + public Matrix3f getInertiaTensorLocal() { + return this.inertiaTensorLocal; + } + + /** + * Get the inertia tensor in world coordinates. + * The inertia tensor I_w in world coordinates is computed + * with the local inertia tensor I_b in body coordinates + * by I_w = R * I_b * R^T + * where R is the rotation matrix (and R^T its transpose) of + * the current orientation quaternion of the body + * @return The 3x3 inertia tensor matrix of the body in world-space coordinates + */ + public Matrix3f getInertiaTensorWorld() { + // Compute and return the inertia tensor in world coordinates + return this.transform.getOrientation().getMatrix().multiplyNew(this.inertiaTensorLocal).multiply(this.transform.getOrientation().getMatrix().transposeNew()); + } + + /** + * Get the first element of the linked list of joints involving this body + * @return The first element of the linked-list of all the joints involving this body + */ + public JointListElement getJointsList() { + return this.jointsList; + } + + /** + * Get the linear velocity damping factor + * @return The linear damping factor of this body + */ + public float getLinearDamping() { + return this.linearDamping; + } + + /** + * Get the linear velocity + * @return The linear velocity vector of the body + */ + public Vector3f getLinearVelocity() { + return this.linearVelocity; + } + + /** + * Get the mass of the body + * @return The mass (in kilograms) of the body + */ + public float getMass() { + return this.initMass; + } + + /** + * get a reference to the material properties of the rigid body + * @return A reference to the material of the body + */ + public Material getMaterial() { + return this.material; + } + + public boolean isAngularReactionEnable() { + return this.angularReactionEnable; + } + + /** + * get the need of gravity appling to this rigid body + * @return True if the gravity is applied to the body + */ + public boolean isGravityEnabled() { + return this.isGravityEnabled; + } + + /** + * Recompute the center of mass, total mass and inertia tensor of the body using all the collision shapes attached to the body. + */ + public void recomputeMassInformation() { + this.initMass = 0.0f; + this.massInverse = 0.0f; + this.inertiaTensorLocal.setZero(); + this.inertiaTensorLocalInverse.setZero(); + this.centerOfMassLocal.setZero(); + // If it is STATIC or KINEMATIC body + if (this.type == BodyType.STATIC || this.type == BodyType.KINEMATIC) { + this.centerOfMassWorld = this.transform.getPosition(); + return; + } + assert (this.type == BodyType.DYNAMIC); + // Compute the total mass of the body + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + this.initMass += shape.getMass(); + this.centerOfMassLocal.add(shape.getLocalToBodyTransform().getPosition().multiplyNew(shape.getMass())); + } + if (this.initMass > 0.0f) { + this.massInverse = 1.0f / this.initMass; + } else { + this.initMass = 1.0f; + this.massInverse = 1.0f; + } + // Compute the center of mass + final Vector3f oldCenterOfMass = this.centerOfMassWorld; + this.centerOfMassLocal.multiply(this.massInverse); + this.centerOfMassWorld = this.transform.multiplyNew(this.centerOfMassLocal); + // Compute the total mass and inertia tensor using all the collision shapes + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + // Get the inertia tensor of the collision shape in its local-space + Matrix3f inertiaTensor = new Matrix3f(); + shape.getCollisionShape().computeLocalInertiaTensor(inertiaTensor, shape.getMass()); + // Convert the collision shape inertia tensor into the local-space of the body + final Transform3D shapeTransform = shape.getLocalToBodyTransform(); + final Matrix3f rotationMatrix = shapeTransform.getOrientation().getMatrix(); + inertiaTensor = rotationMatrix.multiplyNew(inertiaTensor).multiply(rotationMatrix.transposeNew()); + // Use the parallel axis theorem to convert the inertia tensor w.r.t the collision shape + // center into a inertia tensor w.r.t to the body origin. + final Vector3f offset = shapeTransform.getPosition().lessNew(this.centerOfMassLocal); + final float offsetSquare = offset.length2(); + final Vector3f off1 = offset.multiplyNew(-offset.x); + final Vector3f off2 = offset.multiplyNew(-offset.y); + final Vector3f off3 = offset.multiplyNew(-offset.z); + final Matrix3f offsetMatrix = new Matrix3f(off1.x + offsetSquare, off1.y, off1.z, off2.x, off2.y + offsetSquare, off2.z, off3.x, off3.y, off3.z + offsetSquare); + offsetMatrix.multiply(shape.getMass()); + this.inertiaTensorLocal.add(inertiaTensor.addNew(offsetMatrix)); + } + // Compute the local inverse inertia tensor + this.inertiaTensorLocalInverse = this.inertiaTensorLocal.inverseNew(); + // Update the linear velocity of the center of mass + this.linearVelocity.add(this.angularVelocity.cross(this.centerOfMassWorld.lessNew(oldCenterOfMass))); + } + + @Override + public void removeCollisionShape(final ProxyShape _proxyShape) { + super.removeCollisionShape(_proxyShape); + recomputeMassInformation(); + } + + /** + * Remove a joint from the joints list + */ + public void removeJointFromjointsList(final Joint joint) { + assert (joint != null); + assert (this.jointsList != null); + // Remove the joint from the linked list of the joints of the first body + if (this.jointsList.joint == joint) { // If the first element is the one to remove + JointListElement elementToRemove = this.jointsList; + this.jointsList = elementToRemove.next; + elementToRemove = null; + } else { // If the element to remove is not the first one in the list + JointListElement currentElement = this.jointsList; + while (currentElement.next != null) { + if (currentElement.next.joint == joint) { + JointListElement elementToRemove = currentElement.next; + currentElement.next = elementToRemove.next; + elementToRemove = null; + break; + } + currentElement = currentElement.next; + } + } + } + + /** + * Set the angular damping factor. This is the ratio of the angular velocity that the body will lose at every seconds of simulation. + * @param _angularDamping The angular damping factor of this body + */ + public void setAngularDamping(final float _angularDamping) { + assert (_angularDamping >= 0.0f); + this.angularDamping = _angularDamping; + } + + public void setAngularReactionEnable(final boolean angularReactionEnable) { + this.angularReactionEnable = angularReactionEnable; + } + + /** + * Set the angular velocity. + * @param _angularVelocity The angular velocity vector of the body + */ + public void setAngularVelocity(final Vector3f _angularVelocity) { + if (this.type == BodyType.STATIC) { + return; + } + this.angularVelocity = _angularVelocity; + if (this.angularVelocity.length2() > 0.0f) { + setIsSleeping(false); + } + } + + /** + * Set the local center of mass of the body (in local-space coordinates) + * @param _centerOfMassLocal The center of mass of the body in local-space coordinates + */ + public void setCenterOfMassLocal(final Vector3f centerOfMassLocal) { + if (this.type != BodyType.DYNAMIC) { + return; + } + final Vector3f oldCenterOfMass = this.centerOfMassWorld; + this.centerOfMassLocal = centerOfMassLocal; + this.centerOfMassWorld = this.transform.multiplyNew(this.centerOfMassLocal); + this.linearVelocity.add(this.angularVelocity.cross(this.centerOfMassWorld.lessNew(oldCenterOfMass))); + } + + /** + * Set the local inertia tensor of the body (in local-space coordinates) + * @param _inertiaTensorLocal The 3x3 inertia tensor matrix of the body in local-space coordinates + */ + public void setInertiaTensorLocal(final Matrix3f inertiaTensorLocal) { + if (this.type != BodyType.DYNAMIC) { + return; + } + this.inertiaTensorLocal = inertiaTensorLocal; + this.inertiaTensorLocalInverse = this.inertiaTensorLocal.inverseNew(); + } + + /** + * Set the variable to know whether or not the body is sleeping + * @param _isSleeping New sleeping state of the body + */ + @Override + public void setIsSleeping(final boolean _isSleeping) { + if (_isSleeping) { + this.linearVelocity.setZero(); + this.angularVelocity.setZero(); + this.externalForce.setZero(); + this.externalTorque.setZero(); + } + super.setIsSleeping(_isSleeping); + } + + /** + * Set the linear damping factor. This is the ratio of the linear velocity that the body will lose every at seconds of simulation. + * @param _linearDamping The linear damping factor of this body + */ + public void setLinearDamping(final float _linearDamping) { + assert (_linearDamping >= 0.0f); + this.linearDamping = _linearDamping; + } + + /** + * Set the linear velocity of the rigid body. + * @param _linearVelocity Linear velocity vector of the body + */ + public void setLinearVelocity(final Vector3f _linearVelocity) { + if (this.type == BodyType.STATIC) { + return; + } + this.linearVelocity = _linearVelocity; + if (this.linearVelocity.length2() > 0.0f) { + setIsSleeping(false); + } + } + + /** + * Set the mass of the rigid body + * @param _mass The mass (in kilograms) of the body + */ + public void setMass(final float _mass) { + if (this.type != BodyType.DYNAMIC) { + return; + } + this.initMass = _mass; + if (this.initMass > 0.0f) { + this.massInverse = 1.0f / this.initMass; + } else { + this.initMass = 1.0f; + this.massInverse = 1.0f; + } + } + + /** + * Set a new material for this rigid body + * @param _material The material you want to set to the body + */ + public void setMaterial(final Material _material) { + this.material = _material; + } + + /** + * Set the current position and orientation + * @param _transform The transformation of the body that transforms the local-space of the body into world-space + */ + @Override + public void setTransform(final Transform3D _transform) { + //Log.info(" set transform: " + this.transform + " ==> " + _transform); + if (_transform.getPosition().x == Float.NaN) { + Log.critical(" set transform: " + this.transform + " ==> " + _transform); + } + if (Float.isInfinite(_transform.getOrientation().z)) { + Log.critical(" set transform: " + this.transform + " ==> " + _transform); + } + this.transform = _transform; + final Vector3f oldCenterOfMass = this.centerOfMassWorld; + // Compute the new center of mass in world-space coordinates + this.centerOfMassWorld = this.transform.multiplyNew(this.centerOfMassLocal); + // Update the linear velocity of the center of mass + this.linearVelocity.add(this.angularVelocity.cross(this.centerOfMassWorld.lessNew(oldCenterOfMass))); + updateBroadPhaseState(); + } + + @Override + public void setType(final BodyType _type) { + if (this.type == _type) { + return; + } + super.setType(_type); + recomputeMassInformation(); + if (this.type == BodyType.STATIC) { + // Reset the velocity to zero + this.linearVelocity.setZero(); + this.angularVelocity.setZero(); + } + if (this.type == BodyType.STATIC || this.type == BodyType.KINEMATIC) { + // Reset the inverse mass and inverse inertia tensor to zero + this.massInverse = 0.0f; + this.inertiaTensorLocal.setZero(); + this.inertiaTensorLocalInverse.setZero(); + } else { + this.massInverse = 1.0f / this.initMass; + this.inertiaTensorLocalInverse = this.inertiaTensorLocal.inverseNew(); + } + setIsSleeping(false); + resetContactManifoldsList(); + // Ask the broad-phase to test again the collision shapes of the body for collision detection (as if the body has moved) + askForBroadPhaseCollisionCheck(); + this.externalForce.setZero(); + this.externalTorque.setZero(); + } + + @Override + public void updateBroadPhaseState() { + final DynamicsWorld world = (DynamicsWorld) this.world; + final Vector3f displacement = this.linearVelocity.multiplyNew(world.timeStep); + + // For all the proxy collision shapes of the body + for (ProxyShape shape = this.proxyCollisionShapes; shape != null; shape = shape.next) { + // Recompute the world-space AABB of the collision shape + final AABB aabb = new AABB(); + //Log.info(" : " + aabb.getMin() + " " + aabb.getMax()); + //Log.info(" this.transform: " + this.transform); + shape.getCollisionShape().computeAABB(aabb, this.transform.multiplyNew(shape.getLocalToBodyTransform())); + //Log.info(" : " + aabb.getMin() + " " + aabb.getMax()); + // Update the broad-phase state for the proxy collision shape + //Log.warning(" ==> updateProxyCollisionShape"); + world.collisionDetection.updateProxyCollisionShape(shape, aabb, displacement); + } + } + + /** + * Update the transform of the body after a change of the center of mass + */ + public void updateTransformWithCenterOfMass() { + // Translate the body according to the translation of the center of mass position + this.transform.setPosition(this.centerOfMassWorld.lessNew(this.transform.getOrientation().multiply(this.centerOfMassLocal))); + if (Float.isNaN(this.transform.getPosition().x) == true) { + Log.critical("updateTransformWithCenterOfMass: " + this.transform); + } + if (Float.isInfinite(this.transform.getOrientation().z) == true) { + Log.critical(" set transform: " + this.transform); + } + } + +} diff --git a/src/org/atriasoft/ephysics/collision/CollisionDetection.java b/src/org/atriasoft/ephysics/collision/CollisionDetection.java new file mode 100644 index 0000000..3356833 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/CollisionDetection.java @@ -0,0 +1,497 @@ +package org.atriasoft.ephysics.collision; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import org.atriasoft.ephysics.RaycastCallback; +import org.atriasoft.ephysics.RaycastTest; +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.collision.broadphase.BroadPhaseAlgorithm; +import org.atriasoft.ephysics.collision.broadphase.DTree; +import org.atriasoft.ephysics.collision.broadphase.PairDTree; +import org.atriasoft.ephysics.collision.narrowphase.CollisionDispatch; +import org.atriasoft.ephysics.collision.narrowphase.DefaultCollisionDispatch; +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseAlgorithm; +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseCallback; +import org.atriasoft.ephysics.collision.narrowphase.GJK.GJKAlgorithm; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.collision.shapes.CollisionShapeType; +import org.atriasoft.ephysics.constraint.ContactPoint; +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.ephysics.engine.CollisionCallback; +import org.atriasoft.ephysics.engine.CollisionWorld; +import org.atriasoft.ephysics.engine.EventListener; +import org.atriasoft.ephysics.engine.OverlappingPair; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.ephysics.mathematics.PairInt; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.ephysics.mathematics.Set; +import org.atriasoft.ephysics.mathematics.SetMultiple; +import org.atriasoft.etk.math.Vector3f; + +/* + * @brief It computes the collision detection algorithms. We first + * perform a broad-phase algorithm to know which pairs of bodies can + * collide and then we run a narrow-phase algorithm to compute the + * collision contacts between bodies. + */ +public class CollisionDetection implements NarrowPhaseCallback { + /// Reference on the physics world + private final CollisionWorld world; + /// Broad-phase algorithm + private final BroadPhaseAlgorithm broadPhaseAlgorithm; + /// Collision Detection Dispatch configuration + private CollisionDispatch collisionDispatch; + private final DefaultCollisionDispatch defaultCollisionDispatch = new DefaultCollisionDispatch(); + /// Collision detection matrix (algorithms to use) + private final NarrowPhaseAlgorithm[][] collisionMatrix = new NarrowPhaseAlgorithm[CollisionShapeType.NB_COLLISION_SHAPE_TYPES][CollisionShapeType.NB_COLLISION_SHAPE_TYPES]; + public Map overlappingPairs = new TreeMap<>(Set.getPairDTreeCoparator()); //!< Broad-phase overlapping pairs + private final Map contactOverlappingPair = new TreeMap<>(Set.getPairDTreeCoparator()); //!< Overlapping pairs in contact (during the current Narrow-phase collision detection) + // TODO : Delete this + private final GJKAlgorithm narrowPhaseGJKAlgorithm; //!< Narrow-phase GJK algorithm + private final SetMultiple noCollisionPairs = new SetMultiple(Set.getPairIntCoparator()); //!< Set of pair of bodies that cannot collide between each other + private boolean isCollisionShapesAdded; //!< True if some collision shapes have been added previously + + /// Constructor + public CollisionDetection(final CollisionWorld _world) { + this.world = _world; + this.broadPhaseAlgorithm = new BroadPhaseAlgorithm(this); + this.isCollisionShapesAdded = false; + this.narrowPhaseGJKAlgorithm = new GJKAlgorithm(this); + // Set the default collision dispatch configuration + setCollisionDispatch(this.defaultCollisionDispatch); + // Fill-in the collision detection matrix with algorithms + fillInCollisionMatrix(); + } + + /// Add all the contact manifold of colliding pairs to their bodies + private void addAllContactManifoldsToBodies() { + // For each overlapping pairs in contact during the narrow-phase + for (final OverlappingPair it : this.contactOverlappingPair.values()) { + // Add all the contact manifolds of the pair int32_to the list of contact manifolds + // of the two bodies involved in the contact + addContactManifoldToBody(it); + } + } + + /// Add a contact manifold to the linked list of contact manifolds of the two bodies + /// involed in the corresponding contact. + private void addContactManifoldToBody(final OverlappingPair _pair) { + assert (_pair != null); + final CollisionBody body1 = _pair.getShape1().getBody(); + final CollisionBody body2 = _pair.getShape2().getBody(); + final ContactManifoldSet manifoldSet = _pair.getContactManifoldSet(); + // For each contact manifold in the set of manifolds in the pair + for (int i = 0; i < manifoldSet.getNbContactManifolds(); i++) { + final ContactManifold contactManifold = manifoldSet.getContactManifold(i); + assert (contactManifold.getNbContactPoints() > 0); + // Add the contact manifold at the beginning of the linked + // list of contact manifolds of the first body + body1.contactManifoldsList = new ContactManifoldListElement(contactManifold, body1.contactManifoldsList); + + // Add the contact manifold at the beginning of the linked + // list of the contact manifolds of the second body + body2.contactManifoldsList = new ContactManifoldListElement(contactManifold, body2.contactManifoldsList); + + } + } + + /// Add a pair of bodies that cannot collide with each other + public void addNoCollisionPair(final CollisionBody _body1, final CollisionBody _body2) { + this.noCollisionPairs.add(OverlappingPair.computeBodiesIndexPair(_body1, _body2)); + } + + /// Add a proxy collision shape to the collision detection + public void addProxyCollisionShape(final ProxyShape _proxyShape, final AABB _aabb) { + // Add the body to the broad-phase + this.broadPhaseAlgorithm.addProxyCollisionShape(_proxyShape, _aabb); + this.isCollisionShapesAdded = true; + } + + // Ask for a collision shape to be tested again during broad-phase. + /// We simply put the shape in the list of collision shape that have moved in the + /// previous frame so that it is tested for collision again in the broad-phase. + public void askForBroadPhaseCollisionCheck(final ProxyShape _shape) { + this.broadPhaseAlgorithm.addMovedCollisionShape(_shape.broadPhaseID); + } + + /// Allow the broadphase to notify the collision detection about an overlapping pair. + /// This method is called by the broad-phase collision detection algorithm + public void broadPhaseNotifyOverlappingPair(final ProxyShape _shape1, final ProxyShape _shape2) { + assert (_shape1.broadPhaseID != _shape2.broadPhaseID); + // If the two proxy collision shapes are from the same body, skip it + if (_shape1.getBody().getID() == _shape2.getBody().getID()) { + return; + } + //Log.info(" check collision is allowed: " + _shape1.getBody().getID() + " " + _shape2.getBody().getID()); + // Check if the collision filtering allows collision between the two shapes + if ((_shape1.getCollideWithMaskBits() & _shape2.getCollisionCategoryBits()) == 0 || (_shape1.getCollisionCategoryBits() & _shape2.getCollideWithMaskBits()) == 0) { + Log.info(" ==> not permited ..."); + return; + } + // Compute the overlapping pair ID + final PairDTree pairID = OverlappingPair.computeID(_shape1, _shape2); + // Check if the overlapping pair already exists + if (this.overlappingPairs.get(pairID) != null) { + return; + } + // Compute the maximum number of contact manifolds for this pair + final int nbMaxManifolds = CollisionShape.computeNbMaxContactManifolds(_shape1.getCollisionShape().getType(), _shape2.getCollisionShape().getType()); + // Create the overlapping pair and add it int32_to the set of overlapping pairs + final OverlappingPair newPair = new OverlappingPair(_shape1, _shape2, nbMaxManifolds); + assert (newPair != null); + this.overlappingPairs.putIfAbsent(pairID, newPair); + // Wake up the two bodies + _shape1.getBody().setIsSleeping(false); + _shape2.getBody().setIsSleeping(false); + } + + /// Delete all the contact points in the currently overlapping pairs + private void clearContactPoints() { + // For each overlapping pair + this.overlappingPairs.forEach((key, value) -> value.clearContactPoints()); + } + + /// Compute the broad-phase collision detection + private void computeBroadPhase() { + // If new collision shapes have been added to bodies + if (this.isCollisionShapesAdded) { + // Ask the broad-phase to recompute the overlapping pairs of collision + // shapes. This call can only add new overlapping pairs in the collision + // detection. + this.broadPhaseAlgorithm.computeOverlappingPairs(); + } + } + + /// Compute the collision detection + public void computeCollisionDetection() { + //Log.info("computeBroadPhase();"); + // Compute the broad-phase collision detection + computeBroadPhase(); + //Log.info("computeNarrowPhase();"); + // Compute the narrow-phase collision detection + computeNarrowPhase(); + } + + /// Compute the narrow-phase collision detection + private void computeNarrowPhase() { + // Clear the set of overlapping pairs in narrow-phase contact + this.contactOverlappingPair.clear(); + { + //Log.info("list elements:"); + final Iterator> ittt = this.overlappingPairs.entrySet().iterator(); + while (ittt.hasNext()) { + final Map.Entry entry = ittt.next(); + //Log.info(" " + entry.getKey() + " " + entry.getValue()); + + } + } + // For each possible collision pair of bodies + final Iterator> it = this.overlappingPairs.entrySet().iterator(); + + while (it.hasNext()) { + final Map.Entry entry = it.next(); + final OverlappingPair pair = entry.getValue(); + final ProxyShape shape1 = pair.getShape1(); + final ProxyShape shape2 = pair.getShape2(); + assert (shape1.broadPhaseID != shape2.broadPhaseID); + // Check if the collision filtering allows collision between the two shapes and + // that the two shapes are still overlapping. Otherwise, we destroy the + // overlapping pair + if (((shape1.getCollideWithMaskBits() & shape2.getCollisionCategoryBits()) == 0 || (shape1.getCollisionCategoryBits() & shape2.getCollideWithMaskBits()) == 0) + || !this.broadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) { + // TODO : Remove all the contact manifold of the overlapping pair from the contact manifolds list of the two bodies involved + // Destroy the overlapping pair + it.remove(); + continue; + } + final CollisionBody body1 = shape1.getBody(); + final CollisionBody body2 = shape2.getBody(); + // Update the contact cache of the overlapping pair + pair.update(); + // Check that at least one body is awake and not static + final boolean isBody1Active = !body1.isSleeping() && body1.getType() != BodyType.STATIC; + final boolean isBody2Active = !body2.isSleeping() && body2.getType() != BodyType.STATIC; + if (!isBody1Active && !isBody2Active) { + continue; + } + // Check if the bodies are in the set of bodies that cannot collide between each other + final PairInt bodiesIndex = OverlappingPair.computeBodiesIndexPair(body1, body2); + if (this.noCollisionPairs.count(bodiesIndex) > 0) { + continue; + } + // Select the narrow phase algorithm to use according to the two collision shapes + final CollisionShapeType shape1Type = shape1.getCollisionShape().getType(); + final CollisionShapeType shape2Type = shape2.getCollisionShape().getType(); + final NarrowPhaseAlgorithm narrowPhaseAlgorithm = this.collisionMatrix[shape1Type.value][shape2Type.value]; + // If there is no collision algorithm between those two kinds of shapes + if (narrowPhaseAlgorithm == null) { + continue; + } + // Notify the narrow-phase algorithm about the overlapping pair we are going to test + narrowPhaseAlgorithm.setCurrentOverlappingPair(pair); + // Create the CollisionShapeInfo objects + final CollisionShapeInfo shape1Info = new CollisionShapeInfo(shape1, shape1.getCollisionShape(), shape1.getLocalToWorldTransform(), pair, shape1.getCachedCollisionData()); + + final CollisionShapeInfo shape2Info = new CollisionShapeInfo(shape2, shape2.getCollisionShape(), shape2.getLocalToWorldTransform(), pair, shape2.getCachedCollisionData()); + + // Use the narrow-phase collision detection algorithm to check + // if there really is a collision. If a collision occurs, the + // notifyContact() callback method will be called. + narrowPhaseAlgorithm.testCollision(shape1Info, shape2Info, this); + } + + // Add all the contact manifolds (between colliding bodies) to the bodies + addAllContactManifoldsToBodies(); + + } + + /// Compute the narrow-phase collision detection + public void computeNarrowPhaseBetweenShapes(final CollisionCallback _callback, final Set _shapes1, final Set _shapes2) { + this.contactOverlappingPair.clear(); + // For each possible collision pair of bodies + final Iterator> it = this.overlappingPairs.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + final OverlappingPair pair = entry.getValue(); + final ProxyShape shape1 = pair.getShape1(); + final ProxyShape shape2 = pair.getShape2(); + assert (shape1.broadPhaseID != shape2.broadPhaseID); + // If both shapes1 and shapes2 sets are non-empty, we check that + // shape1 is among on set and shape2 is among the other one + if (!_shapes1.isEmpty() && !_shapes2.isEmpty() && (_shapes1.count(shape1.broadPhaseID) == 0 || _shapes2.count(shape2.broadPhaseID) == 0) + && (_shapes1.count(shape2.broadPhaseID) == 0 || _shapes2.count(shape1.broadPhaseID) == 0)) { + continue; + } + if (!_shapes1.isEmpty() && _shapes2.isEmpty() && _shapes1.count(shape1.broadPhaseID) == 0 && _shapes1.count(shape2.broadPhaseID) == 0) { + continue; + } + if (!_shapes2.isEmpty() && _shapes1.isEmpty() && _shapes2.count(shape1.broadPhaseID) == 0 && _shapes2.count(shape2.broadPhaseID) == 0) { + continue; + } + // Check if the collision filtering allows collision between the two shapes and + // that the two shapes are still overlapping. Otherwise, we destroy the + // overlapping pair + if (((shape1.getCollideWithMaskBits() & shape2.getCollisionCategoryBits()) == 0 || (shape1.getCollisionCategoryBits() & shape2.getCollideWithMaskBits()) == 0) + || !this.broadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) { + // TODO : Remove all the contact manifold of the overlapping pair from the contact manifolds list of the two bodies involved + // Destroy the overlapping pair + it.remove(); + continue; + } + final CollisionBody body1 = shape1.getBody(); + final CollisionBody body2 = shape2.getBody(); + // Update the contact cache of the overlapping pair + pair.update(); + // Check if the two bodies are allowed to collide, otherwise, we do not test for collision + if (body1.getType() != BodyType.DYNAMIC && body2.getType() != BodyType.DYNAMIC) { + continue; + } + final PairInt bodiesIndex = OverlappingPair.computeBodiesIndexPair(body1, body2); + if (this.noCollisionPairs.count(bodiesIndex) > 0) { + continue; + } + // Check if the two bodies are sleeping, if so, we do no test collision between them + if (body1.isSleeping() && body2.isSleeping()) { + continue; + } + // Select the narrow phase algorithm to use according to the two collision shapes + final CollisionShapeType shape1Type = shape1.getCollisionShape().getType(); + final CollisionShapeType shape2Type = shape2.getCollisionShape().getType(); + final NarrowPhaseAlgorithm narrowPhaseAlgorithm = this.collisionMatrix[shape1Type.value][shape2Type.value]; + // If there is no collision algorithm between those two kinds of shapes + if (narrowPhaseAlgorithm == null) { + continue; + } + // Notify the narrow-phase algorithm about the overlapping pair we are going to test + narrowPhaseAlgorithm.setCurrentOverlappingPair(pair); + // Create the CollisionShapeInfo objects + final CollisionShapeInfo shape1Info = new CollisionShapeInfo(shape1, shape1.getCollisionShape(), shape1.getLocalToWorldTransform(), pair, shape1.getCachedCollisionData()); + + final CollisionShapeInfo shape2Info = new CollisionShapeInfo(shape2, shape2.getCollisionShape(), shape2.getLocalToWorldTransform(), pair, shape2.getCachedCollisionData()); + + final TestCollisionBetweenShapesCallback narrowPhaseCallback = new TestCollisionBetweenShapesCallback(_callback); + // Use the narrow-phase collision detection algorithm to check + // if there really is a collision + narrowPhaseAlgorithm.testCollision(shape1Info, shape2Info, narrowPhaseCallback); + } + + // Add all the contact manifolds (between colliding bodies) to the bodies + addAllContactManifoldsToBodies(); + } + + void createContact(final OverlappingPair _overlappingPair, final ContactPointInfo _contactInfo) { + // Create a new contact + final ContactPoint contact = new ContactPoint(_contactInfo); + // Add the contact to the contact manifold set of the corresponding overlapping pair + _overlappingPair.addContact(contact); + // Add the overlapping pair int32_to the set of pairs in contact during narrow-phase + final PairDTree pairId = OverlappingPair.computeID(_overlappingPair.getShape1(), _overlappingPair.getShape2()); + this.contactOverlappingPair.put(pairId, _overlappingPair); + } + + /// Fill-in the collision detection matrix + private void fillInCollisionMatrix() { + // For each possible type of collision shape + for (int i = 0; i < CollisionShapeType.NB_COLLISION_SHAPE_TYPES; i++) { + for (int j = 0; j < CollisionShapeType.NB_COLLISION_SHAPE_TYPES; j++) { + this.collisionMatrix[i][j] = this.collisionDispatch.selectAlgorithm(CollisionShapeType.getType(i), CollisionShapeType.getType(j)); + } + } + } + + /// Return the Narrow-phase collision detection algorithm to use between two types of shapes + public NarrowPhaseAlgorithm getCollisionAlgorithm(final CollisionShapeType _shape1Type, final CollisionShapeType _shape2Type) { + return this.collisionMatrix[_shape1Type.value][_shape2Type.value]; + } + + public GJKAlgorithm getNarrowPhaseGJKAlgorithm() { + return this.narrowPhaseGJKAlgorithm; + } + + /// Return a pointer to the world + public CollisionWorld getWorld() { + return this.world; + } + + /// Return the world event listener + public EventListener getWorldEventListener() { + return this.world.eventListener; + } + + /// Called by a narrow-phase collision algorithm when a new contact has been found + @Override + public void notifyContact(final OverlappingPair _overlappingPair, final ContactPointInfo _contactInfo) { + Log.error("Notify Contact ... --------------------"); + // If it is the first contact since the pairs are overlapping + if (_overlappingPair.getNbContactPoints() == 0) { + // Trigger a callback event + if (this.world.eventListener != null) { + this.world.eventListener.beginContact(_contactInfo); + } + } + // Create a new contact + createContact(_overlappingPair, _contactInfo); + // Trigger a callback event for the new contact + if (this.world.eventListener != null) { + this.world.eventListener.newContact(_contactInfo); + } + } + + /// Ray casting method + public void raycast(final RaycastCallback _raycastCallback, final Ray _ray, final int _raycastWithCategoryMaskBits) { + final RaycastTest rayCastTest = new RaycastTest(_raycastCallback); + // Ask the broad-phase algorithm to call the testRaycastAgainstShape() + // callback method for each proxy shape hit by the ray in the broad-phase + this.broadPhaseAlgorithm.raycast(_ray, rayCastTest, _raycastWithCategoryMaskBits); + } + + /// Remove a pair of bodies that cannot collide with each other + public void removeNoCollisionPair(final CollisionBody _body1, final CollisionBody _body2) { + this.noCollisionPairs.erase(this.noCollisionPairs.find(OverlappingPair.computeBodiesIndexPair(_body1, _body2))); + } + + /// Remove a proxy collision shape from the collision detection + public void removeProxyCollisionShape(final ProxyShape _proxyShape) { + // Remove all the overlapping pairs involving this proxy shape + final Iterator> it = this.overlappingPairs.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + final OverlappingPair pair = entry.getValue(); + if (pair.getShape1().broadPhaseID == _proxyShape.broadPhaseID || pair.getShape2().broadPhaseID == _proxyShape.broadPhaseID) { + // TODO : Remove all the contact manifold of the overlapping pair from the contact manifolds list of the two bodies involved + // Destroy the overlapping pair + it.remove(); + } + } + // Remove the body from the broad-phase + this.broadPhaseAlgorithm.removeProxyCollisionShape(_proxyShape); + } + + /// Report collision between two sets of shapes + public void reportCollisionBetweenShapes(final CollisionCallback _callback, final Set _shapes1, final Set _shapes2) { + + // For each possible collision pair of bodies + final Iterator> it = this.overlappingPairs.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = it.next(); + final OverlappingPair pair = entry.getValue(); + final ProxyShape shape1 = pair.getShape1(); + final ProxyShape shape2 = pair.getShape2(); + assert (shape1.broadPhaseID != shape2.broadPhaseID); + // If both shapes1 and shapes2 sets are non-empty, we check that + // shape1 is among on set and shape2 is among the other one + if (!_shapes1.isEmpty() && !_shapes2.isEmpty() && (_shapes1.count(shape1.broadPhaseID) == 0 || _shapes2.count(shape2.broadPhaseID) == 0) + && (_shapes1.count(shape2.broadPhaseID) == 0 || _shapes2.count(shape1.broadPhaseID) == 0)) { + continue; + } + if (!_shapes1.isEmpty() && _shapes2.isEmpty() && _shapes1.count(shape1.broadPhaseID) == 0 && _shapes1.count(shape2.broadPhaseID) == 0) { + continue; + } + if (!_shapes2.isEmpty() && _shapes1.isEmpty() && _shapes2.count(shape1.broadPhaseID) == 0 && _shapes2.count(shape2.broadPhaseID) == 0) { + continue; + } + // For each contact manifold set of the overlapping pair + final ContactManifoldSet manifoldSet = pair.getContactManifoldSet(); + for (int j = 0; j < manifoldSet.getNbContactManifolds(); j++) { + final ContactManifold manifold = manifoldSet.getContactManifold(j); + // For each contact manifold of the manifold set + for (int i = 0; i < manifold.getNbContactPoints(); i++) { + final ContactPoint contactPoint = manifold.getContactPoint(i); + // Create the contact info object for the contact + final ContactPointInfo contactInfo = new ContactPointInfo(manifold.getShape1(), manifold.getShape2(), manifold.getShape1().getCollisionShape(), + manifold.getShape2().getCollisionShape(), contactPoint.getNormal(), contactPoint.getPenetrationDepth(), contactPoint.getLocalPointOnBody1(), + contactPoint.getLocalPointOnBody2()); + // Notify the collision callback about this new contact + if (_callback != null) { + _callback.notifyContact(contactInfo); + } + } + + } + } + } + + /// Set the collision dispatch configuration + public void setCollisionDispatch(final CollisionDispatch _collisionDispatch) { + this.collisionDispatch = _collisionDispatch; + this.collisionDispatch.init(this); + // Fill-in the collision matrix with the new algorithms to use + fillInCollisionMatrix(); + } + + /// Test if the AABBs of two proxy shapes overlap + public boolean testAABBOverlap(final ProxyShape _shape1, final ProxyShape _shape2) { + // If one of the shape's body is not active, we return no overlap + if (!_shape1.getBody().isActive() || !_shape2.getBody().isActive()) { + return false; + } + return this.broadPhaseAlgorithm.testOverlappingShapes(_shape1, _shape2); + } + + /// Compute the collision detection + public void testCollisionBetweenShapes(final CollisionCallback _callback, final Set _shapes1, final Set _shapes2) { + // Compute the broad-phase collision detection + computeBroadPhase(); + // Delete all the contact points in the currently overlapping pairs + clearContactPoints(); + // Compute the narrow-phase collision detection among given sets of shapes + computeNarrowPhaseBetweenShapes(_callback, _shapes1, _shapes2); + } + + /// Update a proxy collision shape (that has moved for instance) + public void updateProxyCollisionShape(final ProxyShape _shape, final AABB _aabb) { + updateProxyCollisionShape(_shape, _aabb, new Vector3f(0, 0, 0), false); + } + + public void updateProxyCollisionShape(final ProxyShape _shape, final AABB _aabb, final Vector3f _displacement) { + updateProxyCollisionShape(_shape, _aabb, _displacement, false); + } + + public void updateProxyCollisionShape(final ProxyShape _shape, final AABB _aabb, final Vector3f _displacement, final boolean _forceReinsert) { + this.broadPhaseAlgorithm.updateProxyCollisionShape(_shape, _aabb, _displacement); + } +} diff --git a/src/org/atriasoft/ephysics/collision/CollisionShapeInfo.java b/src/org/atriasoft/ephysics/collision/CollisionShapeInfo.java new file mode 100644 index 0000000..3e4fbe7 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/CollisionShapeInfo.java @@ -0,0 +1,30 @@ +package org.atriasoft.ephysics.collision; + +import org.atriasoft.etk.math.Transform3D; + +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.engine.OverlappingPair; + +/** + * It regroups different things about a collision shape. This is + * used to pass information about a collision shape to a collision algorithm. + */ +public class CollisionShapeInfo { + + public final OverlappingPair overlappingPair; //!< Broadphase overlapping pair + public final ProxyShape proxyShape; //!< Proxy shape + public final CollisionShape collisionShape; //!< Pointer to the collision shape + public final Transform3D shapeToWorldTransform; //!< Transform3D that maps from collision shape local-space to world-space + public final Object cachedCollisionData; //!< Cached collision data of the proxy shape + /// Constructor + + public CollisionShapeInfo(final ProxyShape _proxyCollisionShape, final CollisionShape _shape, final Transform3D _shapeLocalToWorldTransform, final OverlappingPair _pair, + final Object _cachedData) { + this.overlappingPair = _pair; + this.proxyShape = _proxyCollisionShape; + this.collisionShape = _shape; + this.shapeToWorldTransform = _shapeLocalToWorldTransform; + this.cachedCollisionData = _cachedData; + + } +} diff --git a/src/org/atriasoft/ephysics/collision/ContactManifold.java b/src/org/atriasoft/ephysics/collision/ContactManifold.java new file mode 100644 index 0000000..c0525d3 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/ContactManifold.java @@ -0,0 +1,374 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ + +package org.atriasoft.ephysics.collision; + +import org.atriasoft.ephysics.Configuration; +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.constraint.ContactPoint; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents the set of contact points between two bodies. + * The contact manifold is implemented in a way to cache the contact + * points among the frames for better stability following the + * "Contact Generation" presentation of Erwin Coumans at GDC 2010 + * conference (bullet.googlecode.com/files/GDC10_Coumans_Erwin_Contact.pdf). + * Some code of this class is based on the implementation of the + * btPersistentManifold class from Bullet physics engine (www.http://bulletphysics.org). + * The contacts between two bodies are added one after the other in the cache. + * When the cache is full, we have to remove one point. The idea is to keep + * the point with the deepest penetration depth and also to keep the + * points producing the larger area (for a more stable contact manifold). + * The new added point is always kept. + */ +public class ContactManifold { + + //!< Maximum number of contacts in the manifold + public static final int MAX_CONTACT_POINTS_IN_MANIFOLD = 4;; + + private final ProxyShape shape1; //!< Pointer to the first proxy shape of the contact + + private final ProxyShape shape2; //!< Pointer to the second proxy shape of the contact + private final ContactPoint[] contactPoints = new ContactPoint[MAX_CONTACT_POINTS_IN_MANIFOLD]; //!< Contact points in the manifold + private final int normalDirectionId; //!< Normal direction Id (Unique Id representing the normal direction) + private int nbContactPoints; //!< Number of contacts in the cache + private Vector3f frictionVector1 = new Vector3f(); //!< First friction vector of the contact manifold + private Vector3f frictionvec2 = new Vector3f(); //!< Second friction vector of the contact manifold + private float frictionImpulse1; //!< First friction raint accumulated impulse + + private float frictionImpulse2; //!< Second friction raint accumulated impulse + private float frictionTwistImpulse; //!< Twist friction raint accumulated impulse + private Vector3f rollingResistanceImpulse; //!< Accumulated rolling resistance impulse + public boolean isAlreadyInIsland; //!< True if the contact manifold has already been added longo an island + /// Return the index of maximum area + /// Constructor + + public ContactManifold(final ProxyShape _shape1, final ProxyShape _shape2, final int _normalDirectionId) { + this.shape1 = _shape1; + this.shape2 = _shape2; + this.normalDirectionId = _normalDirectionId; + this.nbContactPoints = 0; + this.frictionImpulse1 = 0.0f; + this.frictionImpulse2 = 0.0f; + this.frictionTwistImpulse = 0.0f; + this.isAlreadyInIsland = false; + + } + + /// Add a contact point to the manifold + public void addContactPoint(final ContactPoint contact) { + // For contact already in the manifold + for (int iii = 0; iii < this.nbContactPoints; iii++) { + // Check if the new point point does not correspond to a same contact point + // already in the manifold. + final float distance = (this.contactPoints[iii].getWorldPointOnBody1().lessNew(contact.getWorldPointOnBody1()).length2()); + if (distance <= Configuration.PERSISTENT_CONTACT_DIST_THRESHOLD * Configuration.PERSISTENT_CONTACT_DIST_THRESHOLD) { + assert (this.nbContactPoints > 0); + return; + } + } + // If the contact manifold is full + if (this.nbContactPoints == MAX_CONTACT_POINTS_IN_MANIFOLD) { + final int indexMaxPenetration = getIndexOfDeepestPenetration(contact); + final int indexToRemove = getIndexToRemove(indexMaxPenetration, contact.getLocalPointOnBody1()); + removeContactPoint(indexToRemove); + } + // Add the new contact point in the manifold + this.contactPoints[this.nbContactPoints] = contact; + this.nbContactPoints++; + assert (this.nbContactPoints > 0); + } + + /// Clear the contact manifold + public void clear() { + for (int iii = 0; iii < this.nbContactPoints; ++iii) { + this.contactPoints[iii] = null; + } + this.nbContactPoints = 0; + } + + /// Return the normalized averaged normal vector + public Vector3f getAverageContactNormal() { + if (this.nbContactPoints == 0) { + return new Vector3f(0, 0, 1); + } + final Vector3f averageNormal = new Vector3f(); + for (int iii = 0; iii < this.nbContactPoints; iii++) { + averageNormal.add(this.contactPoints[iii].getNormal()); + } + return averageNormal.safeNormalizeNew(); + } + + /// Return a pointer to the first body of the contact manifold + public CollisionBody getBody1() { + return this.shape1.getBody(); + } + + /// Return a pointer to the second body of the contact manifold + public CollisionBody getBody2() { + return this.shape2.getBody(); + } + + /// Return a contact point of the manifold + public ContactPoint getContactPoint(final int index) { + assert (index < this.nbContactPoints); + return this.contactPoints[index]; + } + + /// Return the first friction accumulated impulse + public float getFrictionImpulse1() { + return this.frictionImpulse1; + } + + /// Return the second friction accumulated impulse + public float getFrictionImpulse2() { + return this.frictionImpulse2; + } + + /// Return the friction twist accumulated impulse + public float getFrictionTwistImpulse() { + return this.frictionTwistImpulse; + } + + /// Return the second friction vector at the center of the contact manifold + public Vector3f getFrictionvec2() { + return this.frictionvec2; + } + + /// Return the first friction vector at the center of the contact manifold + public Vector3f getFrictionVector1() { + return this.frictionVector1; + } + + /** + * Return the index of the contact with the larger penetration depth. + * + * This corresponding contact will be kept in the cache. The method returns -1 is + * the new contact is the deepest. + */ + int getIndexOfDeepestPenetration(final ContactPoint newContact) { + assert (this.nbContactPoints == MAX_CONTACT_POINTS_IN_MANIFOLD); + int indexMaxPenetrationDepth = -1; + float maxPenetrationDepth = newContact.getPenetrationDepth(); + // For each contact in the cache + for (int iii = 0; iii < this.nbContactPoints; iii++) { + // If the current contact has a larger penetration depth + if (this.contactPoints[iii].getPenetrationDepth() > maxPenetrationDepth) { + maxPenetrationDepth = this.contactPoints[iii].getPenetrationDepth(); + indexMaxPenetrationDepth = iii; + } + } + // Return the index of largest penetration depth + return indexMaxPenetrationDepth; + } + + /** + * Return the index that will be removed. + * The index of the contact point with the larger penetration + * depth is given as a parameter. This contact won't be removed. Given this contact, we compute + * the different area and we want to keep the contacts with the largest area. The new point is also + * kept. In order to compute the area of a quadrilateral, we use the formula : + * Area = 0.5 * | AC x BD | where AC and BD form the diagonals of the quadrilateral. Note that + * when we compute this area, we do not calculate it exactly but we + * only estimate it because we do not compute the actual diagonals of the quadrialteral. Therefore, + * this is only a guess that is faster to compute. This idea comes from the Bullet Physics library + * by Erwin Coumans (http://wwww.bulletphysics.org). + */ + int getIndexToRemove(final int indexMaxPenetration, final Vector3f newPoint) { + assert (this.nbContactPoints == MAX_CONTACT_POINTS_IN_MANIFOLD); + float area0 = 0.0f; // Area with contact 1,2,3 and newPoint + float area1 = 0.0f; // Area with contact 0,2,3 and newPoint + float area2 = 0.0f; // Area with contact 0,1,3 and newPoint + float area3 = 0.0f; // Area with contact 0,1,2 and newPoint + if (indexMaxPenetration != 0) { + // Compute the area + final Vector3f vector1 = newPoint.lessNew(this.contactPoints[1].getLocalPointOnBody1()); + final Vector3f vector2 = this.contactPoints[3].getLocalPointOnBody1().lessNew(this.contactPoints[2].getLocalPointOnBody1()); + final Vector3f crossProduct = vector1.cross(vector2); + area0 = crossProduct.length2(); + } + if (indexMaxPenetration != 1) { + // Compute the area + final Vector3f vector1 = newPoint.lessNew(this.contactPoints[0].getLocalPointOnBody1()); + final Vector3f vector2 = this.contactPoints[3].getLocalPointOnBody1().lessNew(this.contactPoints[2].getLocalPointOnBody1()); + final Vector3f crossProduct = vector1.cross(vector2); + area1 = crossProduct.length2(); + } + if (indexMaxPenetration != 2) { + // Compute the area + final Vector3f vector1 = newPoint.lessNew(this.contactPoints[0].getLocalPointOnBody1()); + final Vector3f vector2 = this.contactPoints[3].getLocalPointOnBody1().lessNew(this.contactPoints[1].getLocalPointOnBody1()); + final Vector3f crossProduct = vector1.cross(vector2); + area2 = crossProduct.length2(); + } + if (indexMaxPenetration != 3) { + // Compute the area + final Vector3f vector1 = newPoint.lessNew(this.contactPoints[0].getLocalPointOnBody1()); + final Vector3f vector2 = this.contactPoints[2].getLocalPointOnBody1().lessNew(this.contactPoints[1].getLocalPointOnBody1()); + final Vector3f crossProduct = vector1.cross(vector2); + area3 = crossProduct.length2(); + } + // Return the index of the contact to remove + return getMaxArea(area0, area1, area2, area3); + } + + /// Return the largest depth of all the contact points + public float getLargestContactDepth() { + float largestDepth = 0.0f; + for (int iii = 0; iii < this.nbContactPoints; iii++) { + final float depth = this.contactPoints[iii].getPenetrationDepth(); + if (depth > largestDepth) { + largestDepth = depth; + } + } + return largestDepth; + } + + private int getMaxArea(final float area0, final float area1, final float area2, final float area3) { + if (area0 < area1) { + if (area1 < area2) { + if (area2 < area3) { + return 3; + } else { + return 2; + } + } else if (area1 < area3) { + return 3; + } else { + return 1; + } + } else if (area0 < area2) { + if (area2 < area3) { + return 3; + } else { + return 2; + } + } else if (area0 < area3) { + return 3; + } else { + return 0; + } + } + + /// Return the number of contact points in the manifold + public int getNbContactPoints() { + return this.nbContactPoints; + } + + /// Return the normal direction Id + public int getNormalDirectionId() { + return this.normalDirectionId; + } + + public Vector3f getRollingResistanceImpulse() { + return this.rollingResistanceImpulse; + } + + /// Return a pointer to the first proxy shape of the contact + public ProxyShape getShape1() { + return this.shape1; + } + + /// Return a pointer to the second proxy shape of the contact + public ProxyShape getShape2() { + return this.shape2; + } + + /// Return true if the contact manifold has already been added longo an island + public boolean isAlreadyInIsland() { + return this.isAlreadyInIsland; + } + + /// Remove a contact point from the manifold + public void removeContactPoint(final int index) { + assert (index < this.nbContactPoints); + assert (this.nbContactPoints > 0); + this.contactPoints[index] = null; + // If we don't remove the last index + if (index < this.nbContactPoints - 1) { + this.contactPoints[index] = this.contactPoints[this.nbContactPoints - 1]; + } + this.nbContactPoints--; + } + + /// Set the first friction accumulated impulse + public void setFrictionImpulse1(final float frictionImpulse1) { + this.frictionImpulse1 = frictionImpulse1; + } + + /// Set the second friction accumulated impulse + public void setFrictionImpulse2(final float frictionImpulse2) { + this.frictionImpulse2 = frictionImpulse2; + } + + /// Set the friction twist accumulated impulse + public void setFrictionTwistImpulse(final float frictionTwistImpulse) { + this.frictionTwistImpulse = frictionTwistImpulse; + } + + /// set the second friction vector at the center of the contact manifold + public void setFrictionvec2(final Vector3f frictionvec2) { + this.frictionvec2 = frictionvec2; + } + + /// set the first friction vector at the center of the contact manifold + public void setFrictionVector1(final Vector3f frictionVector1) { + this.frictionVector1 = frictionVector1; + } + + /// Set the accumulated rolling resistance impulse + public void setRollingResistanceImpulse(final Vector3f rollingResistanceImpulse) { + this.rollingResistanceImpulse = rollingResistanceImpulse; + } + + /** + * Update the contact manifold. + * + * First the world space coordinates of the current contacts in the manifold are recomputed from + * the corresponding transforms of the bodies because they have moved. Then we remove the contacts + * with a negative penetration depth (meaning that the bodies are not penetrating anymore) and also + * the contacts with a too large distance between the contact points in the plane orthogonal to the + * contact normal. + */ + public void update(final Transform3D transform1, final Transform3D transform2) { + if (this.nbContactPoints == 0) { + return; + } + // Update the world coordinates and penetration depth of the contact points in the manifold + for (int iii = 0; iii < this.nbContactPoints; iii++) { + this.contactPoints[iii].setWorldPointOnBody1(transform1.multiplyNew(this.contactPoints[iii].getLocalPointOnBody1())); + this.contactPoints[iii].setWorldPointOnBody2(transform2.multiplyNew(this.contactPoints[iii].getLocalPointOnBody2())); + this.contactPoints[iii] + .setPenetrationDepth((this.contactPoints[iii].getWorldPointOnBody1().lessNew(this.contactPoints[iii].getWorldPointOnBody2())).dot(this.contactPoints[iii].getNormal())); + } + final float squarePersistentContactThreshold = Configuration.PERSISTENT_CONTACT_DIST_THRESHOLD * Configuration.PERSISTENT_CONTACT_DIST_THRESHOLD; + // Remove the contact points that don't represent very well the contact manifold + for (int iii = (this.nbContactPoints) - 1; iii >= 0; iii--) { + assert (iii < (this.nbContactPoints)); + // Compute the distance between contact points in the normal direction + final float distanceNormal = -this.contactPoints[iii].getPenetrationDepth(); + // If the contacts points are too far from each other in the normal direction + if (distanceNormal > squarePersistentContactThreshold) { + removeContactPoint(iii); + } else { + // Compute the distance of the two contact points in the plane + // orthogonal to the contact normal + final Vector3f projOfPoint1 = this.contactPoints[iii].getNormal().multiplyNew(distanceNormal).add(this.contactPoints[iii].getWorldPointOnBody1()); + final Vector3f projDifference = this.contactPoints[iii].getWorldPointOnBody2().lessNew(projOfPoint1); + // If the orthogonal distance is larger than the valid distance + // threshold, we remove the contact + if (projDifference.length2() > squarePersistentContactThreshold) { + removeContactPoint(iii); + } + } + } + } +} diff --git a/src/org/atriasoft/ephysics/collision/ContactManifoldListElement.java b/src/org/atriasoft/ephysics/collision/ContactManifoldListElement.java new file mode 100644 index 0000000..022ee70 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/ContactManifoldListElement.java @@ -0,0 +1,14 @@ +package org.atriasoft.ephysics.collision; + +/** + * This structure represents a single element of a linked list of contact manifolds + */ +public class ContactManifoldListElement { + public ContactManifold contactManifold; //!< Pointer to the actual contact manifold + public ContactManifoldListElement next; //!< Next element of the list + + public ContactManifoldListElement(final ContactManifold _initContactManifold, final ContactManifoldListElement _initNext) { + this.contactManifold = _initContactManifold; + this.next = _initNext; + } +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/ContactManifoldSet.java b/src/org/atriasoft/ephysics/collision/ContactManifoldSet.java new file mode 100644 index 0000000..7c3c0b5 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/ContactManifoldSet.java @@ -0,0 +1,217 @@ +package org.atriasoft.ephysics.collision; + +import org.atriasoft.ephysics.constraint.ContactPoint; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a set of one or several contact manifolds. Typically a + * convex/convex collision will have a set with a single manifold and a convex-concave + * collision can have more than one manifolds. Note that a contact manifold can + * contains several contact points. + */ +public class ContactManifoldSet { + + private static final int MAX_MANIFOLDS_IN_CONTACT_MANIFOLD_SET = 3; // Maximum number of contact manifolds in the set + private static final int CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS = 3; // N Number for the N x N subdivisions of the cubemap + + private final int nbMaxManifolds; //!< Maximum number of contact manifolds in the set + private int nbManifolds; //!< Current number of contact manifolds in the set + private final ProxyShape shape1; //!< Pointer to the first proxy shape of the contact + private final ProxyShape shape2; //!< Pointer to the second proxy shape of the contact + + private final ContactManifold[] manifolds = new ContactManifold[MAX_MANIFOLDS_IN_CONTACT_MANIFOLD_SET]; //!< Contact manifolds of the set + /// Create a new contact manifold and add it to the set + + /// Constructor + public ContactManifoldSet(final ProxyShape _shape1, final ProxyShape _shape2, final int _nbMaxManifolds) { + this.nbMaxManifolds = _nbMaxManifolds; + this.nbManifolds = 0; + this.shape1 = _shape1; + this.shape2 = _shape2; + assert (_nbMaxManifolds >= 1); + } + + /// Add a contact point to the manifold set + public void addContactPoint(final ContactPoint contact) { + // Compute an Id corresponding to the normal direction (using a cubemap) + final int normalDirectionId = computeCubemapNormalId(contact.getNormal()); + // If there is no contact manifold yet + if (this.nbManifolds == 0) { + createManifold(normalDirectionId); + this.manifolds[0].addContactPoint(contact); + assert (this.manifolds[this.nbManifolds - 1].getNbContactPoints() > 0); + for (int iii = 0; iii < this.nbManifolds; iii++) { + assert (this.manifolds[iii].getNbContactPoints() > 0); + } + return; + } + // Select the manifold with the most similar normal (if exists) + int similarManifoldIndex = 0; + if (this.nbMaxManifolds > 1) { + similarManifoldIndex = selectManifoldWithSimilarNormal(normalDirectionId); + } + // If a similar manifold has been found + if (similarManifoldIndex != -1) { + // Add the contact point to that similar manifold + this.manifolds[similarManifoldIndex].addContactPoint(contact); + assert (this.manifolds[similarManifoldIndex].getNbContactPoints() > 0); + return; + } + // If the maximum number of manifold has not been reached yet + if (this.nbManifolds < this.nbMaxManifolds) { + // Create a new manifold for the contact point + createManifold(normalDirectionId); + this.manifolds[this.nbManifolds - 1].addContactPoint(contact); + for (int iii = 0; iii < this.nbManifolds; iii++) { + assert (this.manifolds[iii].getNbContactPoints() > 0); + } + return; + } + // The contact point will be in a new contact manifold, we now have too much + // manifolds condidates. We need to remove one. We choose to keep the manifolds + // with the largest contact depth among their points + int smallestDepthIndex = -1; + float minDepth = contact.getPenetrationDepth(); + assert (this.nbManifolds == this.nbMaxManifolds); + for (int iii = 0; iii < this.nbManifolds; iii++) { + final float depth = this.manifolds[iii].getLargestContactDepth(); + if (depth < minDepth) { + minDepth = depth; + smallestDepthIndex = iii; + } + } + // If we do not want to keep to new manifold (not created yet) with the + // new contact point + if (smallestDepthIndex == -1) { + return; + } + assert (smallestDepthIndex >= 0 && smallestDepthIndex < this.nbManifolds); + // Here we need to replace an existing manifold with a new one (that contains + // the new contact point) + removeManifold(smallestDepthIndex); + createManifold(normalDirectionId); + this.manifolds[this.nbManifolds - 1].addContactPoint(contact); + assert (this.manifolds[this.nbManifolds - 1].getNbContactPoints() > 0); + for (int iii = 0; iii < this.nbManifolds; iii++) { + assert (this.manifolds[iii].getNbContactPoints() > 0); + } + return; + } + + /// Clear the contact manifold set + public void clear() { + for (int iii = this.nbManifolds - 1; iii >= 0; iii--) { + removeManifold(iii); + } + assert (this.nbManifolds == 0); + } + + // Map the normal vector longo a cubemap face bucket (a face contains 4x4 buckets) + // Each face of the cube is divided longo 4x4 buckets. This method maps the + // normal vector longo of the of the bucket and returns a unique Id for the bucket + private int computeCubemapNormalId(final Vector3f normal) { + assert (normal.length2() > Constant.FLOAT_EPSILON); + int faceNo; + float u, v; + final float max = FMath.max(FMath.abs(normal.x), FMath.abs(normal.y), FMath.abs(normal.z)); + final Vector3f normalScaled = normal.divideNew(max); + if (normalScaled.x >= normalScaled.y && normalScaled.x >= normalScaled.z) { + faceNo = normalScaled.x > 0 ? 0 : 1; + u = normalScaled.y; + v = normalScaled.z; + } else if (normalScaled.y >= normalScaled.x && normalScaled.y >= normalScaled.z) { + faceNo = normalScaled.y > 0 ? 2 : 3; + u = normalScaled.x; + v = normalScaled.z; + } else { + faceNo = normalScaled.z > 0 ? 4 : 5; + u = normalScaled.x; + v = normalScaled.y; + } + int indexU = FMath.floor(((u + 1) / 2) * CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS); + int indexV = FMath.floor(((v + 1) / 2) * CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS); + if (indexU == CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS) { + indexU--; + } + if (indexV == CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS) { + indexV--; + } + final int nbSubDivInFace = CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS * CONTACT_CUBEMAP_FACE_NB_SUBDIVISIONS; + return faceNo * 200 + indexU * nbSubDivInFace + indexV; + } + + private void createManifold(final int normalDirectionId) { + assert (this.nbManifolds < this.nbMaxManifolds); + this.manifolds[this.nbManifolds] = new ContactManifold(this.shape1, this.shape2, normalDirectionId); + this.nbManifolds++; + } + + /// Return a given contact manifold + public ContactManifold getContactManifold(final int index) { + assert (index >= 0 && index < this.nbManifolds); + return this.manifolds[index]; + } + + /// Return the number of manifolds in the set + public long getNbContactManifolds() { + return this.nbManifolds; + } + + /// Return the first proxy shape + public ProxyShape getShape1() { + return this.shape1; + } + + /// Return the second proxy shape + public ProxyShape getShape2() { + return this.shape2; + } + + /// Return the total number of contact points in the set of manifolds + public int getTotalNbContactPoints() { + int nbPoints = 0; + for (int iii = 0; iii < this.nbManifolds; iii++) { + nbPoints += this.manifolds[iii].getNbContactPoints(); + } + return nbPoints; + } + + /// Remove a contact manifold from the set + private void removeManifold(final int index) { + assert (this.nbManifolds > 0); + assert (index >= 0 && index < this.nbManifolds); + // Delete the new contact + this.manifolds[index] = null; + for (int iii = index; (iii + 1) < this.nbManifolds; iii++) { + this.manifolds[iii] = this.manifolds[iii + 1]; + } + this.nbManifolds--; + } + + // Return the index of the contact manifold with a similar average normal. + private int selectManifoldWithSimilarNormal(final int normalDirectionId) { + // Return the Id of the manifold with the same normal direction id (if exists) + for (int iii = 0; iii < this.nbManifolds; iii++) { + if (normalDirectionId == this.manifolds[iii].getNormalDirectionId()) { + return iii; + } + } + return -1; + } + + /// Update the contact manifolds + public void update() { + for (int iii = this.nbManifolds - 1; iii >= 0; iii--) { + // Update the contact manifold + this.manifolds[iii].update(this.shape1.getBody().getTransform().multiplyNew(this.shape1.getLocalToBodyTransform()), + this.shape2.getBody().getTransform().multiplyNew(this.shape2.getLocalToBodyTransform())); + // Remove the contact manifold if has no contact points anymore + if (this.manifolds[iii].getNbContactPoints() == 0) { + removeManifold(iii); + } + } + } + +} diff --git a/src/org/atriasoft/ephysics/collision/ProxyShape.java b/src/org/atriasoft/ephysics/collision/ProxyShape.java new file mode 100644 index 0000000..6464cc4 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/ProxyShape.java @@ -0,0 +1,201 @@ +package org.atriasoft.ephysics.collision; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.collision.broadphase.DTree; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +public class ProxyShape { + protected CollisionBody body; //!< Pointer to the parent body + protected CollisionShape collisionShape; //!< Internal collision shape + public Transform3D localToBodyTransform; //!< Local-space to parent body-space transform (does not change over time) + protected float mass; //!< Mass (in kilogramms) of the corresponding collision shape + public ProxyShape next = null; //!< Pointer to the next proxy shape of the body (linked list) + public DTree broadPhaseID = null; //!< Broad-phase ID (node ID in the dynamic AABB tree) + + protected Object cachedCollisionData = null; //!< Cached collision data + + protected Object userData = null; //!< Pointer to user data + + /** + * @brief Bits used to define the collision category of this shape. + * You can set a single bit to one to define a category value for this + * shape. This value is one (0x0001) by default. This variable can be used + * together with the this.collideWithMaskBits variable so that given + * categories of shapes collide with each other and do not collide with + * other categories. + */ + protected int collisionCategoryBits = 0x0001; + /** + * @brief Bits mask used to state which collision categories this shape can + * collide with. This value is 0xFFFF by default. It means that this + * proxy shape will collide with every collision categories by default. + */ + protected int collideWithMaskBits = 0xFFFF; + + /** + * @param body Pointer to the parent body + * @param shape Pointer to the collision shape + * @param transform Transform3Dation from collision shape local-space to body local-space + * @param mass Mass of the collision shape (in kilograms) + */ + public ProxyShape(final CollisionBody body, final CollisionShape shape, final Transform3D transform, final float mass) { + this.body = body; + this.collisionShape = shape; + this.localToBodyTransform = transform; + this.mass = mass; + } + + /** + * @return Pointer to the parent body + */ + public CollisionBody getBody() { + return this.body; + } + + public DTree getBroadPhaseID() { + return this.broadPhaseID; + } + + /// Return the pointer to the cached collision data + public Object getCachedCollisionData() { + return this.cachedCollisionData; + } + + /// Return the collision bits mask + public int getCollideWithMaskBits() { + return this.collideWithMaskBits; + } + + /// Return the collision category bits + public int getCollisionCategoryBits() { + return this.collisionCategoryBits; + } + + /// Return the collision shape + public CollisionShape getCollisionShape() { + return this.collisionShape; + } + + /// Return the local scaling vector of the collision shape + public Vector3f getLocalScaling() { + return this.collisionShape.getScaling(); + } + + /** + * Return the local to parent body transform + * @return The transformation that transforms the local-space of the collision shape + * to the local-space of the parent body + */ + public Transform3D getLocalToBodyTransform() { + return this.localToBodyTransform; + } + + /// Return the local to world transform + public Transform3D getLocalToWorldTransform() { + return this.body.getTransform().multiplyNew(this.localToBodyTransform); + } + + /// Return the mass of the collision shape + public float getMass() { + return this.mass; + } + + /// Return the next proxy shape in the linked list of proxy shapes + public ProxyShape getNext() { + return this.next; + } + + /// Return a pointer to the user data attached to this body + public Object getUserData() { + return this.userData; + } + + /** + * @param ray Ray to use for the raycasting + * @param[out] raycastInfo Result of the raycasting that is valid only if the + * methods returned true + * @return True if the ray hit the collision shape + */ + public boolean raycast(final Ray ray, final RaycastInfo raycastInfo) { + + // If the corresponding body is not active, it cannot be hit by rays + if (!this.body.isActive()) { + return false; + } + + // Convert the ray longo the local-space of the collision shape + final Transform3D localToWorldTransform = getLocalToWorldTransform(); + final Transform3D worldToLocalTransform = localToWorldTransform.inverseNew(); + + final Ray rayLocal = new Ray(worldToLocalTransform.multiplyNew(ray.point1), worldToLocalTransform.multiplyNew(ray.point2), ray.maxFraction); + + final boolean isHit = this.collisionShape.raycast(rayLocal, raycastInfo, this); + if (isHit == true) { + // Convert the raycast info longo world-space + raycastInfo.worldPoint = localToWorldTransform.multiplyNew(raycastInfo.worldPoint); + raycastInfo.worldNormal = localToWorldTransform.getOrientation().multiply(raycastInfo.worldNormal); + raycastInfo.worldNormal.normalize(); + } + return isHit; + } + + public void setBroadPhaseID(final DTree broadPhase) { + this.broadPhaseID = broadPhase; + } + + /// Set the collision bits mask + public void setCollideWithMaskBits(final int collideWithMaskBits) { + this.collideWithMaskBits = collideWithMaskBits; + } + + /// Set the collision category bits + public void setCollisionCategoryBits(final int collisionCategoryBits) { + this.collisionCategoryBits = collisionCategoryBits; + } + + /** + * Set the local scaling vector of the collision shape + * @param scaling The new local scaling vector + */ + public void setLocalScaling(final Vector3f scaling) { + + // Set the local scaling of the collision shape + this.collisionShape.setLocalScaling(scaling); + + this.body.setIsSleeping(false); + + // Notify the body that the proxy shape has to be updated in the broad-phase + this.body.updateProxyShapeInBroadPhase(this, true); + } + + /// Set the local to parent body transform + public void setLocalToBodyTransform(final Transform3D transform) { + + this.localToBodyTransform = transform; + + this.body.setIsSleeping(false); + + // Notify the body that the proxy shape has to be updated in the broad-phase + this.body.updateProxyShapeInBroadPhase(this, true); + } + + /// Attach user data to this body + public void setUserData(final Object userData) { + this.userData = userData; + } + + /** + * @param worldPoint Point to test in world-space coordinates + * @return True if the point is inside the collision shape + */ + public boolean testPointInside(final Vector3f worldPoint) { + final Transform3D localToWorld = this.body.getTransform().multiplyNew(this.localToBodyTransform); + final Vector3f localPoint = localToWorld.inverseNew().multiply(worldPoint); + return this.collisionShape.testPointInside(localPoint, this); + } + +} diff --git a/src/org/atriasoft/ephysics/collision/TestCollisionBetweenShapesCallback.java b/src/org/atriasoft/ephysics/collision/TestCollisionBetweenShapesCallback.java new file mode 100644 index 0000000..e5b8873 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/TestCollisionBetweenShapesCallback.java @@ -0,0 +1,22 @@ +package org.atriasoft.ephysics.collision; + +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseCallback; +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.ephysics.engine.CollisionCallback; +import org.atriasoft.ephysics.engine.OverlappingPair; + +public class TestCollisionBetweenShapesCallback implements NarrowPhaseCallback { + + private final CollisionCallback collisionCallback; + + // Constructor + public TestCollisionBetweenShapesCallback(final CollisionCallback _callback) { + this.collisionCallback = _callback; + + } + + // Called by a narrow-phase collision algorithm when a new contact has been found + public void notifyContact(final OverlappingPair _overlappingPair, final ContactPointInfo _contactInfo) { + this.collisionCallback.notifyContact(_contactInfo); + } +} diff --git a/src/org/atriasoft/ephysics/collision/Triangle.java b/src/org/atriasoft/ephysics/collision/Triangle.java new file mode 100644 index 0000000..3d6a928 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/Triangle.java @@ -0,0 +1,12 @@ +package org.atriasoft.ephysics.collision; + +import org.atriasoft.etk.math.Vector3f; + +public class Triangle { + public final Vector3f[] value = new Vector3f[3]; + + public Vector3f get(final int _id) { + return this.value[_id]; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/TriangleMesh.java b/src/org/atriasoft/ephysics/collision/TriangleMesh.java new file mode 100644 index 0000000..0de2d3c --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/TriangleMesh.java @@ -0,0 +1,36 @@ +package org.atriasoft.ephysics.collision; + +import java.util.ArrayList; +import java.util.List; + +public class TriangleMesh { + /// All the triangle arrays of the mesh (one triangle array per part) + protected List triangleArrays = new ArrayList<>(); + + /** + * Constructor + */ + public TriangleMesh() {} + + /** + * Add a subpart of the mesh + */ + public void addSubpart(final TriangleVertexArray _triangleVertexArray) { + this.triangleArrays.add(_triangleVertexArray); + } + + /** + * Get the number of subparts of the mesh + */ + public int getNbSubparts() { + return this.triangleArrays.size(); + } + + /** + * Get a pointer to a given subpart (triangle vertex array) of the mesh + */ + public TriangleVertexArray getSubpart(final int _indexSubpart) { + assert (_indexSubpart < this.triangleArrays.size()); + return this.triangleArrays.get(_indexSubpart); + } +} diff --git a/src/org/atriasoft/ephysics/collision/TriangleVertexArray.java b/src/org/atriasoft/ephysics/collision/TriangleVertexArray.java new file mode 100644 index 0000000..2c2c03f --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/TriangleVertexArray.java @@ -0,0 +1,84 @@ +package org.atriasoft.ephysics.collision; + +import java.util.List; + +import org.atriasoft.etk.math.Vector3f; + +/** + * This class is used to describe the vertices and faces of a triangular mesh. + * A TriangleVertexArray represents a continuous array of vertices and indexes + * of a triangular mesh. When you create a TriangleVertexArray, no data is copied + * longo the array. It only stores pointer to the data. The purpose is to allow + * the user to share vertices data between the physics engine and the rendering + * part. Therefore, make sure that the data pointed by a TriangleVertexArray + * remains valid during the TriangleVertexArray life. + */ +public class TriangleVertexArray { + /// Vertice list + protected final Vector3f[] vertices; + /// List of triangle (3 pos for each triangle) + protected final int[] triangles; + + public TriangleVertexArray(final List _vertices, final List _triangles) { + this.vertices = _vertices.toArray(new Vector3f[0]); + + this.triangles = new int[_triangles.size()]; + for (int iii = 0; iii < this.triangles.length; iii++) { + this.triangles[iii] = _triangles.get(iii); + } + } + + /** + * Constructor + * @param _vertices List Of all vertices + * @param _triangles List of all linked points + */ + public TriangleVertexArray(final Vector3f[] _vertices, final int[] _triangles) { + this.vertices = _vertices; + this.triangles = _triangles; + } + + /** + * Get The table of the triangle indice + * @return reference on the triangle indice + */ + public int[] getIndices() { + return this.triangles; + } + + /** + * Get the number of triangle + * @return Number of triangles + */ + public int getNbTriangles() { + return this.triangles.length / 3; + } + + /** + * Get the number of vertices + * @return Number of vertices + */ + public int getNbVertices() { + return this.vertices.length; + } + + /** + * Get a triangle at the specific ID + * @return Buffer of 3 points + */ + public Triangle getTriangle(final int _id) { + final Triangle out = new Triangle(); + out.value[0] = this.vertices[this.triangles[_id * 3]]; + out.value[1] = this.vertices[this.triangles[_id * 3 + 1]]; + out.value[2] = this.vertices[this.triangles[_id * 3 + 2]]; + return out; + } + + /** + * Get The table of the vertices + * @return reference on the vertices + */ + public Vector3f[] getVertices() { + return this.vertices; + } +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.java b/src/org/atriasoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.java new file mode 100644 index 0000000..c4a76d8 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.java @@ -0,0 +1,256 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.collision.broadphase; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.atriasoft.ephysics.Configuration; +import org.atriasoft.ephysics.RaycastTest; +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Vector3f; + +/** + * It represents the broad-phase collision detection. The + * goal of the broad-phase collision detection is to compute the pairs of proxy shapes + * that have their AABBs overlapping. Only those pairs of bodies will be tested + * later for collision during the narrow-phase collision detection. A dynamic AABB + * tree data structure is used for fast broad-phase collision detection. + */ +public class BroadPhaseAlgorithm { + /** + * Callback called when the AABB of a leaf node is hit by a ray the + * broad-phase Dynamic AABB Tree. + */ + public class BroadPhaseRaycastCallback implements CallbackRaycast { + private final DynamicAABBTree dynamicAABBTree; + private final int raycastWithCategoryMaskBits; + private final RaycastTest raycastTest; + + // Constructor + public BroadPhaseRaycastCallback(final DynamicAABBTree _dynamicAABBTree, final int _raycastWithCategoryMaskBits, final RaycastTest _raycastTest) { + this.dynamicAABBTree = _dynamicAABBTree; + this.raycastWithCategoryMaskBits = _raycastWithCategoryMaskBits; + this.raycastTest = _raycastTest; + + } + + @Override + public float callback(final DTree _node, final Ray _ray) { + float hitFraction = -1.0f; + // Get the proxy shape from the node + final ProxyShape proxyShape = (ProxyShape) this.dynamicAABBTree.getNodeDataPointer(_node); + // Check if the raycast filtering mask allows raycast against this shape + if ((this.raycastWithCategoryMaskBits & proxyShape.getCollisionCategoryBits()) != 0) { + // Ask the collision detection to perform a ray cast test against + // the proxy shape of this node because the ray is overlapping + // with the shape in the broad-phase + hitFraction = this.raycastTest.raycastAgainstShape(proxyShape, _ray); + } + return hitFraction; + } + }; + + /// A tree of data that is separated by a specific distance in AABB model. + protected DynamicAABBTree dynamicAABBTree; + /** + * Array with the broad-phase IDs of all collision shapes that have moved (or have been created) during the last simulation step. + * Those are the shapes that need to be tested for overlapping in the next simulation step. ==> and re-index in the overlapping tree + */ + protected List movedShapes = new ArrayList<>(); + /** + * Temporary array of potential overlapping pairs (with potential duplicates) + */ + protected List potentialPairs = new ArrayList<>(); + /** + * Reference to the collision detection object + */ + protected CollisionDetection collisionDetection; // TODO revoked with generic interface for the callback... + + public BroadPhaseAlgorithm(final CollisionDetection _collisionDetection) { + this.dynamicAABBTree = new DynamicAABBTree(Configuration.DYNAMIC_TREE_AABB_GAP); + this.collisionDetection = _collisionDetection; + } + + /// Add a collision shape in the array of shapes that have moved in the last simulation step + /// and that need to be tested again for broad-phase overlapping. + public void addMovedCollisionShape(final DTree _broadPhaseID) { + //Log.info(" ** Element that has moved ... = " + _broadPhaseID); + this.movedShapes.add(_broadPhaseID); + } + + /// Add a proxy collision shape longo the broad-phase collision detection + public void addProxyCollisionShape(final ProxyShape _proxyShape, final AABB _aabb) { + // Add the collision shape longo the dynamic AABB tree and get its broad-phase ID + final DTree nodeId = this.dynamicAABBTree.addObject(_aabb, _proxyShape); + // Set the broad-phase ID of the proxy shape + _proxyShape.setBroadPhaseID(nodeId); + // Add the collision shape longo the array of bodies that have moved (or have been created) + // during the last simulation step + addMovedCollisionShape(_proxyShape.getBroadPhaseID()); + } + + /// Compute all the overlapping pairs of collision shapes + public void computeOverlappingPairs() { + this.potentialPairs.clear(); + // For all collision shapes that have moved (or have been created) during the + // last simulation step + //Log.info("moved shape = " + this.movedShapes.size()); + /* + for (final DTree it : this.movedShapes) { + Log.info(" - " + it); + } + */ + for (final DTree it : this.movedShapes) { + // Get the AABB of the shape + final AABB shapeAABB = this.dynamicAABBTree.getFatAABB(it); + final BroadPhaseAlgorithm self = this; + // Ask the dynamic AABB tree to report all collision shapes that overlap with + // this AABB. The method BroadPhase::notifiyOverlappingPair() will be called + // by the dynamic AABB tree for each potential overlapping pair. + this.dynamicAABBTree.reportAllShapesOverlappingWithAABB(shapeAABB, new CallbackOverlapping() { + @Override + public void callback(final DTree _nodeId) { + // TODO Auto-generated method stub// If both the nodes are the same, we do not create store the overlapping pair + if (it == _nodeId) { + return; + } + // Add the new potential pair longo the array of potential overlapping pairs + self.potentialPairs.add(new PairDTree(it, _nodeId)); + } + }); + } + //Log.print("Find potential pair : " + this.potentialPairs.size()); + // Reset the array of collision shapes that have move (or have been created) during the last simulation step + this.movedShapes.clear(); + // Sort the array of potential overlapping pairs in order to remove duplicate pairs + this.potentialPairs.sort(new Comparator() { + @Override + public int compare(final PairDTree _pair1, final PairDTree _pair2) { + if (_pair1.first.uid < _pair2.first.uid) { + return -1; + } + if (_pair1.first.uid == _pair2.first.uid) { + if (_pair1.second.uid < _pair2.second.uid) { + return -1; + } + if (_pair1.second.uid == _pair2.second.uid) { + return 0; + } + return +1; + } + return +1; + } + }); + // Check all the potential overlapping pairs avoiding duplicates to report unique + // overlapping pairs + int iii = 0; + while (iii < this.potentialPairs.size()) { + // Get a potential overlapping pair + final PairDTree pair = this.potentialPairs.get(iii); + ++iii; + // Get the two collision shapes of the pair + final ProxyShape shape1 = (ProxyShape) (this.dynamicAABBTree.getNodeDataPointer(pair.first)); + final ProxyShape shape2 = (ProxyShape) (this.dynamicAABBTree.getNodeDataPointer(pair.second)); + // Notify the collision detection about the overlapping pair + this.collisionDetection.broadPhaseNotifyOverlappingPair(shape1, shape2); + // Skip the duplicate overlapping pairs + while (iii < this.potentialPairs.size()) { + // Get the next pair + final PairDTree nextPair = this.potentialPairs.get(iii); + // If the next pair is different from the previous one, we stop skipping pairs + // TODO check if it work without uid ... + if (nextPair.first.uid != pair.first.uid || nextPair.second.uid != pair.second.uid) { + break; + } + ++iii; + } + } + } + + /// Ray casting method + public void raycast(final Ray _ray, final RaycastTest _raycastTest, final int _raycastWithCategoryMaskBits) { + final BroadPhaseRaycastCallback broadPhaseRaycastCallback = new BroadPhaseRaycastCallback(this.dynamicAABBTree, _raycastWithCategoryMaskBits, _raycastTest); + this.dynamicAABBTree.raycast(_ray, broadPhaseRaycastCallback); + } + + /// Remove a collision shape from the array of shapes that have moved in the last simulation + /// step and that need to be tested again for broad-phase overlapping. + public void removeMovedCollisionShape(final DTree _broadPhaseID) { + final Iterator it = this.movedShapes.iterator(); + while (it.hasNext()) { + final DTree elem = it.next(); + if (elem == _broadPhaseID) { + it.remove(); + } + } + } + + /// Remove a proxy collision shape from the broad-phase collision detection + public void removeProxyCollisionShape(final ProxyShape _proxyShape) { + final DTree broadPhaseID = _proxyShape.getBroadPhaseID(); + // Remove the collision shape from the dynamic AABB tree + this.dynamicAABBTree.removeObject(broadPhaseID); + // Remove the collision shape longo the array of shapes that have moved (or have been created) + // during the last simulation step + removeMovedCollisionShape(broadPhaseID); + } + + /// Return true if the two broad-phase collision shapes are overlapping + public boolean testOverlappingShapes(final ProxyShape _shape1, final ProxyShape _shape2) { + if (_shape1 == _shape2) { + return false; + } + // Get the two AABBs of the collision shapes + final AABB aabb1 = this.dynamicAABBTree.getFatAABB(_shape1.getBroadPhaseID()); + final AABB aabb2 = this.dynamicAABBTree.getFatAABB(_shape2.getBroadPhaseID()); + // Check if the two AABBs are overlapping + return aabb1.testCollision(aabb2); + } + + /// Notify the broad-phase that a collision shape has moved and need to be updated + public void updateProxyCollisionShape(final ProxyShape _proxyShape, final AABB _aabb, final Vector3f _displacement) { + updateProxyCollisionShape(_proxyShape, _aabb, _displacement, false); + } + + public void updateProxyCollisionShape(final ProxyShape _proxyShape, final AABB _aabb, final Vector3f _displacement, final boolean _forceReinsert) { + final DTree broadPhaseID = _proxyShape.getBroadPhaseID(); + // Update the dynamic AABB tree according to the movement of the collision shape + final boolean hasBeenReInserted = this.dynamicAABBTree.updateObject(broadPhaseID, _aabb, _displacement, _forceReinsert); + // If the collision shape has moved out of its fat AABB (and therefore has been reinserted + //Log.error(" ==> hasBeenReInserted = " + hasBeenReInserted); + // longo the tree). + if (hasBeenReInserted) { + // Add the collision shape longo the array of shapes that have moved (or have been created) + // during the last simulation step + addMovedCollisionShape(broadPhaseID); + } + } +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/CallbackOverlapping.java b/src/org/atriasoft/ephysics/collision/broadphase/CallbackOverlapping.java new file mode 100644 index 0000000..ea63903 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/CallbackOverlapping.java @@ -0,0 +1,5 @@ +package org.atriasoft.ephysics.collision.broadphase; + +public interface CallbackOverlapping { + public void callback(DTree _nodeId); +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/CallbackRaycast.java b/src/org/atriasoft/ephysics/collision/broadphase/CallbackRaycast.java new file mode 100644 index 0000000..6429992 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/CallbackRaycast.java @@ -0,0 +1,7 @@ +package org.atriasoft.ephysics.collision.broadphase; + +import org.atriasoft.ephysics.mathematics.Ray; + +public interface CallbackRaycast { + public float callback(DTree _node, Ray _ray); +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/DTree.java b/src/org/atriasoft/ephysics/collision/broadphase/DTree.java new file mode 100644 index 0000000..60e3ad2 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/DTree.java @@ -0,0 +1,28 @@ +package org.atriasoft.ephysics.collision.broadphase; + +import java.lang.ref.WeakReference; + +import org.atriasoft.ephysics.collision.shapes.AABB; + +/** + * It represents a node of the dynamic AABB tree. + */ +public class DTree { + private static int simpleCounter = 0; + public final int uid; + /** + * A node is either in the tree (has a parent) or in the free nodes list (has a next node) + */ + WeakReference parent = null; //!< Parent node ID (0 is ROOT) + int height = -1; //!< Height of the node in the tree TODO check the need... + AABB aabb = new AABB(); //!< Fat axis aligned bounding box (AABB) corresponding to the node + + public DTree() { + this.uid = simpleCounter++; + } + + boolean isLeaf() { + return false; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafData.java b/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafData.java new file mode 100644 index 0000000..1caf6d9 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafData.java @@ -0,0 +1,15 @@ +package org.atriasoft.ephysics.collision.broadphase; + +class DTreeLeafData extends DTree { + Object dataPointer = null; + + public DTreeLeafData(final Object dataPointer) { + super(); + this.dataPointer = dataPointer; + } + + @Override + boolean isLeaf() { + return true; + } +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafInt.java b/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafInt.java new file mode 100644 index 0000000..ad37a35 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/DTreeLeafInt.java @@ -0,0 +1,17 @@ +package org.atriasoft.ephysics.collision.broadphase; + +class DTreeLeafInt extends DTree { + int dataInt_0 = 0; + int dataInt_1 = 0; + + public DTreeLeafInt(final int dataInt_0, final int dataInt_1) { + super(); + this.dataInt_0 = dataInt_0; + this.dataInt_1 = dataInt_1; + } + + @Override + boolean isLeaf() { + return true; + } +} diff --git a/src/org/atriasoft/ephysics/collision/broadphase/DTreeNode.java b/src/org/atriasoft/ephysics/collision/broadphase/DTreeNode.java new file mode 100644 index 0000000..17774f8 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/DTreeNode.java @@ -0,0 +1,6 @@ +package org.atriasoft.ephysics.collision.broadphase; + +class DTreeNode extends DTree { + DTree children_left = null; //!< Left child of the node + DTree children_right = null; //!< Right child of the node +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/broadphase/DynamicAABBTree.java b/src/org/atriasoft/ephysics/collision/broadphase/DynamicAABBTree.java new file mode 100644 index 0000000..322444f --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/DynamicAABBTree.java @@ -0,0 +1,576 @@ +package org.atriasoft.ephysics.collision.broadphase; + +import java.lang.ref.WeakReference; +import java.util.Stack; + +import org.atriasoft.ephysics.Configuration; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Vector3f; + +/** + * It implements a dynamic AABB tree that is used for broad-phase + * collision detection. This data structure is inspired by Nathanael Presson's + * dynamic tree implementation in BulletPhysics. The following implementation is + * based on the one from Erin Catto in Box2D as described in the book + * "Introduction to Game Physics with Box2D" by Ian Parberry. + */ +public class DynamicAABBTree { + + private DTree rootNode; //!< Pointer to the memory location of the nodes of the tree + + private final float extraAABBGap; //!< Extra AABB Gap used to allow the collision shape to move a little bit without triggering a large modification of the tree which can be costly + /// Allocate and return a node to use in the tree + + /// Constructor + public DynamicAABBTree() { + this(0.0f); + } + + public DynamicAABBTree(final float extraAABBGap) { + this.extraAABBGap = extraAABBGap; + init(); + } + + /// Add an object into the tree (where node data are two integers) + public DTree addObject(final AABB aabb, final int data1, final int data2) { + final DTreeLeafInt node = new DTreeLeafInt(data1, data2); + addObjectInternal(aabb, node); + return node; + } + + /// Add an object into the tree (where node data is a pointer) + public DTree addObject(final AABB aabb, final Object data) { + final DTreeLeafData node = new DTreeLeafData(data); + addObjectInternal(aabb, node); + return node; + } + + /// Internally add an object into the tree + private void addObjectInternal(final AABB aabb, final DTree leafNode) { + // Create the fat aabb to use in the tree + leafNode.aabb.setMin(aabb.getMin().lessNew(this.extraAABBGap)); + leafNode.aabb.setMax(aabb.getMax().addNew(this.extraAABBGap)); + // Set the height of the node in the tree + leafNode.height = 0; + // Insert the new leaf node in the tree + insertLeafNode(leafNode); + assert (leafNode.isLeaf()); + } + + /// Balance the sub-tree of a given node using left or right rotations. + private DTree balanceSubTreeAtNode(final DTree _node) { + assert (_node != null); + // If the node is a leaf or the height of A's sub-tree is less than 2 + if (_node.isLeaf() || _node.height < 2) { + // Do not perform any rotation + return _node; + } + final DTreeNode nodeA = (DTreeNode) _node; + // Get the two children nodes + final DTree nodeB = nodeA.children_left; + final DTree nodeC = nodeA.children_right; + // Compute the factor of the left and right sub-trees + final int balanceFactor = nodeC.height - nodeB.height; + // If the right node C is 2 higher than left node B + if (balanceFactor > 1) { + assert (!nodeC.isLeaf()); + final DTreeNode nodeCTree = (DTreeNode) nodeC; + final DTree nodeF = nodeCTree.children_left; + final DTree nodeG = nodeCTree.children_right; + nodeCTree.children_left = _node; + nodeCTree.parent = nodeA.parent; + nodeA.parent = new WeakReference(nodeC); + if (nodeC.parent != null) { + final DTreeNode nodeCParent = (DTreeNode) nodeC.parent.get(); + if (nodeCParent.children_left == _node) { + nodeCParent.children_left = nodeC; + } else { + assert (nodeCParent.children_right == _node); + nodeCParent.children_right = nodeC; + } + } else { + this.rootNode = nodeC; + } + assert (!nodeC.isLeaf()); + assert (!nodeA.isLeaf()); + // If the right node C was higher than left node B because of the F node + if (nodeF.height > nodeG.height) { + nodeCTree.children_right = nodeF; + nodeA.children_right = nodeG; + nodeG.parent = new WeakReference(_node); + // Recompute the AABB of node A and C + nodeA.aabb.mergeTwoAABBs(nodeB.aabb, nodeG.aabb); + nodeC.aabb.mergeTwoAABBs(nodeA.aabb, nodeF.aabb); + // Recompute the height of node A and C + nodeA.height = FMath.max(nodeB.height, nodeG.height) + 1; + nodeC.height = FMath.max(nodeA.height, nodeF.height) + 1; + assert (nodeA.height > 0); + assert (nodeC.height > 0); + } else { + // If the right node C was higher than left node B because of node G + nodeCTree.children_right = nodeG; + nodeA.children_right = nodeF; + nodeF.parent = new WeakReference(_node); + // Recompute the AABB of node A and C + nodeA.aabb.mergeTwoAABBs(nodeB.aabb, nodeF.aabb); + nodeC.aabb.mergeTwoAABBs(nodeA.aabb, nodeG.aabb); + // Recompute the height of node A and C + nodeA.height = FMath.max(nodeB.height, nodeF.height) + 1; + nodeC.height = FMath.max(nodeA.height, nodeG.height) + 1; + assert (nodeA.height > 0); + assert (nodeC.height > 0); + } + // Return the new root of the sub-tree + return nodeC; + } + // If the left node B is 2 higher than right node C + if (balanceFactor < -1) { + assert (!nodeB.isLeaf()); + final DTreeNode nodeBTree = (DTreeNode) nodeB; + final DTree nodeF = nodeBTree.children_left; + final DTree nodeG = nodeBTree.children_right; + nodeBTree.children_left = _node; + nodeB.parent = nodeA.parent; + nodeA.parent = new WeakReference(nodeB); + if (nodeB.parent != null) { + final DTreeNode nodeBParent = (DTreeNode) nodeB.parent.get(); + if (nodeBParent.children_left == _node) { + nodeBParent.children_left = nodeB; + } else { + assert (nodeBParent.children_right == _node); + nodeBParent.children_right = nodeB; + } + } else { + this.rootNode = nodeB; + } + assert (!nodeB.isLeaf()); + assert (!nodeA.isLeaf()); + // If the left node B was higher than right node C because of the F node + if (nodeF.height > nodeG.height) { + nodeBTree.children_right = nodeF; + nodeA.children_left = nodeG; + nodeG.parent = new WeakReference(_node); + // Recompute the AABB of node A and B + nodeA.aabb.mergeTwoAABBs(nodeC.aabb, nodeG.aabb); + nodeB.aabb.mergeTwoAABBs(nodeA.aabb, nodeF.aabb); + // Recompute the height of node A and B + nodeA.height = FMath.max(nodeC.height, nodeG.height) + 1; + nodeB.height = FMath.max(nodeA.height, nodeF.height) + 1; + assert (nodeA.height > 0); + assert (nodeB.height > 0); + } else { + // If the left node B was higher than right node C because of node G + nodeBTree.children_right = nodeG; + nodeA.children_left = nodeF; + nodeF.parent = new WeakReference(_node); + // Recompute the AABB of node A and B + nodeA.aabb.mergeTwoAABBs(nodeC.aabb, nodeF.aabb); + nodeB.aabb.mergeTwoAABBs(nodeA.aabb, nodeG.aabb); + // Recompute the height of node A and B + nodeA.height = FMath.max(nodeC.height, nodeF.height) + 1; + nodeB.height = FMath.max(nodeA.height, nodeG.height) + 1; + assert (nodeA.height > 0); + assert (nodeB.height > 0); + } + // Return the new root of the sub-tree + return nodeB; + } + // If the sub-tree is balanced, return the current root node + return _node; + } + + /// Compute the height of the tree + public int computeHeight() { + return computeHeight(this.rootNode); + } + + /// Compute the height of a given node in the tree + private int computeHeight(final DTree _node) { + // If the node is a leaf, its height is zero + if (_node.isLeaf()) { + return 0; + } + final DTreeNode nodeTree = (DTreeNode) _node; + + // Compute the height of the left and right sub-tree + final int leftHeight = computeHeight(nodeTree.children_left); + final int rightHeight = computeHeight(nodeTree.children_right); + // Return the height of the node + return 1 + Math.max(leftHeight, rightHeight); + } + + /// Return the fat AABB corresponding to a given node ID + public AABB getFatAABB(final DTree node) { + return node.aabb; + } + + public int getNodeDataInt_0(final DTree node) { + assert (node.isLeaf()); + return ((DTreeLeafInt) node).dataInt_0; + } + + public int getNodeDataInt_1(final DTree node) { + assert (node.isLeaf()); + return ((DTreeLeafInt) node).dataInt_1; + } + + /// Return the data pointer of a given leaf node of the tree + public Object getNodeDataPointer(final DTree node) { + assert (node.isLeaf()); + return ((DTreeLeafData) node).dataPointer; + } + + /// Return the root AABB of the tree + public AABB getRootAABB() { + return getFatAABB(this.rootNode); + } + + /// Initialize the tree + private void init() { + this.rootNode = null; + } + + /// Insert a leaf node in the tree + private void insertLeafNode(final DTree _node) { + // If the tree is empty + if (this.rootNode == null) { + this.rootNode = _node; + return; + } + // Find the best sibling node for the new node + final AABB newNodeAABB = _node.aabb; + DTree currentNode = this.rootNode; + while (!currentNode.isLeaf()) { + final DTreeNode node = (DTreeNode) currentNode; + final DTree leftChild = node.children_left; + final DTree rightChild = node.children_right; + // Compute the merged AABB + final float volumeAABB = currentNode.aabb.getVolume(); + final AABB mergedAABBs = new AABB(); + mergedAABBs.mergeTwoAABBs(currentNode.aabb, newNodeAABB); + final float mergedVolume = mergedAABBs.getVolume(); + // Compute the cost of making the current node the sibbling of the new node + final float costS = 2.0f * mergedVolume; + // Compute the minimum cost of pushing the new node further down the tree (inheritance cost) + final float costI = 2.0f * (mergedVolume - volumeAABB); + // Compute the cost of descending into the left child + float costLeft; + final AABB currentAndLeftAABB = new AABB(); + currentAndLeftAABB.mergeTwoAABBs(newNodeAABB, leftChild.aabb); + if (leftChild.isLeaf()) { // If the left child is a leaf + costLeft = currentAndLeftAABB.getVolume() + costI; + } else { + final float leftChildVolume = leftChild.aabb.getVolume(); + costLeft = costI + currentAndLeftAABB.getVolume() - leftChildVolume; + } + // Compute the cost of descending into the right child + float costRight; + final AABB currentAndRightAABB = new AABB(); + currentAndRightAABB.mergeTwoAABBs(newNodeAABB, rightChild.aabb); + if (rightChild.isLeaf()) { // If the right child is a leaf + costRight = currentAndRightAABB.getVolume() + costI; + } else { + final float rightChildVolume = rightChild.aabb.getVolume(); + costRight = costI + currentAndRightAABB.getVolume() - rightChildVolume; + } + // If the cost of making the current node a sibbling of the new node is smaller than + // the cost of going down into the left or right child + if (costS < costLeft && costS < costRight) { + break; + } + // It is cheaper to go down into a child of the current node, choose the best child + if (costLeft < costRight) { + currentNode = leftChild; + } else { + currentNode = rightChild; + } + } + final DTree siblingNode = currentNode; + // Create a new parent for the new node and the sibling node + final WeakReference oldParentNode = siblingNode.parent; + final DTreeNode newParentNode = new DTreeNode(); + newParentNode.parent = currentNode.parent; + newParentNode.aabb.mergeTwoAABBs(currentNode.aabb, newNodeAABB); + newParentNode.height = currentNode.height + 1; + // If the sibling node was not the root node + if (oldParentNode != null) { + // replace in parent the child with the new child + final DTreeNode parentNode = (DTreeNode) oldParentNode.get(); + if (parentNode.children_left == siblingNode) { + parentNode.children_left = newParentNode; + } else { + parentNode.children_right = newParentNode; + } + } else { + // If the sibling node was the root node + this.rootNode = newParentNode; + } + // setup the children + newParentNode.children_left = siblingNode; + newParentNode.children_right = _node; + siblingNode.parent = new WeakReference(newParentNode); + _node.parent = new WeakReference(newParentNode); + + // Move up in the tree to change the AABBs that have changed + currentNode = newParentNode; + assert (!currentNode.isLeaf()); + while (currentNode != null) { + // Balance the sub-tree of the current node if it is not balanced + currentNode = balanceSubTreeAtNode(currentNode); + assert (_node.isLeaf()); + assert (!currentNode.isLeaf()); + final DTreeNode nodeDouble = (DTreeNode) currentNode; + final DTree leftChild = nodeDouble.children_left; + final DTree rightChild = nodeDouble.children_right; + assert (leftChild != null); + assert (rightChild != null); + // Recompute the height of the node in the tree + currentNode.height = FMath.max(leftChild.height, rightChild.height) + 1; + assert (currentNode.height > 0); + // Recompute the AABB of the node + currentNode.aabb.mergeTwoAABBs(leftChild.aabb, rightChild.aabb); + if (currentNode.parent != null) { + currentNode = currentNode.parent.get(); + } else { + currentNode = null; + } + } + assert (_node.isLeaf()); + } + + /// Ray casting method + public void raycast(final Ray _ray, final CallbackRaycast _callback) { + if (_callback == null) { + Log.error("call with null callback"); + return; + } + float maxFraction = _ray.maxFraction; + // 128 max + final Stack stack = new Stack(); + stack.push(this.rootNode); + // Walk through the tree from the root looking for proxy shapes + // that overlap with the ray AABB + while (stack.size() > 0) { + // Get the next node in the stack + final DTree node = stack.pop(); + // If it is a null node, skip it + if (node == null) { + continue; + } + final Ray rayTemp = new Ray(_ray.point1, _ray.point2, maxFraction); + // Test if the ray intersects with the current node AABB + if (node.aabb.testRayIntersect(rayTemp) == false) { + continue; + } + // If the node is a leaf of the tree + if (node.isLeaf()) { + // Call the callback that will raycast again the broad-phase shape + final float hitFraction = _callback.callback(node, rayTemp); + // If the user returned a hitFraction of zero, it means that + // the raycasting should stop here + if (hitFraction == 0.0f) { + return; + } + // If the user returned a positive fraction + if (hitFraction > 0.0f) { + // We update the maxFraction value and the ray + // AABB using the new maximum fraction + if (hitFraction < maxFraction) { + maxFraction = hitFraction; + } + } + // If the user returned a negative fraction, we continue + // the raycasting as if the proxy shape did not exist + } else { // If the node has children + final DTreeNode tmpNode = (DTreeNode) node; + // Push its children in the stack of nodes to explore + stack.push(tmpNode.children_left); + stack.push(tmpNode.children_right); + } + } + + } + + /// Release a node + private void releaseNode(final DTree _node) { + //this.numberNodes--; + } + + /// Remove a leaf node from the tree + private void removeLeafNode(final DTree _node) { + assert (_node.isLeaf()); + // If we are removing the root node (root node is a leaf in this case) + if (this.rootNode == _node) { + this.rootNode = null; + return; + } + // parent can not be null. + final DTreeNode parentNode = (DTreeNode) _node.parent.get(); + final WeakReference grandParentNodeWeak = parentNode.parent; + DTree siblingNode; + if (parentNode.children_left == _node) { + siblingNode = parentNode.children_right; + } else { + siblingNode = parentNode.children_left; + } + // If the parent of the node to remove is not the root node + if (grandParentNodeWeak == null) { + // If the parent of the node to remove is the root node + // The sibling node becomes the new root node + this.rootNode = siblingNode; + siblingNode.parent = null; + releaseNode(parentNode); + } else { + final DTreeNode grandParentNode = (DTreeNode) grandParentNodeWeak.get(); + // Destroy the parent node + if (grandParentNode.children_left == parentNode) { + grandParentNode.children_left = siblingNode; + } else { + grandParentNode.children_right = siblingNode; + } + siblingNode.parent = parentNode.parent; + releaseNode(parentNode); + // Now, we need to recompute the AABBs of the node on the path back to the root + // and make sure that the tree is still balanced + DTree currentNode = grandParentNode; + while (currentNode != null) { + // Balance the current sub-tree if necessary + currentNode = balanceSubTreeAtNode(currentNode); + assert (!currentNode.isLeaf()); + final DTreeNode currentTreeNode = (DTreeNode) currentNode; + // Get the two children of the current node + final DTree leftChild = currentTreeNode.children_left; + final DTree rightChild = currentTreeNode.children_right; + // Recompute the AABB and the height of the current node + currentNode.aabb.mergeTwoAABBs(leftChild.aabb, rightChild.aabb); + currentNode.height = FMath.max(leftChild.height, rightChild.height) + 1; + assert (currentNode.height > 0); + if (currentNode.parent == null) { + currentNode = null; + } else { + currentNode = currentNode.parent.get(); + } + } + } + } + + /// Remove an object from the tree + public void removeObject(final DTree _node) { + assert (_node.isLeaf()); + // Remove the node from the tree + removeLeafNode(_node); + releaseNode(_node); + } + + /// Report all shapes overlapping with the AABB given in parameter. + public void reportAllShapesOverlappingWithAABB(final AABB _aabb, final CallbackOverlapping _callback) { + if (_callback == null) { + Log.error("call with null callback"); + return; + } + //Log.error("reportAllShapesOverlappingWithAABB"); + // Create a stack with the nodes to visit + final Stack stack = new Stack(); + // 64 max + stack.push(this.rootNode); + //Log.error(" add stack: " + this.rootNode); + // While there are still nodes to visit + while (stack.size() > 0) { + // Get the next node ID to visit + final DTree nodeIDToVisit = stack.pop(); + // Skip it if it is a null node + if (nodeIDToVisit == null) { + continue; + } + // Get the corresponding node + final DTree nodeToVisit = nodeIDToVisit; + //Log.error(" check colision: " + nodeIDToVisit); + // If the AABB in parameter overlaps with the AABB of the node to visit + if (_aabb.testCollision(nodeToVisit.aabb)) { + // If the node is a leaf + if (nodeToVisit.isLeaf()) { + /* + if (_aabb != nodeToVisit.aabb) { + Log.error(" ======> Real collision ..."); + } + */ + // Notify the broad-phase about a new potential overlapping pair + _callback.callback(nodeIDToVisit); + } else { + final DTreeNode tmp = (DTreeNode) nodeToVisit; + // If the node is not a leaf + // We need to visit its children + stack.push(tmp.children_left); + stack.push(tmp.children_right); + //Log.error(" add stack: " + tmp.children_left); + //Log.error(" add stack: " + tmp.children_right); + } + } + } + } + + /// Clear all the nodes and reset the tree + public void reset() { + // Initialize the tree + init(); + } + + /// Update the dynamic tree after an object has moved. + /// If the new AABB of the object that has moved is still inside its fat AABB, then + /// nothing is done. Otherwise, the corresponding node is removed and reinserted into the tree. + /// The method returns true if the object has been reinserted into the tree. The "displacement" + /// argument is the linear velocity of the AABB multiplied by the elapsed time between two + /// frames. If the "forceReinsert" parameter is true, we force a removal and reinsertion of the node + /// (this can be useful if the shape AABB has become much smaller than the previous one for instance). + public boolean updateObject(final DTree _node, final AABB _newAABB, final Vector3f _displacement) { + return updateObject(_node, _newAABB, _displacement, false); + } + + public boolean updateObject(final DTree _node, final AABB _newAABB, final Vector3f _displacement, final boolean _forceReinsert) { + assert (_node.isLeaf()); + assert (_node.height >= 0); + //Log.verbose(" compare : " + _node.aabb.getMin() + " " + _node.aabb.getMax()); + //Log.verbose(" : " + _newAABB.getMin() + " " + _newAABB.getMax()); + // If the new AABB is still inside the fat AABB of the node + if (_forceReinsert == false && _node.aabb.contains(_newAABB)) { + return false; + } + // If the new AABB is outside the fat AABB, we remove the corresponding node + removeLeafNode(_node); + // Compute the fat AABB by inflating the AABB with a ant gap + _node.aabb = _newAABB; + + final Vector3f gap = new Vector3f(this.extraAABBGap, this.extraAABBGap, this.extraAABBGap); + _node.aabb.getMin().less(gap); + _node.aabb.getMax().add(gap); + // Inflate the fat AABB in direction of the linear motion of the AABB + if (_displacement.x < 0.0f) { + _node.aabb.getMin().setX(_node.aabb.getMin().x + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.x); + } else { + _node.aabb.getMax().setX(_node.aabb.getMax().x + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.x); + } + if (_displacement.y < 0.0f) { + _node.aabb.getMin().setY(_node.aabb.getMin().y + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.y); + } else { + _node.aabb.getMax().setY(_node.aabb.getMax().y + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.y); + } + if (_displacement.z < 0.0f) { + _node.aabb.getMin().setZ(_node.aabb.getMin().z + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.z); + } else { + _node.aabb.getMax().setZ(_node.aabb.getMax().z + Configuration.DYNAMIC_TREE_AABB_LIN_GAP_MULTIPLIER * _displacement.z); + } + //Log.error(" compare : " + _node.aabb.getMin() + " " + _node.aabb.getMax()); + //Log.error(" : " + _newAABB.getMin() + " " + _newAABB.getMax()); + if (_node.aabb.contains(_newAABB) == false) { + //Log.critical("ERROR"); + } + assert (_node.aabb.contains(_newAABB)); + // Reinsert the node into the tree + insertLeafNode(_node); + return true; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/broadphase/PairDTree.java b/src/org/atriasoft/ephysics/collision/broadphase/PairDTree.java new file mode 100644 index 0000000..3c4373d --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/broadphase/PairDTree.java @@ -0,0 +1,73 @@ +package org.atriasoft.ephysics.collision.broadphase; + +import java.util.Set; + +import javafx.collections.transformation.SortedList; + +public class PairDTree { + public static int countInSet(final Set values, final PairDTree sample) { + int count = 0; + for (final PairDTree elem : values) { + if (elem.first != sample.first) { + continue; + } + if (elem.second != sample.second) { + continue; + } + count++; + } + return count; + } + + public static int countInSet(final SortedList values, final PairDTree sample) { + int count = 0; + for (final PairDTree elem : values) { + if (elem.first != sample.first) { + continue; + } + if (elem.second != sample.second) { + continue; + } + count++; + } + return count; + } + + public final DTree first; + + public final DTree second; + + public PairDTree(final DTree first, final DTree second) { + if (first.uid < second.uid) { + this.first = first; + this.second = second; + } else { + this.first = second; + this.second = first; + + } + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PairDTree tmp = (PairDTree) obj; + if (this.first != tmp.first) { + return false; + } + if (this.second != tmp.second) { + return false; + } + return true; + } + + @Override + public String toString() { + return "PairDTree [first=" + this.first.uid + ", second=" + this.second.uid + "]"; + } +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/CollisionDispatch.java b/src/org/atriasoft/ephysics/collision/narrowphase/CollisionDispatch.java new file mode 100644 index 0000000..dedceb3 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/CollisionDispatch.java @@ -0,0 +1,21 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.shapes.CollisionShapeType; + +/** + * @biref Abstract base class for dispatching the narrow-phase + * collision detection algorithm. Collision dispatching decides which collision + * algorithm to use given two types of proxy collision shapes. + */ +public abstract class CollisionDispatch { + + /// Initialize the collision dispatch configuration + public void init(final CollisionDetection _collisionDetection) { + // Nothing to do ... + } + + /// Select and return the narrow-phase collision detection algorithm to + /// use between two types of collision shapes. + public abstract NarrowPhaseAlgorithm selectAlgorithm(CollisionShapeType _shape1Type, CollisionShapeType _shape2Type); +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.java b/src/org/atriasoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.java new file mode 100644 index 0000000..ff75532 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.java @@ -0,0 +1,355 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.CollisionShapeInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.collision.shapes.CollisionShapeType; +import org.atriasoft.ephysics.collision.shapes.ConcaveShape; +import org.atriasoft.ephysics.collision.shapes.ConvexShape; +import org.atriasoft.ephysics.collision.shapes.TriangleShape; +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.ephysics.engine.OverlappingPair; +import org.atriasoft.ephysics.mathematics.Mathematics; +import org.atriasoft.ephysics.mathematics.PairIntVector3f; + +//static boolean sortFunction(SmoothMeshContactInfo&_contact1,SmoothMeshContactInfo&_contact2){return _contact1.contactInfo.penetrationDepth<=_contact2.contactInfo.penetrationDepth;} + +/** + * This class is used to compute the narrow-phase collision detection + * between a concave collision shape and a convex collision shape. The idea is + * to use the GJK collision detection algorithm to compute the collision between + * the convex shape and each of the triangles in the concave shape. + */ +public class ConcaveVsConvexAlgorithm extends NarrowPhaseAlgorithm { + /** + * This class is used to encapsulate a callback method for + * collision detection between the triangle of a concave mesh shape + * and a convex shape. + */ + class ConvexVsTriangleCallback implements ConcaveShape.TriangleCallback { + + protected CollisionDetection collisionDetection; //!< Pointer to the collision detection object + protected NarrowPhaseCallback narrowPhaseCallback; //!< Narrow-phase collision callback + protected ConvexShape convexShape; //!< Convex collision shape to test collision with + protected ConcaveShape concaveShape; //!< Concave collision shape + protected ProxyShape convexProxyShape; //!< Proxy shape of the convex collision shape + protected ProxyShape concaveProxyShape; //!< Proxy shape of the concave collision shape + protected OverlappingPair overlappingPair; //!< Broadphase overlapping pair + + // protected static boolean contactsDepthCompare(ContactPointInfo _contact1, ContactPointInfo _contact2); + + /// Set the collision detection pointer + public void setCollisionDetection(final CollisionDetection _collisionDetection) { + this.collisionDetection = _collisionDetection; + } + + /// Set the concave collision shape + public void setConcaveShape(final ConcaveShape _concaveShape) { + this.concaveShape = _concaveShape; + } + + /// Set the convex collision shape to test collision with + public void setConvexShape(final ConvexShape _convexShape) { + this.convexShape = _convexShape; + } + + /// Set the narrow-phase collision callback + public void setNarrowPhaseCallback(final NarrowPhaseCallback _callback) { + this.narrowPhaseCallback = _callback; + } + + /// Set the broadphase overlapping pair + public void setOverlappingPair(final OverlappingPair _overlappingPair) { + this.overlappingPair = _overlappingPair; + } + + /// Set the proxy shapes of the two collision shapes + public void setProxyShapes(final ProxyShape _convexProxyShape, final ProxyShape _concaveProxyShape) { + this.convexProxyShape = _convexProxyShape; + this.concaveProxyShape = _concaveProxyShape; + } + + /// Test collision between a triangle and the convex mesh shape + @Override + public void testTriangle(final Vector3f[] _trianglePoints) { + // Create a triangle collision shape + final float margin = this.concaveShape.getTriangleMargin(); + + final TriangleShape triangleShape = new TriangleShape(_trianglePoints[0], _trianglePoints[1], _trianglePoints[2], margin); + // Select the collision algorithm to use between the triangle and the convex shape + final NarrowPhaseAlgorithm algo = this.collisionDetection.getCollisionAlgorithm(triangleShape.getType(), this.convexShape.getType()); + // If there is no collision algorithm between those two kinds of shapes + if (algo == null) { + return; + } + // Notify the narrow-phase algorithm about the overlapping pair we are going to test + algo.setCurrentOverlappingPair(this.overlappingPair); + // Create the CollisionShapeInfo objects + final CollisionShapeInfo shapeConvexInfo = new CollisionShapeInfo(this.convexProxyShape, this.convexShape, this.convexProxyShape.getLocalToWorldTransform(), this.overlappingPair, + this.convexProxyShape.getCachedCollisionData()); + + final CollisionShapeInfo shapeConcaveInfo = new CollisionShapeInfo(this.concaveProxyShape, triangleShape, this.concaveProxyShape.getLocalToWorldTransform(), this.overlappingPair, + this.concaveProxyShape.getCachedCollisionData()); + // Use the collision algorithm to test collision between the triangle and the other convex shape + algo.testCollision(shapeConvexInfo, shapeConcaveInfo, this.narrowPhaseCallback); + } + + } + + /** + * This class is used as a narrow-phase callback to get narrow-phase contacts + * of the concave triangle mesh to temporary store them in order to be used in + * the smooth mesh collision algorithm if this one is enabled. + */ + class SmoothCollisionNarrowPhaseCallback implements NarrowPhaseCallback { + private final List contactPoints; + + // Constructor + public SmoothCollisionNarrowPhaseCallback(final List _contactPoints) { + this.contactPoints = new ArrayList<>(_contactPoints); + } + + /// Called by a narrow-phase collision algorithm when a new contact has been found + @Override + public void notifyContact(final OverlappingPair _overlappingPair, final ContactPointInfo _contactInfo) { + final Vector3f[] triangleVertices = new Vector3f[3]; + boolean isFirstShapeTriangle; + // If the collision shape 1 is the triangle + if (_contactInfo.collisionShape1.getType() == CollisionShapeType.TRIANGLE) { + assert (_contactInfo.collisionShape2.getType() != CollisionShapeType.TRIANGLE); + final TriangleShape triangleShape = (TriangleShape) _contactInfo.collisionShape1; + triangleVertices[0] = triangleShape.getVertex(0); + triangleVertices[1] = triangleShape.getVertex(1); + triangleVertices[2] = triangleShape.getVertex(2); + isFirstShapeTriangle = true; + } else { // If the collision shape 2 is the triangle + assert (_contactInfo.collisionShape2.getType() == CollisionShapeType.TRIANGLE); + final TriangleShape triangleShape = (TriangleShape) _contactInfo.collisionShape2; + triangleVertices[0] = triangleShape.getVertex(0); + triangleVertices[1] = triangleShape.getVertex(1); + triangleVertices[2] = triangleShape.getVertex(2); + isFirstShapeTriangle = false; + } + + final SmoothMeshContactInfo smoothContactInfo = new SmoothMeshContactInfo(_contactInfo, isFirstShapeTriangle, triangleVertices[0], triangleVertices[1], triangleVertices[2]); + // Add the narrow-phase contact into the list of contact to process for + // smooth mesh collision + this.contactPoints.add(smoothContactInfo); + } + + } + + /** + * This class is used to store data about a contact with a triangle for the smooth + * mesh algorithm. + */ + class SmoothMeshContactInfo { + public ContactPointInfo contactInfo; + public boolean isFirstShapeTriangle; + + public Vector3f[] triangleVertices = new Vector3f[3]; + + public SmoothMeshContactInfo() { + // TODO: add it for List + } + + /// Constructor + public SmoothMeshContactInfo(final ContactPointInfo _contact, final boolean _firstShapeTriangle, final Vector3f _trianglePoint1, final Vector3f _trianglePoint2, + final Vector3f _trianglePoint3) { + this.contactInfo = new ContactPointInfo(_contact); + this.isFirstShapeTriangle = _firstShapeTriangle; + this.triangleVertices[0] = _trianglePoint1; + this.triangleVertices[1] = _trianglePoint2; + this.triangleVertices[2] = _trianglePoint3; + } + } + + /// Constructor + public ConcaveVsConvexAlgorithm(final CollisionDetection collisionDetection) { + super(collisionDetection); + } + + /// Add a triangle vertex into the set of processed triangles + protected void addProcessedVertex(final List _processTriangleVertices, final Vector3f _vertex) { + _processTriangleVertices.add(new PairIntVector3f((int) (_vertex.x * _vertex.y * _vertex.z), _vertex)); + } + + /// Return true if the vertex is in the set of already processed vertices + protected boolean hasVertexBeenProcessed(final List _processTriangleVertices, final Vector3f _vertex) { + /* TODO : List> was an unordered map ... ==> stupid idee... I replace code because I do not have enouth time to do something good... + int key = int(_vertex.x * _vertex.y * _vertex.z); + auto range = _processTriangleVertices.equal_range(key); + for (auto it = range.first; it != range.second; ++it) { + if ( _vertex.x == it.second.x + && _vertex.y == it.second.y + && _vertex.z == it.second.z) { + return true; + } + } + return false; + */ + // TODO : This is not really the same ... + for (final PairIntVector3f it : _processTriangleVertices) { + if (_vertex.x == it.second.x && _vertex.y == it.second.y && _vertex.z == it.second.z) { + return true; + } + } + return false; + } + + /// Process the concave triangle mesh collision using the smooth mesh collision algorithm + protected void processSmoothMeshCollision(final OverlappingPair _overlappingPair, final List _contactPoints, final NarrowPhaseCallback _callback) { + // Set with the triangle vertices already processed to void further contacts with same triangle + final List processTriangleVertices = new ArrayList<>(); + // Sort the list of narrow-phase contacts according to their penetration depth + _contactPoints.sort(new Comparator() { + @Override + public int compare(final SmoothMeshContactInfo _pair1, final SmoothMeshContactInfo _pair2) { + if (_pair1.contactInfo.penetrationDepth < _pair2.contactInfo.penetrationDepth) { + return -1; + } + if (_pair1.contactInfo.penetrationDepth == _pair2.contactInfo.penetrationDepth) { + return 0; + } + return +1; + } + }); + + // For each contact point (from smaller penetration depth to larger) + for (final SmoothMeshContactInfo info : _contactPoints) { + final Vector3f contactPoint = info.isFirstShapeTriangle ? info.contactInfo.localPoint1 : info.contactInfo.localPoint2; + // Compute the barycentric coordinates of the point in the triangle + final Float u = 0.0f, v = 0.0f, w = 0.0f; + Mathematics.computeBarycentricCoordinatesInTriangle(info.triangleVertices[0], info.triangleVertices[1], info.triangleVertices[2], contactPoint, u, v, w); + int nbZeros = 0; + final boolean isUZero = Mathematics.ApproxEqual(u, 0.0f, 0.0001f); + final boolean isVZero = Mathematics.ApproxEqual(v, 0.0f, 0.0001f); + final boolean isWZero = Mathematics.ApproxEqual(w, 0.0f, 0.0001f); + if (isUZero) { + nbZeros++; + } + if (isVZero) { + nbZeros++; + } + if (isWZero) { + nbZeros++; + } + // If it is a vertex contact + if (nbZeros == 2) { + final Vector3f contactVertex = !isUZero ? info.triangleVertices[0] : (!isVZero ? info.triangleVertices[1] : info.triangleVertices[2]); + // Check that this triangle vertex has not been processed yet + if (!hasVertexBeenProcessed(processTriangleVertices, contactVertex)) { + // Keep the contact as it is and report it + _callback.notifyContact(_overlappingPair, info.contactInfo); + } + } else if (nbZeros == 1) { + // If it is an edge contact + final Vector3f contactVertex1 = isUZero ? info.triangleVertices[1] : (isVZero ? info.triangleVertices[0] : info.triangleVertices[0]); + final Vector3f contactVertex2 = isUZero ? info.triangleVertices[2] : (isVZero ? info.triangleVertices[2] : info.triangleVertices[1]); + // Check that this triangle edge has not been processed yet + if (!hasVertexBeenProcessed(processTriangleVertices, contactVertex1) && !hasVertexBeenProcessed(processTriangleVertices, contactVertex2)) { + // Keep the contact as it is and report it + _callback.notifyContact(_overlappingPair, info.contactInfo); + } + } else { + // If it is a face contact + final ContactPointInfo newContactInfo = new ContactPointInfo(info.contactInfo); + ProxyShape firstShape; + ProxyShape secondShape; + if (info.isFirstShapeTriangle) { + firstShape = _overlappingPair.getShape1(); + secondShape = _overlappingPair.getShape2(); + } else { + firstShape = _overlappingPair.getShape2(); + secondShape = _overlappingPair.getShape1(); + } + // We use the triangle normal as the contact normal + final Vector3f a = info.triangleVertices[1].lessNew(info.triangleVertices[0]); + final Vector3f b = info.triangleVertices[2].lessNew(info.triangleVertices[0]); + final Vector3f localNormal = a.cross(b); + newContactInfo.normal = firstShape.getLocalToWorldTransform().getOrientation().multiply(localNormal); + final Vector3f firstLocalPoint = info.isFirstShapeTriangle ? info.contactInfo.localPoint1 : info.contactInfo.localPoint2; + final Vector3f firstWorldPoint = firstShape.getLocalToWorldTransform().multiplyNew(firstLocalPoint); + newContactInfo.normal.normalize(); + if (newContactInfo.normal.dot(info.contactInfo.normal) < 0) { + newContactInfo.normal.multiply(-1); + } + // We recompute the contact point on the second body with the new normal as described in + // the Smooth Mesh Contacts with GJK of the Game Physics Pearls book (from Gino van Den Bergen and + // Dirk Gregorius) to avoid adding torque + final Transform3D worldToLocalSecondPoint = secondShape.getLocalToWorldTransform().inverseNew(); + if (info.isFirstShapeTriangle) { + final Vector3f newSecondWorldPoint = firstWorldPoint.addNew(newContactInfo.normal); + newContactInfo.localPoint2 = worldToLocalSecondPoint.multiplyNew(newSecondWorldPoint); + } else { + final Vector3f newSecondWorldPoint = firstWorldPoint.lessNew(newContactInfo.normal); + newContactInfo.localPoint1 = worldToLocalSecondPoint.multiplyNew(newSecondWorldPoint); + } + // Report the contact + _callback.notifyContact(_overlappingPair, newContactInfo); + } + + // Add the three vertices of the triangle to the set of processed + // triangle vertices + addProcessedVertex(processTriangleVertices, info.triangleVertices[0]); + addProcessedVertex(processTriangleVertices, info.triangleVertices[1]); + addProcessedVertex(processTriangleVertices, info.triangleVertices[2]); + } + + } + + /// Compute a contact info if the two bounding volume collide + @Override + public void testCollision(final CollisionShapeInfo _shape1Info, final CollisionShapeInfo _shape2Info, final NarrowPhaseCallback _callback) { + ProxyShape convexProxyShape; + ProxyShape concaveProxyShape; + ConvexShape convexShape; + ConcaveShape concaveShape; + // Collision shape 1 is convex, collision shape 2 is concave + if (_shape1Info.collisionShape.isConvex()) { + convexProxyShape = _shape1Info.proxyShape; + convexShape = (ConvexShape) _shape1Info.collisionShape; + concaveProxyShape = _shape2Info.proxyShape; + concaveShape = (ConcaveShape) _shape2Info.collisionShape; + } else { + // Collision shape 2 is convex, collision shape 1 is concave + convexProxyShape = _shape2Info.proxyShape; + convexShape = (ConvexShape) _shape2Info.collisionShape; + concaveProxyShape = _shape1Info.proxyShape; + concaveShape = (ConcaveShape) _shape1Info.collisionShape; + } + // Set the parameters of the callback object + final ConvexVsTriangleCallback convexVsTriangleCallback = new ConvexVsTriangleCallback(); + convexVsTriangleCallback.setCollisionDetection(this.collisionDetection); + convexVsTriangleCallback.setConvexShape(convexShape); + convexVsTriangleCallback.setConcaveShape(concaveShape); + convexVsTriangleCallback.setProxyShapes(convexProxyShape, concaveProxyShape); + convexVsTriangleCallback.setOverlappingPair(_shape1Info.overlappingPair); + // Compute the convex shape AABB in the local-space of the convex shape + final AABB aabb = new AABB(); + convexShape.computeAABB(aabb, convexProxyShape.getLocalToWorldTransform()); + // If smooth mesh collision is enabled for the concave mesh + if (concaveShape.getIsSmoothMeshCollisionEnabled()) { + final List contactPoints = new ArrayList<>(); + + final SmoothCollisionNarrowPhaseCallback smoothNarrowPhaseCallback = new SmoothCollisionNarrowPhaseCallback(contactPoints); + convexVsTriangleCallback.setNarrowPhaseCallback(smoothNarrowPhaseCallback); + // Call the convex vs triangle callback for each triangle of the concave shape + concaveShape.testAllTriangles(convexVsTriangleCallback, aabb); + // Run the smooth mesh collision algorithm + processSmoothMeshCollision(_shape1Info.overlappingPair, contactPoints, _callback); + } else { + convexVsTriangleCallback.setNarrowPhaseCallback(_callback); + // Call the convex vs triangle callback for each triangle of the concave shape + concaveShape.testAllTriangles(convexVsTriangleCallback, aabb); + } + } +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.java b/src/org/atriasoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.java new file mode 100644 index 0000000..77404ce --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.java @@ -0,0 +1,46 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.narrowphase.GJK.GJKAlgorithm; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; +import org.atriasoft.ephysics.collision.shapes.CollisionShapeType; + +/** + * This is the default collision dispatch configuration use in ephysics. + * Collision dispatching decides which collision + * algorithm to use given two types of proxy collision shapes. + */ +public class DefaultCollisionDispatch extends CollisionDispatch { + + //!< Sphere vs Sphere collision algorithm + protected SphereVsSphereAlgorithm sphereVsSphereAlgorithm; + //!< Concave vs Convex collision algorithm + protected ConcaveVsConvexAlgorithm concaveVsConvexAlgorithm; + //!< GJK Algorithm + protected GJKAlgorithm GJKAlgorithm; + + @Override + public void init(final CollisionDetection _collisionDetection) { + // Initialize the collision algorithms + this.sphereVsSphereAlgorithm = new SphereVsSphereAlgorithm(_collisionDetection); + this.GJKAlgorithm = new GJKAlgorithm(_collisionDetection); + this.concaveVsConvexAlgorithm = new ConcaveVsConvexAlgorithm(_collisionDetection); + } + + @Override + public NarrowPhaseAlgorithm selectAlgorithm(final CollisionShapeType _type1, final CollisionShapeType _type2) { + // Sphere vs Sphere algorithm + if (_type1 == CollisionShapeType.SPHERE && _type2 == CollisionShapeType.SPHERE) { + return this.sphereVsSphereAlgorithm; + } else if ((!CollisionShape.isConvex(_type1) && CollisionShape.isConvex(_type2)) || (!CollisionShape.isConvex(_type2) && CollisionShape.isConvex(_type1))) { + // Concave vs Convex algorithm + return this.concaveVsConvexAlgorithm; + } else if (CollisionShape.isConvex(_type1) && CollisionShape.isConvex(_type2)) { + // Convex vs Convex algorithm (GJK algorithm) + return this.GJKAlgorithm; + } else { + return null; + } + } + +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.java b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.java new file mode 100644 index 0000000..116c3e9 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.java @@ -0,0 +1,423 @@ +package org.atriasoft.ephysics.collision.narrowphase.EPA; + +import java.util.Comparator; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.atriasoft.ephysics.collision.CollisionShapeInfo; +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseCallback; +import org.atriasoft.ephysics.collision.narrowphase.GJK.GJKAlgorithm; +import org.atriasoft.ephysics.collision.narrowphase.GJK.Simplex; +import org.atriasoft.ephysics.collision.shapes.CacheData; +import org.atriasoft.ephysics.collision.shapes.ConvexShape; +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * Class EPAAlgorithm + * This class is the implementation of the Expanding Polytope Algorithm (EPA). + * The EPA algorithm computes the penetration depth and contact points between + * two enlarged objects (with margin) where the original objects (without margin) + * intersect. The penetration depth of a pair of intersecting objects A and B is + * the length of a point on the boundary of the Minkowski sum (A-B) closest to the + * origin. The goal of the EPA algorithm is to start with an initial simplex polytope + * that contains the origin and expend it in order to find the point on the boundary + * of (A-B) that is closest to the origin. An initial simplex that contains origin + * has been computed wit GJK algorithm. The EPA Algorithm will extend this simplex + * polytope to find the correct penetration depth. The implementation of the EPA + * algorithm is based on the book "Collision Detection in 3D Environments". + */ +public class EPAAlgorithm { + /// Add a triangle face in the candidate triangle heap + private void addFaceCandidate(final TriangleEPA _triangle, final SortedSet _heap, final float _upperBoundSquarePenDepth) { + ////Log.info("addFaceCandidate: " + _triangle.get(0) + ", " + _triangle.get(1) + ", " + _triangle.get(2) + " " + _upperBoundSquarePenDepth); + // If the closest point of the affine hull of triangle + // points is internal to the triangle and if the distance + // of the closest point from the origin is at most the + // penetration depth upper bound + ////Log.info(" _triangle.isClosestPointInternalToTriangle(): " + _triangle.isClosestPointInternalToTriangle()); + ////Log.info(" _triangle.getDistSquare(): " + _triangle.getDistSquare()); + if (_triangle.isClosestPointInternalToTriangle() && _triangle.getDistSquare() <= _upperBoundSquarePenDepth) { + // Add the triangle face to the list of candidates + _heap.add(_triangle); + ////Log.info("add in heap:"); + //int iii = 0; + //for (final TriangleEPA elem : _heap) { + // ////Log.info(" [" + iii + "] " + elem.getDistSquare()); + // ++iii; + //} + } + } + + // Compute the penetration depth with the EPA algorithm. + /// This method computes the penetration depth and contact points between two + /// enlarged objects (with margin) where the original objects (without margin) + /// intersect. An initial simplex that contains origin has been computed with + /// GJK algorithm. The EPA Algorithm will extend this simplex polytope to find + /// the correct penetration depth + public void computePenetrationDepthAndContactPoints(final Simplex _simplex, final CollisionShapeInfo _shape1Info, final Transform3D _transform1, final CollisionShapeInfo _shape2Info, + final Transform3D _transform2, final Vector3f _vector, final NarrowPhaseCallback _narrowPhaseCallback) { + ////Log.info("computePenetrationDepthAndContactPoints()"); + assert (_shape1Info.collisionShape.isConvex()); + assert (_shape2Info.collisionShape.isConvex()); + final ConvexShape shape1 = (ConvexShape) (_shape1Info.collisionShape); + final ConvexShape shape2 = (ConvexShape) (_shape2Info.collisionShape); + final CacheData shape1CachedCollisionData = (CacheData) _shape1Info.cachedCollisionData; + final CacheData shape2CachedCollisionData = (CacheData) _shape2Info.cachedCollisionData; + final Vector3f suppPointsA[] = new Vector3f[EdgeEPA.MAX_SUPPORT_POINTS]; // Support points of object A in local coordinates + final Vector3f suppPointsB[] = new Vector3f[EdgeEPA.MAX_SUPPORT_POINTS]; // Support points of object B in local coordinates + final Vector3f points[] = new Vector3f[EdgeEPA.MAX_SUPPORT_POINTS]; // Current points + final TrianglesStore triangleStore = new TrianglesStore(); // Store the triangles + + //https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/SortedSet.html + // https://stackoverflow.com/questions/38066291/how-to-define-comparator-on-sortedset-like-treeset + final SortedSet triangleHeap = new TreeSet<>(new Comparator() { + @Override + public int compare(final TriangleEPA _face1, final TriangleEPA _face2) { + final float d1 = _face1.getDistSquare(); + final float d2 = _face2.getDistSquare(); + if (d1 < d2) { + return -1; + } + if (d1 > d2) { + return 1; + } + return 0; + } + }); + // Transform3D a point from local space of body 2 to local + // space of body 1 (the GJK algorithm is done in local space of body 1) + final Transform3D body2Tobody1 = _transform1.inverseNew().multiplyNew(_transform2); + // Matrix that transform a direction from local + // space of body 1 into local space of body 2 + final Quaternion rotateToBody2 = _transform2.getOrientation().inverseNew().multiplyNew(_transform1.getOrientation()); + // Get the simplex computed previously by the GJK algorithm + int nbVertices = _simplex.getSimplex(suppPointsA, suppPointsB, points); + // Compute the tolerance + final float tolerance = Constant.FLOAT_EPSILON * _simplex.getMaxLengthSquareOfAPoint(); + // Clear the storing of triangles + triangleStore.clear(); + // Select an action according to the number of points in the simplex + // computed with GJK algorithm in order to obtain an initial polytope for + // The EPA algorithm. + ////Log.info(">>>>>>>>>>>>>>>>>> *** " + nbVertices); + switch (nbVertices) { + case 1: + // Only one point in the simplex (which should be the origin). + // We have a touching contact with zero penetration depth. + // We drop that kind of contact. Therefore, we return false + return; + case 2: { + // The simplex returned by GJK is a line segment d containing the origin. + // We add two additional support points to ruct a hexahedron (two tetrahedron + // glued together with triangle faces. The idea is to compute three different vectors + // v1, v2 and v3 that are orthogonal to the segment d. The three vectors are relatively + // rotated of 120 degree around the d segment. The the three new points to + // ruct the polytope are the three support points in those three directions + // v1, v2 and v3. + // Direction of the segment + final Vector3f d = points[1].lessNew(points[0]).safeNormalizeNew(); + // Choose the coordinate axis from the minimal absolute component of the vector d + final int minAxis = d.abs().getMinAxis(); + // Compute sin(60) + final float sin60 = FMath.sqrt(3.0f) * 0.5f; + // Create a rotation quaternion to rotate the vector v1 to get the vectors + // v2 and v3 + final Quaternion rotationQuat = new Quaternion(d.x * sin60, d.y * sin60, d.z * sin60, 0.5f); + // Compute the vector v1, v2, v3 + final Vector3f v1 = d.cross(new Vector3f(minAxis == 0 ? 1.0f : 0.0f, minAxis == 1 ? 1.0f : 0.0f, minAxis == 2 ? 1.0f : 0.0f)); + final Vector3f v2 = rotationQuat.multiply(v1); + final Vector3f v3 = rotationQuat.multiply(v2); + // Compute the support point in the direction of v1 + suppPointsA[2] = shape1.getLocalSupportPointWithMargin(v1, shape1CachedCollisionData); + suppPointsB[2] = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(v1.multiplyNew(-1)), shape2CachedCollisionData)); + points[2] = suppPointsA[2].lessNew(suppPointsB[2]); + // Compute the support point in the direction of v2 + suppPointsA[3] = shape1.getLocalSupportPointWithMargin(v2, shape1CachedCollisionData); + suppPointsB[3] = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(v2.multiplyNew(-1)), shape2CachedCollisionData)); + points[3] = suppPointsA[3].lessNew(suppPointsB[3]); + // Compute the support point in the direction of v3 + suppPointsA[4] = shape1.getLocalSupportPointWithMargin(v3, shape1CachedCollisionData); + suppPointsB[4] = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(v3.multiplyNew(-1)), shape2CachedCollisionData)); + points[4] = suppPointsA[4].lessNew(suppPointsB[4]); + // Now we have an hexahedron (two tetrahedron glued together). We can simply keep the + // tetrahedron that contains the origin in order that the initial polytope of the + // EPA algorithm is a tetrahedron, which is simpler to deal with. + // If the origin is in the tetrahedron of points 0, 2, 3, 4 + if (isOriginInTetrahedron(points[0], points[2], points[3], points[4]) == 0) { + // We use the point 4 instead of point 1 for the initial tetrahedron + suppPointsA[1] = suppPointsA[4]; + suppPointsB[1] = suppPointsB[4]; + points[1] = points[4]; + } + // If the origin is in the tetrahedron of points 1, 2, 3, 4 + else if (isOriginInTetrahedron(points[1], points[2], points[3], points[4]) == 0) { + // We use the point 4 instead of point 0 for the initial tetrahedron + suppPointsA[0] = suppPointsA[4]; + suppPointsB[0] = suppPointsB[4]; + points[0] = points[4]; + } else { + // The origin is not in the initial polytope + return; + } + // The polytope contains now 4 vertices + nbVertices = 4; + } + case 4: { + // The simplex computed by the GJK algorithm is a tetrahedron. Here we check + // if this tetrahedron contains the origin. If it is the case, we keep it and + // otherwise we remove the wrong vertex of the tetrahedron and go in the case + // where the GJK algorithm compute a simplex of three vertices. + // Check if the tetrahedron contains the origin (or wich is the wrong vertex otherwise) + final int badVertex = isOriginInTetrahedron(points[0], points[1], points[2], points[3]); + // If the origin is in the tetrahedron + if (badVertex == 0) { + // The tetrahedron is a correct initial polytope for the EPA algorithm. + // Therefore, we ruct the tetrahedron. + // Comstruct the 4 triangle faces of the tetrahedron + ////Log.error("befor call: (points, 0, 1, 2)"); + final TriangleEPA face0 = triangleStore.newTriangle(points, 0, 1, 2); + ////Log.error("befor call: (points, 0, 3, 1)"); + final TriangleEPA face1 = triangleStore.newTriangle(points, 0, 3, 1); + ////Log.error("befor call: (points, 0, 2, 3)"); + final TriangleEPA face2 = triangleStore.newTriangle(points, 0, 2, 3); + ////Log.error("befor call: (points, 1, 3, 2)"); + final TriangleEPA face3 = triangleStore.newTriangle(points, 1, 3, 2); + // If the ructed tetrahedron is not correct + if (!((face0 != null) && (face1 != null) && (face2 != null) && (face3 != null) && face0.getDistSquare() > 0.0f && face1.getDistSquare() > 0.0 && face2.getDistSquare() > 0.0f + && face3.getDistSquare() > 0.0)) { + return; + } + // Associate the edges of neighbouring triangle faces + TriangleEPA.link(new EdgeEPA(face0, 0), new EdgeEPA(face1, 2)); + TriangleEPA.link(new EdgeEPA(face0, 1), new EdgeEPA(face3, 2)); + TriangleEPA.link(new EdgeEPA(face0, 2), new EdgeEPA(face2, 0)); + TriangleEPA.link(new EdgeEPA(face1, 0), new EdgeEPA(face2, 2)); + TriangleEPA.link(new EdgeEPA(face1, 1), new EdgeEPA(face3, 0)); + TriangleEPA.link(new EdgeEPA(face2, 1), new EdgeEPA(face3, 1)); + // Add the triangle faces in the candidate heap + addFaceCandidate(face0, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face1, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face2, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face3, triangleHeap, Float.MAX_VALUE); + break; + } + // The tetrahedron contains a wrong vertex (the origin is not inside the tetrahedron) + // Remove the wrong vertex and continue to the next case with the + // three remaining vertices + if (badVertex < 4) { + suppPointsA[badVertex - 1] = suppPointsA[3]; + suppPointsB[badVertex - 1] = suppPointsB[3]; + points[badVertex - 1] = points[3]; + } + // We have removed the wrong vertex + nbVertices = 3; + } + case 3: { + // The GJK algorithm returned a triangle that contains the origin. + // We need two new vertices to create two tetrahedron. The two new + // vertices are the support points in the "n" and "-n" direction + // where "n" is the normal of the triangle. Then, we use only the + // tetrahedron that contains the origin. + // Compute the normal of the triangle + final Vector3f v1 = points[1].lessNew(points[0]); + final Vector3f v2 = points[2].lessNew(points[0]); + final Vector3f n = v1.cross(v2); + ////Log.info(">>>>>>>>>>>>>>>>>>"); + ////Log.info(" v1 = " + v1); + ////Log.info(" v2 = " + v2); + ////Log.info(" n = " + n); + // Compute the two new vertices to obtain a hexahedron + suppPointsA[3] = shape1.getLocalSupportPointWithMargin(n, shape1CachedCollisionData); + suppPointsB[3] = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(n.multiplyNew(-1)), shape2CachedCollisionData)); + points[3] = suppPointsA[3].lessNew(suppPointsB[3]); + ////Log.info(" suppPointsA[3]= " + suppPointsA[3]); + ////Log.info(" suppPointsB[3]= " + suppPointsB[3]); + ////Log.info(" points[3] = " + points[3]); + suppPointsA[4] = shape1.getLocalSupportPointWithMargin(n.multiplyNew(-1), shape1CachedCollisionData); + suppPointsB[4] = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(n), shape2CachedCollisionData)); + points[4] = suppPointsA[4].lessNew(suppPointsB[4]); + ////Log.info(" suppPointsA[4]= " + suppPointsA[4]); + ////Log.info(" suppPointsB[4]= " + suppPointsB[4]); + ////Log.info(" points[4]= " + points[4]); + TriangleEPA face0 = null; + TriangleEPA face1 = null; + TriangleEPA face2 = null; + TriangleEPA face3 = null; + // If the origin is in the first tetrahedron + if (isOriginInTetrahedron(points[0], points[1], points[2], points[3]) == 0) { + // The tetrahedron is a correct initial polytope for the EPA algorithm. + // Therefore, we ruct the tetrahedron. + // Comstruct the 4 triangle faces of the tetrahedron + ////Log.error("befor call: (points, 0, 1, 2)"); + face0 = triangleStore.newTriangle(points, 0, 1, 2); + ////Log.error("befor call: (points, 0, 2, 1)"); + face1 = triangleStore.newTriangle(points, 0, 3, 1); + ////Log.error("befor call: (points, 0, 2, 3)"); + face2 = triangleStore.newTriangle(points, 0, 2, 3); + ////Log.error("befor call: (points, 1, 3, 2)"); + face3 = triangleStore.newTriangle(points, 1, 3, 2); + } else if (isOriginInTetrahedron(points[0], points[1], points[2], points[4]) == 0) { + // The tetrahedron is a correct initial polytope for the EPA algorithm. + // Therefore, we ruct the tetrahedron. + // Comstruct the 4 triangle faces of the tetrahedron + ////Log.error("befor call: (points, 0, 1, 2)"); + face0 = triangleStore.newTriangle(points, 0, 1, 2); + ////Log.error("befor call: (points, 0, 4, 1)"); + face1 = triangleStore.newTriangle(points, 0, 4, 1); + ////Log.error("befor call: (points, 0, 2, 4)"); + face2 = triangleStore.newTriangle(points, 0, 2, 4); + ////Log.error("befor call: (points, 1, 4, 2)"); + face3 = triangleStore.newTriangle(points, 1, 4, 2); + } else { + return; + } + // If the ructed tetrahedron is not correct + if (!(face0 != null && face1 != null && face2 != null && face3 != null && face0.getDistSquare() > 0.0f && face1.getDistSquare() > 0.0f && face2.getDistSquare() > 0.0f + && face3.getDistSquare() > 0.0f)) { + return; + } + // Associate the edges of neighbouring triangle faces + TriangleEPA.link(new EdgeEPA(face0, 0), new EdgeEPA(face1, 2)); + TriangleEPA.link(new EdgeEPA(face0, 1), new EdgeEPA(face3, 2)); + TriangleEPA.link(new EdgeEPA(face0, 2), new EdgeEPA(face2, 0)); + TriangleEPA.link(new EdgeEPA(face1, 0), new EdgeEPA(face2, 2)); + TriangleEPA.link(new EdgeEPA(face1, 1), new EdgeEPA(face3, 0)); + TriangleEPA.link(new EdgeEPA(face2, 1), new EdgeEPA(face3, 1)); + // Add the triangle faces in the candidate heap + addFaceCandidate(face0, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face1, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face2, triangleHeap, Float.MAX_VALUE); + addFaceCandidate(face3, triangleHeap, Float.MAX_VALUE); + nbVertices = 4; + } + break; + } + // At this point, we have a polytope that contains the origin. Therefore, we + // can run the EPA algorithm. + if (triangleHeap.size() == 0) { + return; + } + TriangleEPA triangle = null; + float upperBoundSquarePenDepth = Float.MAX_VALUE; + do { + triangle = triangleHeap.first(); + triangleHeap.remove(triangle); + ////Log.info("rm from heap:"); + int iii = 0; + for (final TriangleEPA elem : triangleHeap) { + ////Log.info(" [" + iii + "] " + elem.getDistSquare()); + ++iii; + } + // If the candidate face in the heap is not obsolete + if (!triangle.getIsObsolete()) { + // If we have reached the maximum number of support points + if (nbVertices == EdgeEPA.MAX_SUPPORT_POINTS) { + assert (false); + break; + } + // Compute the support point of the Minkowski + // difference (A-B) in the closest point direction + suppPointsA[nbVertices] = shape1.getLocalSupportPointWithMargin(triangle.getClosestPoint(), shape1CachedCollisionData); + suppPointsB[nbVertices] = body2Tobody1 + .multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiply(triangle.getClosestPoint().multiplyNew(-1)), shape2CachedCollisionData)); + points[nbVertices] = suppPointsA[nbVertices].lessNew(suppPointsB[nbVertices]); + final int indexNewVertex = nbVertices; + nbVertices++; + // Update the upper bound of the penetration depth + final float wDotv = points[indexNewVertex].dot(triangle.getClosestPoint()); + ////Log.info(" point=" + points[indexNewVertex]); + ////Log.info("close point=" + triangle.getClosestPoint()); + ////Log.info(" ==>" + wDotv); + if (wDotv < 0.0f) { + ////Log.error("depth penetration error " + wDotv); + continue; + } + assert (wDotv >= 0.0f); + final float wDotVSquare = wDotv * wDotv / triangle.getDistSquare(); + if (wDotVSquare < upperBoundSquarePenDepth) { + upperBoundSquarePenDepth = wDotVSquare; + } + // Compute the error + final float error = wDotv - triangle.getDistSquare(); + if (error <= FMath.max(tolerance, GJKAlgorithm.REL_ERROR_SQUARE * wDotv) || points[indexNewVertex].isEqual(points[triangle.get(0)]) + || points[indexNewVertex].isEqual(points[triangle.get(1)]) || points[indexNewVertex].isEqual(points[triangle.get(2)])) { + break; + } + // Now, we compute the silhouette cast by the new vertex. The current triangle + // face will not be in the convex hull. We start the local recursive silhouette + // algorithm from the current triangle face. + int i = triangleStore.getNbTriangles(); + if (!triangle.computeSilhouette(points, indexNewVertex, triangleStore)) { + break; + } + // Add all the new triangle faces computed with the silhouette algorithm + // to the candidates list of faces of the current polytope + while (i != triangleStore.getNbTriangles()) { + final TriangleEPA newTriangle = triangleStore.get(i); + addFaceCandidate(newTriangle, triangleHeap, upperBoundSquarePenDepth); + i++; + } + } + } while (triangleHeap.size() > 0 && triangleHeap.first().getDistSquare() <= upperBoundSquarePenDepth); + // Compute the contact info + final Vector3f tmp = _transform1.getOrientation().multiply(triangle.getClosestPoint()); + _vector.set(tmp); + final Vector3f pALocal = triangle.computeClosestPointOfObject(suppPointsA); + final Vector3f pBLocal = body2Tobody1.inverseNew().multiply(triangle.computeClosestPointOfObject(suppPointsB)); + final Vector3f normal = _vector.safeNormalizeNew(); + final float penetrationDepth = _vector.length(); + assert (penetrationDepth >= 0.0f); + if (normal.length2() < Constant.FLOAT_EPSILON) { + return; + } + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(_shape1Info.proxyShape, _shape2Info.proxyShape, _shape1Info.collisionShape, _shape2Info.collisionShape, normal, penetrationDepth, + pALocal, pBLocal); + _narrowPhaseCallback.notifyContact(_shape1Info.overlappingPair, contactInfo); + } + + /// Initalize the algorithm + public void init() { + + } + + // Decide if the origin is in the tetrahedron. + /// Return 0 if the origin is in the tetrahedron and return the number (1,2,3 or 4) of + /// the vertex that is wrong if the origin is not in the tetrahedron + private int isOriginInTetrahedron(final Vector3f _p1, final Vector3f _p2, final Vector3f _p3, final Vector3f _p4) { + ////Log.error("isOriginInTetrahedron(" + _p1 + ", " + _p2 + ", " + _p3 + ", " + _p4 + ")"); + // Check vertex 1 + final Vector3f normal1 = _p2.lessNew(_p1).cross(_p3.lessNew(_p1)); + if ((normal1.dot(_p1) > 0.0f) == (normal1.dot(_p4) > 0.0)) { + ////Log.error(" ==> 4"); + return 4; + } + // Check vertex 2 + final Vector3f normal2 = _p4.lessNew(_p2).cross(_p3.lessNew(_p2)); + if ((normal2.dot(_p2) > 0.0f) == (normal2.dot(_p1) > 0.0)) { + ////Log.error(" ==> 1"); + return 1; + } + // Check vertex 3 + final Vector3f normal3 = _p4.lessNew(_p3).cross(_p1.lessNew(_p3)); + if ((normal3.dot(_p3) > 0.0f) == (normal3.dot(_p2) > 0.0)) { + ////Log.error(" ==> 2"); + return 2; + } + // Check vertex 4 + final Vector3f normal4 = _p2.lessNew(_p4).cross(_p1.lessNew(_p4)); + if ((normal4.dot(_p4) > 0.0f) == (normal4.dot(_p3) > 0.0)) { + ////Log.error(" ==> 3"); + return 3; + } + ////Log.error(" ==> 0"); + // The origin is in the tetrahedron, we return 0 + return 0; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EdgeEPA.java b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EdgeEPA.java new file mode 100644 index 0000000..5396e82 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/EdgeEPA.java @@ -0,0 +1,138 @@ +package org.atriasoft.ephysics.collision.narrowphase.EPA; + +import org.atriasoft.etk.math.Vector3f; + +/** + * Class EPAAlgorithm + * This class is the implementation of the Expanding Polytope Algorithm (EPA). + * The EPA algorithm computes the penetration depth and contact points between + * two enlarged objects (with margin) where the original objects (without margin) + * intersect. The penetration depth of a pair of intersecting objects A and B is + * the length of a point on the boundary of the Minkowski sum (A-B) closest to the + * origin. The goal of the EPA algorithm is to start with an initial simplex polytope + * that contains the origin and expend it in order to find the point on the boundary + * of (A-B) that is closest to the origin. An initial simplex that contains origin + * has been computed wit GJK algorithm. The EPA Algorithm will extend this simplex + * polytope to find the correct penetration depth. The implementation of the EPA + * algorithm is based on the book "Collision Detection in 3D Environments". + */ +public class EdgeEPA implements Cloneable { + /// Maximum number of support points of the polytope + static public final int MAX_SUPPORT_POINTS = 100; + /// Maximum number of facets of the polytope + static public final int MAX_FACETS = 200; + + // Return the index of the next counter-clockwise edge of the ownver triangle + static public int indexOfNextCounterClockwiseEdge(final int iii) { + return (iii + 1) % 3; + } + + // Return the index of the previous counter-clockwise edge of the ownver triangle + static public int indexOfPreviousCounterClockwiseEdge(final int iii) { + return (iii + 2) % 3; + } + + /// Pointer to the triangle that contains this edge + private TriangleEPA ownerTriangle; + /// Index of the edge in the triangle (between 0 and 2). + /// The edge with index i connect triangle vertices i and (i+1 % 3) + private int index; + + /// Constructor + public EdgeEPA() { + this.ownerTriangle = null; + }; + + /// Copy-ructor + public EdgeEPA(final EdgeEPA obj) { + this.ownerTriangle = obj.ownerTriangle; + this.index = obj.index; + } + + /// Constructor + public EdgeEPA(final TriangleEPA ownerTriangle, final int index) { + this.ownerTriangle = ownerTriangle; + this.index = index; + assert (index >= 0 && index < 3); + } + + @Override + protected EdgeEPA clone() throws CloneNotSupportedException { + return new EdgeEPA(this); + } + + /// Execute the recursive silhouette algorithm from this edge + public boolean computeSilhouette(final Vector3f[] vertices, final int indexNewVertex, final TrianglesStore triangleStore) { + //Log.error("EdgeEPA computeSilhouette ..."); + // If the edge has not already been visited + if (!this.ownerTriangle.getIsObsolete()) { + // If the triangle of this edge is not visible from the given point + if (!this.ownerTriangle.isVisibleFromVertex(vertices, indexNewVertex)) { + //Log.error("EdgeEPA 1 befor call: " + indexNewVertex + ", " + getTargetVertexIndex() + ", " + getSourceVertexIndex()); + final TriangleEPA triangle = triangleStore.newTriangle(vertices, indexNewVertex, getTargetVertexIndex(), getSourceVertexIndex()); + // If the triangle has been created + if (triangle != null) { + TriangleEPA.halfLink(new EdgeEPA(triangle, 1), this); + return true; + } + return false; + } else { + // The current triangle is visible and therefore obsolete + this.ownerTriangle.setIsObsolete(true); + final int backup = triangleStore.getNbTriangles(); + if (!this.ownerTriangle.getAdjacentEdge(indexOfNextCounterClockwiseEdge(this.index)).computeSilhouette(vertices, indexNewVertex, triangleStore)) { + this.ownerTriangle.setIsObsolete(false); + //Log.error("EdgeEPA 2 befor call: " + indexNewVertex + ", " + getTargetVertexIndex() + ", " + getSourceVertexIndex()); + final TriangleEPA triangle = triangleStore.newTriangle(vertices, indexNewVertex, getTargetVertexIndex(), getSourceVertexIndex()); + // If the triangle has been created + if (triangle != null) { + TriangleEPA.halfLink(new EdgeEPA(triangle, 1), this); + return true; + } + return false; + } else if (!this.ownerTriangle.getAdjacentEdge(indexOfPreviousCounterClockwiseEdge(this.index)).computeSilhouette(vertices, indexNewVertex, triangleStore)) { + this.ownerTriangle.setIsObsolete(false); + triangleStore.resize(backup); + //Log.error("EdgeEPA 3 befor call: " + indexNewVertex + ", " + getTargetVertexIndex() + ", " + getSourceVertexIndex()); + final TriangleEPA triangle = triangleStore.newTriangle(vertices, indexNewVertex, getTargetVertexIndex(), getSourceVertexIndex()); + if (triangle != null) { + TriangleEPA.halfLink(new EdgeEPA(triangle, 1), this); + return true; + } + return false; + } + } + } + return true; + } + + /// Return the index of the edge in the triangle + public int getIndex() { + return this.index; + } + + /// Return the pointer to the owner triangle + public TriangleEPA getOwnerTriangle() { + return this.ownerTriangle; + } + + /// Return index of the source vertex of the edge + public int getSourceVertexIndex() { + //return this.ownerTriangle[this.index]; + return this.ownerTriangle.get(this.index); + } + + /// Return the index of the target vertex of the edge + public int getTargetVertexIndex() { + return this.ownerTriangle.get(indexOfNextCounterClockwiseEdge(this.index)); + //return this.ownerTriangle[indexOfNextCounterClockwiseEdge(this.index)]; + } + + /// Assignment + public EdgeEPA set(final EdgeEPA obj) { + this.ownerTriangle = obj.ownerTriangle; + this.index = obj.index; + return this; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TriangleEPA.java b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TriangleEPA.java new file mode 100644 index 0000000..925b518 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TriangleEPA.java @@ -0,0 +1,223 @@ +package org.atriasoft.ephysics.collision.narrowphase.EPA; + +import org.atriasoft.etk.math.Vector3f; + +/** + * Class TriangleEPA + * This class represents a triangle face of the current polytope in the EPA algorithm. + */ +public class TriangleEPA { + /// Make an half link of an edge with another one from another triangle. An half-link + /// between an edge "edge0" and an edge "edge1" represents the fact that "edge1" is an + /// adjacent edge of "edge0" but not the opposite. The opposite edge connection will + /// be made later. + public static void halfLink(final EdgeEPA _edge0, final EdgeEPA _edge1) { + assert (_edge0.getSourceVertexIndex() == _edge1.getTargetVertexIndex() && _edge0.getTargetVertexIndex() == _edge1.getSourceVertexIndex()); + _edge0.getOwnerTriangle().adjacentEdges[_edge0.getIndex()] = _edge1; + } + + public static boolean link(final EdgeEPA _edge0, final EdgeEPA _edge1) { + if (_edge0.getSourceVertexIndex() == _edge1.getTargetVertexIndex() && _edge0.getTargetVertexIndex() == _edge1.getSourceVertexIndex()) { + _edge0.getOwnerTriangle().adjacentEdges[_edge0.getIndex()] = _edge1; + _edge1.getOwnerTriangle().adjacentEdges[_edge1.getIndex()] = _edge0; + return true; + } + return false; + } + + private final int[] indicesVertices = new int[3]; //!< Indices of the vertices y_i of the triangle + private final EdgeEPA[] adjacentEdges = new EdgeEPA[3]; //!< Three adjacent edges of the triangle (edges of other triangles) + private boolean isObsolete; //!< True if the triangle face is visible from the new support point + private float determinant; //!< Determinant + private Vector3f closestPoint; //!< Point v closest to the origin on the affine hull of the triangle + private float lambda1; //!< Lambda1 value such that v = lambda0 * y_0 + lambda1 * y_1 + lambda2 * y_2 + + private float lambda2; //!< Lambda1 value such that v = lambda0 * y_0 + lambda1 * y_1 + lambda2 * y_2 + + private float distSquare; //!< Square distance of the point closest point v to the origin + + /// Constructor + /* + public TriangleEPA() { + this.adjacentEdges[0] = new EdgeEPA(this, 0); + this.adjacentEdges[1] = new EdgeEPA(this, 1); + this.adjacentEdges[2] = new EdgeEPA(this, 2); + this.closestPoint = new Vector3f(); + } + */ + + /// Constructor + public TriangleEPA(final int _indexVertex1, final int _indexVertex2, final int _indexVertex3) { + this.isObsolete = false; + this.indicesVertices[0] = _indexVertex1; + this.indicesVertices[1] = _indexVertex2; + this.indicesVertices[2] = _indexVertex3; + this.adjacentEdges[0] = new EdgeEPA(); + this.adjacentEdges[1] = new EdgeEPA(); + this.adjacentEdges[2] = new EdgeEPA(); + this.closestPoint = new Vector3f(); + } + + /// Private copy-ructor + /* + public TriangleEPA(final TriangleEPA _triangle) { + this.indicesVertices[0] = _triangle.indicesVertices[0]; + this.indicesVertices[1] = _triangle.indicesVertices[1]; + this.indicesVertices[2] = _triangle.indicesVertices[2]; + this.adjacentEdges[0] = _triangle.adjacentEdges[0]; + this.adjacentEdges[1] = _triangle.adjacentEdges[1]; + this.adjacentEdges[2] = _triangle.adjacentEdges[2]; + this.isObsolete = _triangle.isObsolete; + this.determinant = _triangle.determinant; + this.closestPoint = _triangle.closestPoint; + this.lambda1 = _triangle.lambda1; + this.lambda2 = _triangle.lambda2; + this.distSquare = _triangle.distSquare; + } + */ + + /// Compute the point v closest to the origin of this triangle + public boolean computeClosestPoint(final Vector3f[] _vertices) { + final Vector3f p0 = _vertices[this.indicesVertices[0]]; + final Vector3f v1 = _vertices[this.indicesVertices[1]].lessNew(p0); + final Vector3f v2 = _vertices[this.indicesVertices[2]].lessNew(p0); + final float v1Dotv1 = v1.dot(v1); + final float v1Dotv2 = v1.dot(v2); + final float v2Dotv2 = v2.dot(v2); + final float p0Dotv1 = p0.dot(v1); + final float p0Dotv2 = p0.dot(v2); + // Compute determinant + this.determinant = v1Dotv1 * v2Dotv2 - v1Dotv2 * v1Dotv2; + // Compute lambda values + this.lambda1 = p0Dotv2 * v1Dotv2 - p0Dotv1 * v2Dotv2; + this.lambda2 = p0Dotv1 * v1Dotv2 - p0Dotv2 * v1Dotv1; + // If the determinant is positive + if (this.determinant > 0.0f) { + // Compute the closest point v + this.closestPoint = v1.multiplyNew(this.lambda1).add(v2.multiplyNew(this.lambda2)).multiply(1.0f / this.determinant).add(p0); + // Compute the square distance of closest point to the origin + this.distSquare = this.closestPoint.dot(this.closestPoint); + return true; + } + return false; + } + + /// Compute the point of an object closest to the origin + public Vector3f computeClosestPointOfObject(final Vector3f[] _supportPointsOfObject) { + final Vector3f p0 = _supportPointsOfObject[this.indicesVertices[0]].clone(); + final Vector3f tmp1 = _supportPointsOfObject[this.indicesVertices[1]].lessNew(p0).multiply(this.lambda1); + final Vector3f tmp2 = _supportPointsOfObject[this.indicesVertices[2]].lessNew(p0).multiply(this.lambda2); + return p0.add(tmp1.add(tmp2).multiply(1.0f / this.determinant)); + } + + // Execute the recursive silhouette algorithm from this triangle face. + /// The parameter "vertices" is an array that contains the vertices of the current polytope and the + /// parameter "indexNewVertex" is the index of the new vertex in this array. The goal of the + /// silhouette algorithm is to add the new vertex in the polytope by keeping it convex. Therefore, + /// the triangle faces that are visible from the new vertex must be removed from the polytope and we + /// need to add triangle faces where each face contains the new vertex and an edge of the silhouette. + /// The silhouette is the connected set of edges that are part of the border between faces that + /// are seen and faces that are not seen from the new vertex. This method starts from the nearest + /// face from the new vertex, computes the silhouette and create the new faces from the new vertex in + /// order that we always have a convex polytope. The faces visible from the new vertex are set + /// obselete and will not be idered as being a candidate face in the future. + public boolean computeSilhouette(final Vector3f[] _vertices, final int _indexNewVertex, final TrianglesStore _triangleStore) { + final int first = _triangleStore.getNbTriangles(); + // Mark the current triangle as obsolete because it + setIsObsolete(true); + // Execute recursively the silhouette algorithm for the adjacent edges of neighboring + // triangles of the current triangle + final boolean result = this.adjacentEdges[0].computeSilhouette(_vertices, _indexNewVertex, _triangleStore) + && this.adjacentEdges[1].computeSilhouette(_vertices, _indexNewVertex, _triangleStore) && this.adjacentEdges[2].computeSilhouette(_vertices, _indexNewVertex, _triangleStore); + if (result) { + int i, j; + // For each triangle face that contains the new vertex and an edge of the silhouette + for (i = first, j = _triangleStore.getNbTriangles() - 1; i != _triangleStore.getNbTriangles(); j = i++) { + final TriangleEPA triangle = _triangleStore.get(i); + halfLink(triangle.getAdjacentEdge(1), new EdgeEPA(triangle, 1)); + if (!link(new EdgeEPA(triangle, 0), new EdgeEPA(_triangleStore.get(j), 2))) { + return false; + } + } + } + return result; + } + + /// Access operator + public int get(final int _pos) { + assert (_pos >= 0 && _pos < 3); + return this.indicesVertices[_pos]; + } + /// Link an edge with another one. It means that the current edge of a triangle will + /// be associated with the edge of another triangle in order that both triangles + /// are neighbour aint both edges). + + /// Return an adjacent edge of the triangle + public EdgeEPA getAdjacentEdge(final int _index) { + assert (_index >= 0 && _index < 3); + return this.adjacentEdges[_index]; + } + + /// Return the point closest to the origin + public Vector3f getClosestPoint() { + return this.closestPoint; + } + + /// Return the square distance of the closest point to origin + public float getDistSquare() { + return this.distSquare; + } + + /// Return true if the triangle face is obsolete + public boolean getIsObsolete() { + return this.isObsolete; + } + + // Return true if the closest point on affine hull is inside the triangle + public boolean isClosestPointInternalToTriangle() { + return (this.lambda1 >= 0.0f && this.lambda2 >= 0.0 && (this.lambda1 + this.lambda2) <= this.determinant); + } + + /// Return true if the triangle is visible from a given vertex + public boolean isVisibleFromVertex(final Vector3f[] _vertices, final int _index) { + final Vector3f closestToVert = _vertices[_index].lessNew(this.closestPoint); + return (this.closestPoint.dot(closestToVert) > 0.0f); + } + + /// Constructor + public void set(final int _indexVertex1, final int _indexVertex2, final int _indexVertex3) { + this.isObsolete = false; + this.indicesVertices[0] = _indexVertex1; + this.indicesVertices[1] = _indexVertex2; + this.indicesVertices[2] = _indexVertex3; + } + + /// Private assignment operator + public TriangleEPA set(final TriangleEPA _triangle) { + this.indicesVertices[0] = _triangle.indicesVertices[0]; + this.indicesVertices[1] = _triangle.indicesVertices[1]; + this.indicesVertices[2] = _triangle.indicesVertices[2]; + this.adjacentEdges[0] = _triangle.adjacentEdges[0]; + this.adjacentEdges[1] = _triangle.adjacentEdges[1]; + this.adjacentEdges[2] = _triangle.adjacentEdges[2]; + this.isObsolete = _triangle.isObsolete; + this.determinant = _triangle.determinant; + this.closestPoint = _triangle.closestPoint; + this.lambda1 = _triangle.lambda1; + this.lambda2 = _triangle.lambda2; + this.distSquare = _triangle.distSquare; + return this; + } + + /// Set an adjacent edge of the triangle + public void setAdjacentEdge(final int _index, final EdgeEPA _edge) { + assert (_index >= 0 && _index < 3); + this.adjacentEdges[_index] = _edge; + } + + /// Set the isObsolete value + public void setIsObsolete(final boolean _isObsolete) { + this.isObsolete = _isObsolete; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TrianglesStore.java b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TrianglesStore.java new file mode 100644 index 0000000..478650e --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/EPA/TrianglesStore.java @@ -0,0 +1,70 @@ +package org.atriasoft.ephysics.collision.narrowphase.EPA; + +import java.util.ArrayList; +import java.util.List; + +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.etk.math.Vector3f; + +public class TrianglesStore { + private static int MAX_TRIANGLES = 200; + + public static void shrinkTo(final List list, final int newSize) { + final int size = list.size(); + if (newSize >= size) { + return; + } + for (int i = newSize; i < size; i++) { + list.remove(list.size() - 1); + } + } + + private final List triangles = new ArrayList<>(); + + /// Clear all the storage + public void clear() { + this.triangles.clear(); + } + + /// Access operator + public TriangleEPA get(final int _id) { + return this.triangles.get(_id); + } + + /// Return the number of triangles + public int getNbTriangles() { + return this.triangles.size(); + } + + /// Return the last triangle + public TriangleEPA last() { + return this.triangles.get(this.triangles.size() - 1); + } + + /// Create a new triangle + public TriangleEPA newTriangle(final Vector3f[] _vertices, final int _v0, final int _v1, final int _v2) { + //Log.info("newTriangle: " + _v0 + ", " + _v1 + ", " + _v2); + // If we have not reached the maximum number of triangles + if (this.triangles.size() < MAX_TRIANGLES) { + + final TriangleEPA tmp = new TriangleEPA(_v0, _v1, _v2); + if (!tmp.computeClosestPoint(_vertices)) { + return null; + } + this.triangles.add(tmp); + //Log.info(" ==> retrurn Triangle: " + tmp.get(0) + ", " + tmp.get(1) + ", " + tmp.get(2)); + return tmp; + } + // We are at the limit (internal) + return null; + + } + + /// Set the number of triangles + public void resize(final int _backup) { + if (_backup > this.triangles.size()) { + Log.error("RESIZE BIGGER : " + _backup + " > " + this.triangles.size()); + } + shrinkTo(this.triangles, _backup); + } +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.java b/src/org/atriasoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.java new file mode 100644 index 0000000..6df7bc9 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.java @@ -0,0 +1,435 @@ +package org.atriasoft.ephysics.collision.narrowphase.GJK; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.CollisionShapeInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseAlgorithm; +import org.atriasoft.ephysics.collision.narrowphase.NarrowPhaseCallback; +import org.atriasoft.ephysics.collision.narrowphase.EPA.EPAAlgorithm; +import org.atriasoft.ephysics.collision.shapes.CacheData; +import org.atriasoft.ephysics.collision.shapes.ConvexShape; +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class implements a narrow-phase collision detection algorithm. This + * algorithm uses the ISA-GJK algorithm and the EPA algorithm. This + * implementation is based on the implementation discussed in the book + * "Collision Detection in Interactive 3D Environments" by Gino van den Bergen. + * This method implements the Hybrid Technique for calculating the + * penetration depth. The two objects are enlarged with a small margin. If + * the object intersects in their margins, the penetration depth is quickly + * computed using the GJK algorithm on the original objects (without margin). + * If the original objects (without margin) intersect, we run again the GJK + * algorithm on the enlarged objects (with margin) to compute simplex + * polytope that contains the origin and give it to the EPA (Expanding + * Polytope Algorithm) to compute the correct penetration depth between the + * enlarged objects. + */ +public class GJKAlgorithm extends NarrowPhaseAlgorithm { + public static final float REL_ERROR = 0.001f; + + public static final float REL_ERROR_SQUARE = REL_ERROR * REL_ERROR; + public static final int MAX_ITERATIONS_GJK_RAYCAST = 32; + private final EPAAlgorithm algoEPA; //!< EPA Algorithm + /// This method runs the GJK algorithm on the two enlarged objects (with margin) + /// to compute a simplex polytope that contains the origin. The two objects are + /// assumed to intersect in the original objects (without margin). Therefore such + /// a polytope must exist. Then, we give that polytope to the EPA algorithm to + /// compute the correct penetration depth and contact points of the enlarged objects. + + public GJKAlgorithm(final CollisionDetection collisionDetection) { + super(collisionDetection); + this.algoEPA = new EPAAlgorithm(); + this.algoEPA.init(); + } + + private void computePenetrationDepthForEnlargedObjects(final CollisionShapeInfo shape1Info, final Transform3D transform1, final CollisionShapeInfo shape2Info, final Transform3D transform2, + final NarrowPhaseCallback narrowPhaseCallback, final Vector3f v) { + //Log.info("computePenetrationDepthForEnlargedObjects..."); + final Simplex simplex = new Simplex(); + float distSquare = Float.MAX_VALUE; + float prevDistSquare; + assert (shape1Info.collisionShape.isConvex()); + assert (shape2Info.collisionShape.isConvex()); + final ConvexShape shape1 = (ConvexShape) (shape1Info.collisionShape); + final ConvexShape shape2 = (ConvexShape) (shape2Info.collisionShape); + final Object shape1CachedCollisionData = shape1Info.cachedCollisionData; + final Object shape2CachedCollisionData = shape2Info.cachedCollisionData; + //Log.info(" transform1=" + transform1); + //Log.info(" transform2=" + transform2); + + // Transform3D a point from local space of body 2 to local space + // of body 1 (the GJK algorithm is done in local space of body 1) + final Transform3D body2ToBody1 = transform1.inverseNew().multiplyNew(transform2); + // Matrix that transform a direction from local space of body 1 into local space of body 2 + final Matrix3f rotateToBody2 = transform2.getOrientation().getMatrix().transposeNew().multiply(transform1.getOrientation().getMatrix()); + //Log.info(" body2ToBody1=" + body2ToBody1); + //Log.info(" rotateToBody2=" + rotateToBody2); + //Log.info(" v=" + v); + do { + // Compute the support points for the enlarged object A and B + final Vector3f suppA = shape1.getLocalSupportPointWithMargin(v.multiplyNew(-1), (CacheData) shape1CachedCollisionData); + //Log.info(" suppA=" + suppA); + final Vector3f suppB = body2ToBody1.multiplyNew(shape2.getLocalSupportPointWithMargin(rotateToBody2.multiplyNew(v), (CacheData) shape2CachedCollisionData)); + //Log.info(" suppB=" + suppB); + // Compute the support point for the Minkowski difference A-B + final Vector3f w = suppA.lessNew(suppB); + final float vDotw = v.dot(w); + //Log.info(" vDotw=" + vDotw); + // If the enlarge objects do not intersect + if (vDotw > 0.0f) { + //Log.info(" ==> ret 1"); + // No intersection, we return + return; + } + // Add the new support point to the simplex + simplex.addPoint(w, suppA, suppB); + if (simplex.isAffinelyDependent()) { + //Log.info(" ==> ret 2"); + return; + } + if (!simplex.computeClosestPoint(v)) { + //Log.info(" ==> ret 3"); + return; + } + // Store and update the square distance + prevDistSquare = distSquare; + //Log.info(" distSquare=" + distSquare); + distSquare = v.length2(); + //Log.info(" distSquare=" + distSquare); + if (prevDistSquare - distSquare <= Constant.FLOAT_EPSILON * prevDistSquare) { + //Log.info(" ==> ret 4"); + return; + } + } while (!simplex.isFull() && distSquare > Constant.FLOAT_EPSILON * simplex.getMaxLengthSquareOfAPoint()); + // Give the simplex computed with GJK algorithm to the EPA algorithm + // which will compute the correct penetration depth and contact points + // between the two enlarged objects + //Log.info(" ==> ret 5"); + this.algoEPA.computePenetrationDepthAndContactPoints(simplex, shape1Info, transform1, shape2Info, transform2, v, narrowPhaseCallback); + } + + /// Ray casting algorithm agains a convex collision shape using the GJK Algorithm + /// This method implements the GJK ray casting algorithm described by Gino Van Den Bergen in + /// "Ray Casting against General Convex Objects with Application to Continuous Collision Detection". + public boolean raycast(final Ray ray, final ProxyShape proxyShape, final RaycastInfo raycastInfo) { + assert (proxyShape.getCollisionShape().isConvex()); + final ConvexShape shape = (ConvexShape) (proxyShape.getCollisionShape()); + final Object shapeCachedCollisionData = proxyShape.getCachedCollisionData(); + Vector3f suppA; // Current lower bound point on the ray (starting at ray's origin) + Vector3f suppB; // Support point on the collision shape + final float machineEpsilonSquare = Constant.FLOAT_EPSILON * Constant.FLOAT_EPSILON; + final float epsilon = 0.0001f; + // Convert the ray origin and direction into the local-space of the collision shape + final Vector3f rayDirection = ray.point2.lessNew(ray.point1); + // If the points of the segment are two close, return no hit + if (rayDirection.length2() < machineEpsilonSquare) { + return false; + } + Vector3f w; + // Create a simplex set + final Simplex simplex = new Simplex(); + + Vector3f n = new Vector3f(0.0f, 0.0f, 0.0f); + float lambda = 0.0f; + suppA = ray.point1; // Current lower bound point on the ray (starting at ray's origin) + suppB = shape.getLocalSupportPointWithoutMargin(rayDirection, (CacheData) shapeCachedCollisionData); + final Vector3f v = suppA.lessNew(suppB); + float vDotW, vDotR; + float distSquare = v.length2(); + int nbIterations = 0; + // GJK Algorithm loop + while (distSquare > epsilon && nbIterations < MAX_ITERATIONS_GJK_RAYCAST) { + // Compute the support points + suppB = shape.getLocalSupportPointWithoutMargin(v, (CacheData) shapeCachedCollisionData); + w = suppA.lessNew(suppB); + vDotW = v.dot(w); + if (vDotW > 0.0f) { + vDotR = v.dot(rayDirection); + if (vDotR >= -machineEpsilonSquare) { + return false; + } else { + // We have found a better lower bound for the hit point aint the ray + lambda = lambda - vDotW / vDotR; + suppA = rayDirection.multiplyNew(lambda).add(ray.point1); + w = suppA.lessNew(suppB); + n = v; + } + } + // Add the new support point to the simplex + if (!simplex.isPointInSimplex(w)) { + simplex.addPoint(w, suppA, suppB); + } + // Compute the closest point + if (simplex.computeClosestPoint(v)) { + distSquare = v.length2(); + } else { + distSquare = 0.0f; + } + // If the current lower bound distance is larger than the maximum raycasting distance + if (lambda > ray.maxFraction) { + return false; + } + nbIterations++; + } + // If the origin was inside the shape, we return no hit + if (lambda < Constant.FLOAT_EPSILON) { + return false; + } + // Compute the closet points of both objects (without the margins) + final Vector3f pointA = new Vector3f(); + final Vector3f pointB = new Vector3f(); + simplex.computeClosestPointsOfAandB(pointA, pointB); + // A raycast hit has been found, we fill in the raycast info + raycastInfo.hitFraction = lambda; + raycastInfo.worldPoint = pointB; + raycastInfo.body = proxyShape.getBody(); + raycastInfo.proxyShape = proxyShape; + if (n.length2() >= machineEpsilonSquare) { + // The normal vector is valid + raycastInfo.worldNormal = n; + } else { + // Degenerated normal vector, we return a zero normal vector + raycastInfo.worldNormal = new Vector3f(0.0f, 0.0f, 0.0f); + } + return true; + } + + @Override + public void testCollision(final CollisionShapeInfo shape1Info, final CollisionShapeInfo shape2Info, final NarrowPhaseCallback callback) { + //Log.error("================================================="); + //Log.error(" shape1Info=" + shape1Info.shapeToWorldTransform); + //Log.error(" shape2Info=" + shape2Info.shapeToWorldTransform); + Vector3f suppA = new Vector3f(); // Support point of object A + Vector3f suppB = new Vector3f(); // Support point of object B + Vector3f w = new Vector3f(); // Support point of Minkowski difference A-B + Vector3f pA = new Vector3f(); // Closest point of object A + Vector3f pB = new Vector3f(); // Closest point of object B + float vDotw; + float prevDistSquare; + assert (shape1Info.collisionShape.isConvex()); + assert (shape2Info.collisionShape.isConvex()); + final ConvexShape shape1 = (ConvexShape) (shape1Info.collisionShape); + final ConvexShape shape2 = (ConvexShape) (shape2Info.collisionShape); + final CacheData shape1CachedCollisionData = (CacheData) shape1Info.cachedCollisionData; + final CacheData shape2CachedCollisionData = (CacheData) shape2Info.cachedCollisionData; + // Get the local-space to world-space transforms + final Transform3D transform1 = shape1Info.shapeToWorldTransform.clone(); + final Transform3D transform2 = shape2Info.shapeToWorldTransform.clone(); + // Transform3D a point from local space of body 2 to local + // space of body 1 (the GJK algorithm is done in local space of body 1) + final Transform3D body2Tobody1 = transform1.inverseNew().multiplyNew(transform2); + // Matrix that transform a direction from local + // space of body 1 into local space of body 2 + final Matrix3f rotateToBody2 = transform2.getOrientation().getMatrix().transposeNew().multiplyNew(transform1.getOrientation().getMatrix()); + // Initialize the margin (sum of margins of both objects) + final float margin = shape1.getMargin() + shape2.getMargin(); + final float marginSquare = margin * margin; + assert (margin > 0.0f); + // Create a simplex set + final Simplex simplex = new Simplex(); + // Get the previous point V (last cached separating axis) + final Vector3f cacheSeparatingAxis = this.currentOverlappingPair.getCachedSeparatingAxis().clone(); + // Initialize the upper bound for the square distance + float distSquare = Float.MAX_VALUE; + + //Log.error(" T1=" + transform1 + " T2=" + transform2); + //Log.error(" BT1=" + body2Tobody1 + " RT2=" + rotateToBody2); + //Log.error(" M=" + FMath.floatToString(margin) + " M2=" + FMath.floatToString(marginSquare)); + //Log.error(" v=" + cacheSeparatingAxis); + + do { + //Log.error("------------------"); + // Compute the support points for original objects (without margins) A and B + suppA = shape1.getLocalSupportPointWithoutMargin(cacheSeparatingAxis.multiplyNew(-1.0f), shape1CachedCollisionData); + suppB = body2Tobody1.multiplyNew(shape2.getLocalSupportPointWithoutMargin(rotateToBody2.multiplyNew(cacheSeparatingAxis), shape2CachedCollisionData)); + // Compute the support point for the Minkowski difference A-B + w = suppA.lessNew(suppB); + vDotw = cacheSeparatingAxis.dot(w); + //Log.error(" suppA=" + suppA); + //Log.error(" suppB=" + suppB); + //Log.error(" w=" + w); + // If the enlarge objects (with margins) do not intersect + if (vDotw > 0.0f && vDotw * vDotw > distSquare * marginSquare) { + // Cache the current separating axis for frame coherence + this.currentOverlappingPair.setCachedSeparatingAxis(cacheSeparatingAxis); + // No intersection, we return + return; + } + // If the objects intersect only in the margins + if (simplex.isPointInSimplex(w) || distSquare - vDotw <= distSquare * REL_ERROR_SQUARE) { + //Log.error("11111111 "); + // Compute the closet points of both objects (without the margins) + simplex.computeClosestPointsOfAandB(pA, pB); + // Project those two points on the margins to have the closest points of both + // object with the margins + final float dist = FMath.sqrt(distSquare); + assert (dist > 0.0f); + pA = pA.lessNew(cacheSeparatingAxis.multiplyNew(shape1.getMargin() / dist)); + pB = body2Tobody1.inverseNew().multiplyNew(cacheSeparatingAxis.multiplyNew(shape2.getMargin() / dist).add(pB)); + // Compute the contact info + final Vector3f normal = transform1.getOrientation().multiply(cacheSeparatingAxis.safeNormalizeNew().multiply(-1)); + final float penetrationDepth = margin - dist; + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0f) { + return; + } + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, shape2Info.collisionShape, normal, penetrationDepth, + pA, pB); + callback.notifyContact(shape1Info.overlappingPair, contactInfo); + // There is an intersection, therefore we return + return; + } + // Add the new support point to the simplex + simplex.addPoint(w, suppA, suppB); + // If the simplex is affinely dependent + if (simplex.isAffinelyDependent()) { + //Log.error("222222 "); + // Compute the closet points of both objects (without the margins) + simplex.computeClosestPointsOfAandB(pA, pB); + // Project those two points on the margins to have the closest points of both + // object with the margins + final float dist = FMath.sqrt(distSquare); + assert (dist > 0.0f); + pA = pA.lessNew(cacheSeparatingAxis.multiplyNew(shape1.getMargin() / dist)); + pB = body2Tobody1.inverseNew().multiply(cacheSeparatingAxis.multiplyNew(shape2.getMargin() / dist).add(pB)); + // Compute the contact info + final Vector3f normal = transform1.getOrientation().multiply(cacheSeparatingAxis.safeNormalizeNew().multiply(-1)); + final float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0f) { + //Log.info("penetration depth " + penetrationDepth); + return; + } + + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, shape2Info.collisionShape, normal, penetrationDepth, + pA, pB); + callback.notifyContact(shape1Info.overlappingPair, contactInfo); + // There is an intersection, therefore we return + return; + } + // Compute the point of the simplex closest to the origin + // If the computation of the closest point fail + if (!simplex.computeClosestPoint(cacheSeparatingAxis)) { + //Log.error("3333333333 "); + // Compute the closet points of both objects (without the margins) + simplex.computeClosestPointsOfAandB(pA, pB); + // Project those two points on the margins to have the closest points of both + // object with the margins + final float dist = FMath.sqrt(distSquare); + assert (dist > 0.0f); + pA = pA.lessNew(cacheSeparatingAxis.multiplyNew(shape1.getMargin() / dist)); + pB = body2Tobody1.inverseNew().multiply(cacheSeparatingAxis.multiplyNew(shape2.getMargin() / dist).add(pB)); + // Compute the contact info + final Vector3f normal = transform1.getOrientation().multiply(cacheSeparatingAxis.safeNormalizeNew().multiply(-1)); + final float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0f) { + return; + } + + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, shape2Info.collisionShape, normal, penetrationDepth, + pA, pB); + callback.notifyContact(shape1Info.overlappingPair, contactInfo); + // There is an intersection, therefore we return + return; + } + // Store and update the squared distance of the closest point + prevDistSquare = distSquare; + distSquare = cacheSeparatingAxis.length2(); + // If the distance to the closest point doesn't improve a lot + if (prevDistSquare - distSquare <= Constant.FLOAT_EPSILON * prevDistSquare) { + //Log.error("444444444 "); + simplex.backupClosestPointInSimplex(cacheSeparatingAxis); + + // Get the new squared distance + distSquare = cacheSeparatingAxis.length2(); + // Compute the closet points of both objects (without the margins) + simplex.computeClosestPointsOfAandB(pA, pB); + // Project those two points on the margins to have the closest points of both + // object with the margins + final float dist = FMath.sqrt(distSquare); + assert (dist > 0.0f); + pA = pA.lessNew(cacheSeparatingAxis.multiplyNew(shape1.getMargin() / dist)); + pB = body2Tobody1.inverseNew().multiply(cacheSeparatingAxis.multiplyNew(shape2.getMargin() / dist).add(pB)); + // Compute the contact info + final Vector3f normal = transform1.getOrientation().multiply(cacheSeparatingAxis.safeNormalizeNew().multiply(-1)); + final float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0f) { + return; + } + + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, shape2Info.collisionShape, normal, penetrationDepth, + pA, pB); + callback.notifyContact(shape1Info.overlappingPair, contactInfo); + // There is an intersection, therefore we return + return; + } + } while (!simplex.isFull() && distSquare > Constant.FLOAT_EPSILON * simplex.getMaxLengthSquareOfAPoint()); + // The objects (without margins) intersect. Therefore, we run the GJK algorithm + // again but on the enlarged objects to compute a simplex polytope that contains + // the origin. Then, we give that simplex polytope to the EPA algorithm to compute + // the correct penetration depth and contact points between the enlarged objects. + computePenetrationDepthForEnlargedObjects(shape1Info, transform1, shape2Info, transform2, callback, cacheSeparatingAxis); + } + + /// Use the GJK Algorithm to find if a point is inside a convex collision shape + public boolean testPointInside(final Vector3f localPoint, final ProxyShape proxyShape) { + Vector3f suppA = new Vector3f(); // Support point of object A + Vector3f w = new Vector3f(); // Support point of Minkowski difference A-B + float prevDistSquare; + assert (proxyShape.getCollisionShape().isConvex()); + final ConvexShape shape = (ConvexShape) (proxyShape.getCollisionShape()); + final CacheData shapeCachedCollisionData = (CacheData) proxyShape.getCachedCollisionData(); + // Support point of object B (object B is a single point) + final Vector3f suppB = new Vector3f(localPoint); + // Create a simplex set + final Simplex simplex = new Simplex(); + + // Initial supporting direction + final Vector3f v = new Vector3f(1, 1, 1); + // Initialize the upper bound for the square distance + float distSquare = Float.MAX_VALUE; + do { + // Compute the support points for original objects (without margins) A and B + suppA = shape.getLocalSupportPointWithoutMargin(v.multiplyNew(-1), shapeCachedCollisionData); + // Compute the support point for the Minkowski difference A-B + w = suppA.lessNew(suppB); + // Add the new support point to the simplex + simplex.addPoint(w, suppA, suppB); + // If the simplex is affinely dependent + if (simplex.isAffinelyDependent()) { + return false; + } + // Compute the point of the simplex closest to the origin + // If the computation of the closest point fail + if (!simplex.computeClosestPoint(v)) { + return false; + } + // Store and update the squared distance of the closest point + prevDistSquare = distSquare; + distSquare = v.length2(); + // If the distance to the closest point doesn't improve a lot + if (prevDistSquare - distSquare <= Constant.FLOAT_EPSILON * prevDistSquare) { + return false; + } + } while (!simplex.isFull() && distSquare > Constant.FLOAT_EPSILON * simplex.getMaxLengthSquareOfAPoint()); + // The point is inside the collision shape + return true; + } +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/GJK/Simplex.java b/src/org/atriasoft/ephysics/collision/narrowphase/GJK/Simplex.java new file mode 100644 index 0000000..b948221 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/GJK/Simplex.java @@ -0,0 +1,444 @@ +package org.atriasoft.ephysics.collision.narrowphase.GJK; + +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a simplex which is a set of 3D points. This + * class is used in the GJK algorithm. This implementation is based on + * the implementation discussed in the book "Collision Detection in 3D + * Environments". This class implements the Johnson's algorithm for + * computing the point of a simplex that is closest to the origin and also + * the smallest simplex needed to represent that closest point. + */ +public class Simplex { + + private class Array2f { + private final float[] data; + private final int sizeX; + private final int sizeY; + + public Array2f(final int sizeX, final int sizeY) { + this.sizeX = sizeX; + this.sizeY = sizeY; + this.data = new float[sizeX * sizeY]; + } + + public float get(final int xxx, final int yyy) { + return this.data[yyy * this.sizeX + xxx]; + } + + public int getSizeX() { + return this.sizeX; + } + + public int getSizeY() { + return this.sizeY; + } + + public void set(final int xxx, final int yyy, final float data) { + this.data[yyy * this.sizeX + xxx] = data; + } + } + + private class Array2Vector3f { + private final Vector3f[] data; + private final int sizeX; + private final int sizeY; + + public Array2Vector3f(final int sizeX, final int sizeY) { + this.sizeX = sizeX; + this.sizeY = sizeY; + this.data = new Vector3f[sizeX * sizeY]; + } + + public Vector3f get(final int xxx, final int yyy) { + return this.data[yyy * this.sizeX + xxx]; + } + + public int getSizeX() { + return this.sizeX; + } + + public int getSizeY() { + return this.sizeY; + } + + public void set(final int xxx, final int yyy, final Vector3f data) { + this.data[yyy * this.sizeX + xxx] = data; + } + } + + /// Current points + private final Vector3f[] points = new Vector3f[4]; + + /// pointsLengthSquare[i] = (points[i].length)^2 + private final float[] pointsLengthSquare = new float[4]; + + /// Maximum length of pointsLengthSquare[i] + private float maxLengthSquare; + + /// Support points of object A in local coordinates + private final Vector3f[] suppPointsA = new Vector3f[4]; + + /// Support points of object B in local coordinates + private final Vector3f[] suppPointsB = new Vector3f[4]; + + /// diff[i][j] contains points[i] - points[j] + private final Array2Vector3f diffLength = new Array2Vector3f(4, 4); + + /// Cached determinant values + private final Array2f det = new Array2f(16, 4); + + /// norm[i][j] = (diff[i][j].length())^2 + private final Array2f normalSquare = new Array2f(4, 4); + + /// 4 bits that identify the current points of the simplex + /// For instance, 0101 means that points[1] and points[3] are in the simplex + private int bitsCurrentSimplex = 0; + + /// Number between 1 and 4 that identify the last found support point + private int lastFound = 0; + + /// Position of the last found support point (lastFoundBit = 0x1 << lastFound) + private int lastFoundBit = 0; + + /// allint = bitsCurrentSimplex | lastFoundBit; + private int allBits = 0; + + // -------------------- Methods -------------------- // + + /// Constructor + public Simplex() { + + } + + /// Add a new support point of (A-B) into the simplex. + /// suppPointA : support point of object A in a direction -v + /// suppPointB : support point of object B in a direction v + /// point : support point of object (A-B) => point = suppPointA - suppPointB + public void addPoint(final Vector3f point, final Vector3f suppPointA, final Vector3f suppPointB) { + assert (!isFull()); + //Log.warning("simplex: addPoint(" + point + ", " + suppPointA + ", " + suppPointA + ")"); + this.lastFound = 0; + this.lastFoundBit = 0x1; + + // Look for the bit corresponding to one of the four point that is not in + // the current simplex + while (overlap(this.bitsCurrentSimplex, this.lastFoundBit)) { + this.lastFound++; + this.lastFoundBit <<= 1; + } + //Log.warning(" this.lastFound " + this.lastFound); + //Log.warning(" this.lastFoundBit " + this.lastFoundBit); + + assert (this.lastFound < 4); + + // Add the point into the simplex + this.points[this.lastFound] = point; + this.pointsLengthSquare[this.lastFound] = point.dot(point); + this.allBits = this.bitsCurrentSimplex | this.lastFoundBit; + //Log.warning(" this.allBits " + this.allBits); + + // Update the cached values + updateCache(); + + // Compute the cached determinant values + computeDeterminants(); + + // Add the support points of objects A and B + this.suppPointsA[this.lastFound] = suppPointA; + this.suppPointsB[this.lastFound] = suppPointB; + } + + /// Backup the closest point + public void backupClosestPointInSimplex(final Vector3f point) { + float minDistSquare = Float.MAX_VALUE; + for (int bit = this.allBits; bit != 0x0; bit--) { + if (isSubset(bit, this.allBits) && isProperSubset(bit)) { + final Vector3f u = computeClosestPointForSubset(bit); + final float distSquare = u.dot(u); + if (distSquare < minDistSquare) { + minDistSquare = distSquare; + this.bitsCurrentSimplex = bit; + point.set(u); + } + } + } + } + + /// Compute the closest point to the origin of the current simplex. + /// This method executes the Jonhnson's algorithm for computing the point + /// "v" of simplex that is closest to the origin. The method returns true + /// if a closest point has been found. + public boolean computeClosestPoint(final Vector3f vvv) { + // For each possible simplex set + for (int subset = this.bitsCurrentSimplex; subset != 0x0; subset--) { + // If the simplex is a subset of the current simplex and is valid for the Johnson's + // algorithm test + if (isSubset(subset, this.bitsCurrentSimplex) && isValidSubset(subset | this.lastFoundBit)) { + this.bitsCurrentSimplex = subset | this.lastFoundBit; // Add the last added point to the current simplex + vvv.set(computeClosestPointForSubset(this.bitsCurrentSimplex)); // Compute the closest point in the simplex + return true; + } + } + + // If the simplex that contains only the last added point is valid for the Johnson's algorithm test + if (isValidSubset(this.lastFoundBit)) { + this.bitsCurrentSimplex = this.lastFoundBit; // Set the current simplex to the set that contains only the last added point + this.maxLengthSquare = this.pointsLengthSquare[this.lastFound]; // Update the maximum square length + vvv.set(this.points[this.lastFound]); // The closest point of the simplex "v" is the last added point + return true; + } + + // The algorithm failed to found a point + return false; + } + + /// Return the closest point "v" in the convex hull of a subset of points + private Vector3f computeClosestPointForSubset(final int subset) { + final Vector3f vvv = new Vector3f(0.0f, 0.0f, 0.0f); // Closet point v = sum(lambda_i * points[i]) + this.maxLengthSquare = 0.0f; + float deltaX = 0.0f; // deltaX = sum of all det[subset][i] + // For each four point in the possible simplex set + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + // If the current point is in the subset + if (overlap(subset, bit)) { + // deltaX = sum of all det[subset][i] + deltaX += this.det.get(subset, iii); + if (this.maxLengthSquare < this.pointsLengthSquare[iii]) { + this.maxLengthSquare = this.pointsLengthSquare[iii]; + } + // Closest point v = sum(lambda_i * points[i]) + vvv.add(this.points[iii].multiplyNew(this.det.get(subset, iii))); + } + } + assert (deltaX > 0.0f); + // Return the closet point "v" in the convex hull for the given subset + return vvv.multiply(1.0f / deltaX); + } + + /// Compute the closest points "pA" and "pB" of object A and B. + /// The points are computed as follows : + /// pA = sum(lambda_i * a_i) where "a_i" are the support points of object A + /// pB = sum(lambda_i * b_i) where "b_i" are the support points of object B + /// with lambda_i = deltaX_i / deltaX + public void computeClosestPointsOfAandB(final Vector3f pA, final Vector3f pB) { + float deltaX = 0.0f; + pA.set(0.0f, 0.0f, 0.0f); + pB.set(0.0f, 0.0f, 0.0f); + // For each four points in the possible simplex set + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + // If the current point is part of the simplex + if (overlap(this.bitsCurrentSimplex, bit)) { + deltaX += this.det.get(this.bitsCurrentSimplex, iii); + pA.add(this.suppPointsA[iii].multiplyNew(this.det.get(this.bitsCurrentSimplex, iii))); + pB.add(this.suppPointsB[iii].multiplyNew(this.det.get(this.bitsCurrentSimplex, iii))); + } + } + + assert (deltaX > 0.0f); + final float factor = 1.0f / deltaX; + pA.multiply(factor); + pB.multiply(factor); + } + + /// Compute the cached determinant values + private void computeDeterminants() { + this.det.set(this.lastFoundBit, this.lastFound, 1.0f); + //Log.warning("simplex: computeDeterminants() " + this.det.get(this.lastFoundBit, this.lastFound)); + // If the current simplex is not empty + if (!isEmpty()) { + // For each possible four points in the simplex set + for (int iii = 0, bitI = 0x1; iii < 4; iii++, bitI <<= 1) { + // If the current point is in the simplex + if (overlap(this.bitsCurrentSimplex, bitI)) { + final int bit2 = bitI | this.lastFoundBit; + float tmpp = this.diffLength.get(this.lastFound, iii).dot(this.points[this.lastFound]); + this.det.set(bit2, iii, tmpp); + tmpp = this.diffLength.get(iii, this.lastFound).dot(this.points[iii]); + this.det.set(bit2, this.lastFound, tmpp); + for (int jjj = 0, bitJ = 0x1; jjj < iii; jjj++, bitJ <<= 1) { + if (overlap(this.bitsCurrentSimplex, bitJ)) { + final int bit3 = bitJ | bit2; + int kkk = this.normalSquare.get(iii, jjj) < this.normalSquare.get(this.lastFound, jjj) ? iii : this.lastFound; + float tmp2 = this.det.get(bit2, iii) * this.diffLength.get(kkk, jjj).dot(this.points[iii]) + + this.det.get(bit2, this.lastFound) * this.diffLength.get(kkk, jjj).dot(this.points[this.lastFound]); + this.det.set(bit3, jjj, tmp2); + kkk = this.normalSquare.get(jjj, iii) < this.normalSquare.get(this.lastFound, iii) ? jjj : this.lastFound; + tmp2 = this.det.get(bitJ | this.lastFoundBit, jjj) * this.diffLength.get(kkk, iii).dot(this.points[jjj]) + + this.det.get(bitJ | this.lastFoundBit, this.lastFound) * this.diffLength.get(kkk, iii).dot(this.points[this.lastFound]); + this.det.set(bit3, iii, tmp2); + kkk = this.normalSquare.get(iii, this.lastFound) < this.normalSquare.get(jjj, this.lastFound) ? iii : jjj; + tmp2 = this.det.get(bitJ | bitI, jjj) * this.diffLength.get(kkk, this.lastFound).dot(this.points[jjj]) + + this.det.get(bitJ | bitI, iii) * this.diffLength.get(kkk, this.lastFound).dot(this.points[iii]); + this.det.set(bit3, this.lastFound, tmp2); + } + } + } + } + + if (this.allBits == 0xf) { + int kkk; + + kkk = this.normalSquare.get(1, 0) < this.normalSquare.get(2, 0) ? (this.normalSquare.get(1, 0) < this.normalSquare.get(3, 0) ? 1 : 3) + : (this.normalSquare.get(2, 0) < this.normalSquare.get(3, 0) ? 2 : 3); + float tmp3 = this.det.get(0xe, 1) * this.diffLength.get(kkk, 0).dot(this.points[1]) + this.det.get(0xe, 2) * this.diffLength.get(kkk, 0).dot(this.points[2]) + + this.det.get(0xe, 3) * this.diffLength.get(kkk, 0).dot(this.points[3]); + this.det.set(0xf, 0, tmp3); + + kkk = this.normalSquare.get(0, 1) < this.normalSquare.get(2, 1) ? (this.normalSquare.get(0, 1) < this.normalSquare.get(3, 1) ? 0 : 3) + : (this.normalSquare.get(2, 1) < this.normalSquare.get(3, 1) ? 2 : 3); + tmp3 = this.det.get(0xd, 0) * this.diffLength.get(kkk, 1).dot(this.points[0]) + this.det.get(0xd, 2) * this.diffLength.get(kkk, 1).dot(this.points[2]) + + this.det.get(0xd, 3) * this.diffLength.get(kkk, 1).dot(this.points[3]); + this.det.set(0xf, 1, tmp3); + + kkk = this.normalSquare.get(0, 2) < this.normalSquare.get(1, 2) ? (this.normalSquare.get(0, 2) < this.normalSquare.get(3, 2) ? 0 : 3) + : (this.normalSquare.get(1, 2) < this.normalSquare.get(3, 2) ? 1 : 3); + tmp3 = this.det.get(0xb, 0) * this.diffLength.get(kkk, 2).dot(this.points[0]) + this.det.get(0xb, 1) * this.diffLength.get(kkk, 2).dot(this.points[1]) + + this.det.get(0xb, 3) * this.diffLength.get(kkk, 2).dot(this.points[3]); + this.det.set(0xf, 2, tmp3); + + kkk = this.normalSquare.get(0, 3) < this.normalSquare.get(1, 3) ? (this.normalSquare.get(0, 3) < this.normalSquare.get(2, 3) ? 0 : 2) + : (this.normalSquare.get(1, 3) < this.normalSquare.get(2, 3) ? 1 : 2); + tmp3 = this.det.get(0x7, 0) * this.diffLength.get(kkk, 3).dot(this.points[0]) + this.det.get(0x7, 1) * this.diffLength.get(kkk, 3).dot(this.points[1]) + + this.det.get(0x7, 2) * this.diffLength.get(kkk, 3).dot(this.points[2]); + this.det.set(0xf, 3, tmp3); + } + } + } + + /// Return the maximum squared length of a point + public float getMaxLengthSquareOfAPoint() { + return this.maxLengthSquare; + } + + /// Return the points of the simplex + public int getSimplex(final Vector3f[] suppPointsA, final Vector3f[] suppPointsB, final Vector3f[] points) { + int nbVertices = 0; + // For each four point in the possible simplex + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + // If the current point is in the simplex + if (overlap(this.bitsCurrentSimplex, bit)) { + // Store the points + suppPointsA[nbVertices] = this.suppPointsA[nbVertices].clone(); + suppPointsB[nbVertices] = this.suppPointsB[nbVertices].clone(); + points[nbVertices] = this.points[nbVertices].clone(); + nbVertices++; + } + } + // Return the number of points in the simplex + return nbVertices; + } + + /// Return true if the set is affinely dependent + /// A set if affinely dependent if a point of the set + /// is an affine combination of other points in the set + public boolean isAffinelyDependent() { + float sum = 0.0f; + // For each four point of the possible simplex set + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + if (overlap(this.allBits, bit)) { + sum += this.det.get(this.allBits, iii); + } + } + return (sum <= 0.0f); + } + + /// Return true if the simplex is empty + public boolean isEmpty() { + return (this.bitsCurrentSimplex == 0x0); + } + + /// Return true if the simplex contains 4 points + public boolean isFull() { + return (this.bitsCurrentSimplex == 0xf); + } + + /// Return true if the point is in the simplex + public boolean isPointInSimplex(final Vector3f point) { + // For each four possible points in the simplex + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + // Check if the current point is in the simplex + if (overlap(this.allBits, bit) && point == this.points[iii]) { + return true; + } + } + return false; + } + + /// Return true if the subset is a proper subset. + /// A proper subset X is a subset where for all point "y_i" in X, we have + /// detX_i value bigger than zero + private boolean isProperSubset(final int subset) { + // For each four point of the possible simplex set + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + if (overlap(subset, bit) && this.det.get(subset, iii) <= 0.0f) { + return false; + } + } + return true; + } + + /// Return true if the bits of "b" is a subset of the bits of "a" + private boolean isSubset(final int a, final int b) { + return ((a & b) == a); + } + + /// Return true if the subset is a valid one for the closest point computation. + /// A subset X is valid if : + /// 1. delta(X)_i > 0 for each "i" in I_x and + /// 2. delta(X U {y_j})_j <= 0 for each "j" not in I_x_ + private boolean isValidSubset(final int subset) { + // For each four point in the possible simplex set + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + if (overlap(this.allBits, bit)) { + // If the current point is in the subset + if (overlap(subset, bit)) { + // If one delta(X)_i is smaller or equal to zero + if (this.det.get(subset, iii) <= 0.0f) { + // The subset is not valid + return false; + } + } + // If the point is not in the subset and the value delta(X U {y_j})_j + // is bigger than zero + else if (this.det.get(subset | bit, iii) > 0.0f) { + // The subset is not valid + return false; + } + } + } + return true; + } + + /// Return true if some bits of "a" overlap with bits of "b" + private boolean overlap(final int a, final int b) { + return ((a & b) != 0x0); + } + + /// Update the cached values used during the GJK algorithm + private void updateCache() { + //Log.warning("simplex: update Cache()"); + // For each of the four possible points of the simplex + for (int iii = 0, bit = 0x1; iii < 4; iii++, bit <<= 1) { + //Log.warning("simplex: iii=" + iii); + //Log.warning("simplex: bit=" + bit); + + // If the current points is in the simplex + if (overlap(this.bitsCurrentSimplex, bit)) { + //Log.warning("simplex: ==> overlap"); + // Compute the distance between two points in the possible simplex set + final Vector3f tmp = this.points[iii].lessNew(this.points[this.lastFound]); + this.diffLength.set(iii, this.lastFound, tmp); + final Vector3f tmp2 = tmp.multiplyNew(-1); + this.diffLength.set(this.lastFound, iii, tmp2); + // Compute the squared length of the vector + // distances from points in the possible simplex set + final float normal = tmp.dot(tmp); + this.normalSquare.set(iii, this.lastFound, normal); + this.normalSquare.set(this.lastFound, iii, normal); + } + } + } + +} diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.java b/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.java new file mode 100644 index 0000000..5796a56 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.java @@ -0,0 +1,32 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.CollisionShapeInfo; +import org.atriasoft.ephysics.engine.OverlappingPair; + +/** + * @breif It is the base class for a narrow-phase collision + * detection algorithm. The goal of the narrow phase algorithm is to + * compute information about the contact between two proxy shapes. + */ +public abstract class NarrowPhaseAlgorithm { + + protected CollisionDetection collisionDetection; //!< Pointer to the collision detection object + protected OverlappingPair currentOverlappingPair; //!< Overlapping pair of the bodies currently tested for collision + + /// Constructor + public NarrowPhaseAlgorithm(final CollisionDetection collisionDetection) { + this.currentOverlappingPair = null; + this.collisionDetection = null; + this.collisionDetection = collisionDetection; + } + + /// Set the current overlapping pair of bodies + public void setCurrentOverlappingPair(final OverlappingPair overlappingPair) { + this.currentOverlappingPair = overlappingPair; + } + + /// Compute a contact info if the two bounding volume collide + public abstract void testCollision(final CollisionShapeInfo _shape1Info, CollisionShapeInfo _shape2Info, NarrowPhaseCallback _narrowPhaseCallback); + +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseCallback.java b/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseCallback.java new file mode 100644 index 0000000..86c4ed4 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/NarrowPhaseCallback.java @@ -0,0 +1,14 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import org.atriasoft.ephysics.constraint.ContactPointInfo; +import org.atriasoft.ephysics.engine.OverlappingPair; + +/** + * It is the base class for a narrow-phase collision + * callback class. + */ +public interface NarrowPhaseCallback { + /// Called by a narrow-phase collision algorithm when a new contact has been found + void notifyContact(OverlappingPair _overlappingPair, ContactPointInfo _contactInfo); + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.java b/src/org/atriasoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.java new file mode 100644 index 0000000..a703378 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.java @@ -0,0 +1,50 @@ +package org.atriasoft.ephysics.collision.narrowphase; + +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.CollisionShapeInfo; +import org.atriasoft.ephysics.collision.shapes.SphereShape; +import org.atriasoft.ephysics.constraint.ContactPointInfo; + +/** + * It is used to compute the narrow-phase collision detection + * between two sphere collision shapes. + */ +public class SphereVsSphereAlgorithm extends NarrowPhaseAlgorithm { + + public SphereVsSphereAlgorithm(final CollisionDetection collisionDetection) { + super(collisionDetection); + } + + @Override + public void testCollision(final CollisionShapeInfo _shape1Info, final CollisionShapeInfo _shape2Info, final NarrowPhaseCallback _narrowPhaseCallback) { + // Get the sphere collision shapes + final SphereShape sphereShape1 = (SphereShape) _shape1Info.collisionShape; + final SphereShape sphereShape2 = (SphereShape) _shape2Info.collisionShape; + // Get the local-space to world-space transforms + final Transform3D transform1 = _shape1Info.shapeToWorldTransform; + final Transform3D transform2 = _shape2Info.shapeToWorldTransform; + // Compute the distance between the centers + final Vector3f vectorBetweenCenters = transform2.getPosition().lessNew(transform1.getPosition()); + final float squaredDistanceBetweenCenters = vectorBetweenCenters.length2(); + // Compute the sum of the radius + final float sumRadius = sphereShape1.getRadius() + sphereShape2.getRadius(); + // If the sphere collision shapes intersect + if (squaredDistanceBetweenCenters <= sumRadius * sumRadius) { + final Vector3f centerSphere2InBody1LocalSpace = transform1.inverseNew().multiply(transform2.getPosition()); + final Vector3f centerSphere1InBody2LocalSpace = transform2.inverseNew().multiply(transform1.getPosition()); + final Vector3f intersectionOnBody1 = centerSphere2InBody1LocalSpace.safeNormalizeNew().multiply(sphereShape1.getRadius()); + final Vector3f intersectionOnBody2 = centerSphere1InBody2LocalSpace.safeNormalizeNew().multiply(sphereShape2.getRadius()); + final float penetrationDepth = sumRadius - FMath.sqrt(squaredDistanceBetweenCenters); + + // Create the contact info object + final ContactPointInfo contactInfo = new ContactPointInfo(_shape1Info.proxyShape, _shape2Info.proxyShape, _shape1Info.collisionShape, _shape2Info.collisionShape, + vectorBetweenCenters.safeNormalizeNew(), penetrationDepth, intersectionOnBody1, intersectionOnBody2); + // Notify about the new contact + _narrowPhaseCallback.notifyContact(_shape1Info.overlappingPair, contactInfo); + } + } +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/AABB.java b/src/org/atriasoft/ephysics/collision/shapes/AABB.java new file mode 100644 index 0000000..8bb0397 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/AABB.java @@ -0,0 +1,322 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a bounding volume of type "Axis Aligned Bounding Box". It's a box where all the edges are + * always aligned with the world coordinate system. The AABB is defined by the this.inimum and this.aximum world coordinates of + * the three axis. + * + * @author Jason Sorensen + */ +public class AABB { + + /** + * @brief Create and return an AABB for a triangle + * @param[in] _trianglePoints List of 3 point od a triangle + * @return An AABB box + */ + public static AABB createAABBForTriangle(final Vector3f[] _trianglePoints) { + final Vector3f minCoords = new Vector3f(_trianglePoints[0].x, _trianglePoints[0].y, _trianglePoints[0].z); + final Vector3f maxCoords = new Vector3f(_trianglePoints[0].x, _trianglePoints[0].y, _trianglePoints[0].z); + if (_trianglePoints[1].x < minCoords.x) { + minCoords.setX(_trianglePoints[1].x); + } + if (_trianglePoints[1].y < minCoords.y) { + minCoords.setY(_trianglePoints[1].y); + } + if (_trianglePoints[1].z < minCoords.z) { + minCoords.setZ(_trianglePoints[1].z); + } + if (_trianglePoints[2].x < minCoords.x) { + minCoords.setX(_trianglePoints[2].x); + } + if (_trianglePoints[2].y < minCoords.y) { + minCoords.setY(_trianglePoints[2].y); + } + if (_trianglePoints[2].z < minCoords.z) { + minCoords.setZ(_trianglePoints[2].z); + } + if (_trianglePoints[1].x > maxCoords.x) { + maxCoords.setX(_trianglePoints[1].x); + } + if (_trianglePoints[1].y > maxCoords.y) { + maxCoords.setY(_trianglePoints[1].y); + } + if (_trianglePoints[1].z > maxCoords.z) { + maxCoords.setZ(_trianglePoints[1].z); + } + if (_trianglePoints[2].x > maxCoords.x) { + maxCoords.setX(_trianglePoints[2].x); + } + if (_trianglePoints[2].y > maxCoords.y) { + maxCoords.setY(_trianglePoints[2].y); + } + if (_trianglePoints[2].z > maxCoords.z) { + maxCoords.setZ(_trianglePoints[2].z); + } + return new AABB(minCoords, maxCoords); + } + + // Maximum world coordinates of the AABB on the x,y and z axis + private final Vector3f maxCoordinates; + + // Minimum world coordinates of the AABB on the x,y and z axis + private final Vector3f minCoordinates; + + /** + * @brief default contructor + */ + public AABB() { + this.maxCoordinates = new Vector3f(); + this.minCoordinates = new Vector3f(); + } + + /** + * @brief contructor Whit sizes + * @param[in] _minCoordinates Minimum coordinates + * @param[in] _maxCoordinates Maximum coordinates + */ + public AABB(final Vector3f minCoordinates, final Vector3f maxCoordinates) { + this.maxCoordinates = maxCoordinates; + this.minCoordinates = minCoordinates; + } + + /** + * @brief Return true if the current AABB contains the AABB given in parameter + * @param[in] _aabb AABB box that is contains in the current. + * @return true The parameter in contained inside + */ + public boolean contains(final AABB _aabb) { + boolean isInside = true; + isInside = isInside && this.minCoordinates.x <= _aabb.minCoordinates.x; + isInside = isInside && this.minCoordinates.y <= _aabb.minCoordinates.y; + isInside = isInside && this.minCoordinates.z <= _aabb.minCoordinates.z; + isInside = isInside && this.maxCoordinates.x >= _aabb.maxCoordinates.x; + isInside = isInside && this.maxCoordinates.y >= _aabb.maxCoordinates.y; + isInside = isInside && this.maxCoordinates.z >= _aabb.maxCoordinates.z; + return isInside; + } + + /** + * @brief Return true if a point is inside the AABB + * @param[in] _point Point to check. + * @return true The point in contained inside + */ + public boolean contains(final Vector3f _point) { + return _point.x >= this.minCoordinates.x - Constant.FLOAT_EPSILON && _point.x <= this.maxCoordinates.x + Constant.FLOAT_EPSILON && _point.y >= this.minCoordinates.y - Constant.FLOAT_EPSILON + && _point.y <= this.maxCoordinates.y + Constant.FLOAT_EPSILON && _point.z >= this.minCoordinates.z - Constant.FLOAT_EPSILON + && _point.z <= this.maxCoordinates.z + Constant.FLOAT_EPSILON; + } + + /** + * @brief Get the center point of the AABB box + * @return The 3D position of the center + */ + public Vector3f getCenter() { + return new Vector3f(this.minCoordinates).add(this.maxCoordinates).multiply(0.5f); + } + + /** + * @brief Get the size of the AABB in the three dimension x, y and z + * @return the AABB 3D size + */ + public Vector3f getExtent() { + return this.maxCoordinates.lessNew(this.minCoordinates); + } + + /** + * @brief Return the maximum coordinates of the AABB + * @return The 3d maximum coordonates + */ + public Vector3f getMax() { + return this.maxCoordinates; + } + + /** + * @brief Get the minimum coordinates of the AABB + * @return The 3d minimum coordonates + */ + public Vector3f getMin() { + return this.minCoordinates; + } + + /** + * @brief Get the volume of the AABB + * @return The 3D volume. + */ + public float getVolume() { + final Vector3f diff = this.maxCoordinates.lessNew(this.minCoordinates); + return (diff.x * diff.y * diff.z); + } + + /** + * @brief Inflate each side of the AABB by a given size + * @param[in] _dx Inflate X size + * @param[in] _dy Inflate Y size + * @param[in] _dz Inflate Z size + */ + public void inflate(final float _dx, final float _dy, final float _dz) { + this.maxCoordinates.add(new Vector3f(_dx, _dy, _dz)); + this.minCoordinates.less(new Vector3f(_dx, _dy, _dz)); + } + + /** + * @brief Replace the current AABB with a new AABB that is the union of two AABBs in parameters + * @param[in] _aabb1 first AABB box to merge with _aabb2. + * @param[in] _aabb2 second AABB box to merge with _aabb1. + */ + public void mergeTwoAABBs(final AABB _aabb1, final AABB _aabb2) { + this.minCoordinates.setX(FMath.min(_aabb1.minCoordinates.x, _aabb2.minCoordinates.x)); + this.minCoordinates.setY(FMath.min(_aabb1.minCoordinates.y, _aabb2.minCoordinates.y)); + this.minCoordinates.setZ(FMath.min(_aabb1.minCoordinates.z, _aabb2.minCoordinates.z)); + this.maxCoordinates.setX(FMath.max(_aabb1.maxCoordinates.x, _aabb2.maxCoordinates.x)); + this.maxCoordinates.setY(FMath.max(_aabb1.maxCoordinates.y, _aabb2.maxCoordinates.y)); + this.maxCoordinates.setZ(FMath.max(_aabb1.maxCoordinates.z, _aabb2.maxCoordinates.z)); + } + + /** + * @brief Merge the AABB in parameter with the current one + * @param[in] _aabb Other AABB box to merge. + */ + public void mergeWithAABB(final AABB _aabb) { + this.minCoordinates.setX(FMath.min(this.minCoordinates.x, _aabb.minCoordinates.x)); + this.minCoordinates.setY(FMath.min(this.minCoordinates.y, _aabb.minCoordinates.y)); + this.minCoordinates.setZ(FMath.min(this.minCoordinates.z, _aabb.minCoordinates.z)); + this.maxCoordinates.setX(FMath.max(this.maxCoordinates.x, _aabb.maxCoordinates.x)); + this.maxCoordinates.setY(FMath.max(this.maxCoordinates.y, _aabb.maxCoordinates.y)); + this.maxCoordinates.setZ(FMath.max(this.maxCoordinates.z, _aabb.maxCoordinates.z)); + } + + /** + * @brief Set the maximum coordinates of the AABB + * @param[in] _max The 3d maximum coordonates + */ + public void setMax(final Vector3f max) { + this.maxCoordinates.set(max); + } + + /** + * @brief Set the minimum coordinates of the AABB + * @param[in] _min The 3d minimum coordonates + */ + public void setMin(final Vector3f min) { + this.minCoordinates.set(min); + } + + /** + * @brief Return true if the current AABB is overlapping with the AABB in argument + * Two AABBs overlap if they overlap in the three x, y and z axis at the same time + * @param[in] _aabb Other AABB box to check. + * @return true Collision detected + * @return false Not collide + */ + public boolean testCollision(final AABB aabb) { + if (this == aabb) { + ///Log.info("test : AABB ==> same object "); + return true; + } + //Log.info("test : " + this + " && " + aabb); + if (this.maxCoordinates.getX() < aabb.minCoordinates.getX() || aabb.maxCoordinates.getX() < this.minCoordinates.getX()) { + return false; + } + if (this.maxCoordinates.getZ() < aabb.minCoordinates.getZ() || aabb.maxCoordinates.getZ() < this.minCoordinates.getZ()) { + return false; + } + if (this.maxCoordinates.getY() < aabb.minCoordinates.getY() || aabb.maxCoordinates.getY() < this.minCoordinates.getY()) { + return false; + } + //Log.info("detect collision "); + return true; + } + + /** + * @brief check if the AABB of a triangle intersects the AABB + * @param[in] _trianglePoints List of 3 point od a triangle + * @return true The triangle is contained in the Box + */ + public boolean testCollisionTriangleAABB(final Vector3f[] _trianglePoints) { + if (FMath.min(_trianglePoints[0].x, _trianglePoints[1].x, _trianglePoints[2].x) > this.maxCoordinates.x) { + return false; + } + if (FMath.min(_trianglePoints[0].y, _trianglePoints[1].y, _trianglePoints[2].y) > this.maxCoordinates.y) { + return false; + } + if (FMath.min(_trianglePoints[0].z, _trianglePoints[1].z, _trianglePoints[2].z) > this.maxCoordinates.z) { + return false; + } + if (FMath.max(_trianglePoints[0].x, _trianglePoints[1].x, _trianglePoints[2].x) < this.minCoordinates.x) { + return false; + } + if (FMath.max(_trianglePoints[0].y, _trianglePoints[1].y, _trianglePoints[2].y) < this.minCoordinates.y) { + return false; + } + if (FMath.max(_trianglePoints[0].z, _trianglePoints[1].z, _trianglePoints[2].z) < this.minCoordinates.z) { + return false; + } + return true; + } + + /* + * @brief check if the ray intersects the AABB + * This method use the line vs AABB raycasting technique described in + * Real-time Collision Detection by Christer Ericson. + * @param[in] _ray Ray to test + * @return true The raytest intersect the AABB box + */ + public boolean testRayIntersect(final Ray ray) { + final Vector3f point2 = ray.point2.lessNew(ray.point1).multiply(ray.maxFraction).add(ray.point1); + final Vector3f e = this.maxCoordinates.lessNew(this.minCoordinates); + final Vector3f d = point2.lessNew(ray.point1); + final Vector3f m = ray.point1.addNew(point2).less(this.minCoordinates).less(this.maxCoordinates); + // Test if the AABB face normals are separating axis + float adx = FMath.abs(d.x); + if (FMath.abs(m.x) > e.x + adx) { + return false; + } + float ady = FMath.abs(d.y); + if (FMath.abs(m.y) > e.y + ady) { + return false; + } + float adz = FMath.abs(d.z); + if (FMath.abs(m.z) > e.z + adz) { + return false; + } + // Add in an epsilon term to counteract arithmetic errors when segment is + // (near) parallel to a coordinate axis (see text for detail) + final float epsilon = 0.00001f; + adx += epsilon; + ady += epsilon; + adz += epsilon; + // Test if the cross products between face normals and ray direction are + // separating axis + if (FMath.abs(m.y * d.z - m.z * d.y) > e.y * adz + e.z * ady) { + return false; + } + if (FMath.abs(m.z * d.x - m.x * d.z) > e.x * adz + e.z * adx) { + return false; + } + if (FMath.abs(m.x * d.y - m.y * d.x) > e.x * ady + e.y * adx) { + return false; + } + // No separating axis has been found + return true; + } + + @Override + public String toString() { + return "AABB [min=" + this.minCoordinates + ", max=" + this.maxCoordinates + "]"; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/BoxShape.java b/src/org/atriasoft/ephysics/collision/shapes/BoxShape.java new file mode 100644 index 0000000..0a5572a --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/BoxShape.java @@ -0,0 +1,170 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a 3D box shape. Those axis are unit length. The three extents are half-widths of the box along + * the three axis x, y, z local axis. The "transform" of the corresponding rigid body will give an orientation and a + * position to the box. This collision shape uses an extra margin distance around it for collision detection purpose. + * The default margin is 4cm (if your units are meters, which is recommended). In case, you want to simulate small + * objects (smaller than the margin distance), you might want to reduce the margin by specifying your own margin + * distance using the "margin" parameter in the constructor of the box shape. Otherwise, it is recommended to use the + * default margin distance by not using the "margin" parameter in the constructor. + * + * @author Jason Sorensen + */ +public class BoxShape extends ConvexShape { + + // Extent sizes of the box in the x, y and z direction + private final Vector3f extent; + + // Copy-constructor + public BoxShape(final BoxShape shape) { + super(CollisionShapeType.BOX, shape.margin); + this.extent = shape.extent.clone(); + } + + // Constructor + public BoxShape(final Vector3f extent) { + this(extent, Defaults.OBJECT_MARGIN); + } + + public BoxShape(final Vector3f extent, final float margin) { + super(CollisionShapeType.BOX, margin); + assert (extent.getX() > 0.0f && extent.getX() > margin); + assert (extent.getY() > 0.0f && extent.getY() > margin); + assert (extent.getZ() > 0.0f && extent.getZ() > margin); + this.extent = extent.lessNew(margin); + } + + @Override + public BoxShape clone() { + return new BoxShape(this); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f tensor, final float _mass) { + final float factor = (1.0f / 3.0f) * _mass; + final Vector3f realExtent = this.extent.addNew(new Vector3f(this.margin, this.margin, this.margin)); + final float xSquare = realExtent.x * realExtent.x; + final float ySquare = realExtent.y * realExtent.y; + final float zSquare = realExtent.z * realExtent.z; + tensor.set(factor * (ySquare + zSquare), 0.0f, 0.0f, 0.0f, factor * (xSquare + zSquare), 0.0f, 0.0f, 0.0f, factor * (xSquare + ySquare)); + } + + public Vector3f getExtent() { + return (new Vector3f(this.margin, this.margin, this.margin)).add(this.extent); + } + + @Override + public void getLocalBounds(final Vector3f _min, final Vector3f _max) { + // Maximum bounds + _max.set(this.extent.x + this.margin, this.extent.y + this.margin, this.extent.z + this.margin); + // Minimum bounds + _min.set(-_max.x, -_max.y, -_max.z); + } + + /* + protected size_t getSizeInBytes() { + return sizeof(BoxShape); + } + */ + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + //Log.error("getLocalSupportPointWithoutMargin(" + _direction); + //Log.error(" extends = " + this.extent); + return new Vector3f(_direction.x < 0.0 ? -this.extent.x : this.extent.x, _direction.y < 0.0 ? -this.extent.y : this.extent.y, _direction.z < 0.0 ? -this.extent.z : this.extent.z); + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f rayDirection = _ray.point2.less(_ray.point1); + float tMin = Float.MIN_VALUE; + float tMax = Float.MAX_VALUE; + Vector3f normalDirection = new Vector3f(0, 0, 0); + Vector3f currentNormal = new Vector3f(0, 0, 0); + // For each of the three slabs + for (int iii = 0; iii < 3; ++iii) { + // If ray is parallel to the slab + if (FMath.abs(rayDirection.get(iii)) < Constant.FLOAT_EPSILON) { + // If the ray's origin is not inside the slab, there is no hit + if (_ray.point1.get(iii) > this.extent.get(iii) || _ray.point1.get(iii) < -this.extent.get(iii)) { + return false; + } + } else { + // Compute the intersection of the ray with the near and far plane of the slab + final float oneOverD = 1.0f / rayDirection.get(iii); + float t1 = (-this.extent.get(iii) - _ray.point1.get(iii)) * oneOverD; + float t2 = (this.extent.get(iii) - _ray.point1.get(iii)) * oneOverD; + currentNormal.x = (iii == 0) ? -this.extent.get(iii) : 0.0f; + currentNormal.y = (iii == 1) ? -this.extent.get(iii) : 0.0f; + currentNormal.z = (iii == 2) ? -this.extent.get(iii) : 0.0f; + // Swap t1 and t2 if need so that t1 is intersection with near plane and + // t2 with far plane + if (t1 > t2) { + final float ttt = t2; + t2 = t1; + t1 = ttt; + currentNormal = currentNormal.multiply(-1); + } + // Compute the intersection of the of slab intersection interval with previous slabs + if (t1 > tMin) { + tMin = t1; + normalDirection = currentNormal; + } + tMax = FMath.min(tMax, t2); + // If tMin is larger than the maximum raycasting fraction, we return no hit + if (tMin > _ray.maxFraction) { + return false; + } + // If the slabs intersection is empty, there is no hit + if (tMin > tMax) { + return false; + } + } + } + // If tMin is negative, we return no hit + if (tMin < 0.0f || tMin > _ray.maxFraction) { + return false; + } + if (normalDirection.isZero()) { + return false; + } + // The ray longersects the three slabs, we compute the hit point + final Vector3f localHitPoint = rayDirection.multiplyNew(tMin).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = tMin; + _raycastInfo.worldPoint = localHitPoint; + _raycastInfo.worldNormal = normalDirection; + return true; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.extent.divide(this.scaling).multiply(_scaling); + super.setLocalScaling(_scaling); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + return (_localPoint.x < this.extent.x && _localPoint.x > -this.extent.x && _localPoint.y < this.extent.y && _localPoint.y > -this.extent.y && _localPoint.z < this.extent.z + && _localPoint.z > -this.extent.z); + } + +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/CacheData.java b/src/org/atriasoft/ephysics/collision/shapes/CacheData.java new file mode 100644 index 0000000..b147dab --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/CacheData.java @@ -0,0 +1,10 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2020-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +public class CacheData { + public Object data = null; +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/CapsuleShape.java b/src/org/atriasoft/ephysics/collision/shapes/CapsuleShape.java new file mode 100644 index 0000000..da2926e --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/CapsuleShape.java @@ -0,0 +1,284 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a capsule collision shape that is defined around the Y axis. A capsule shape can be seen as the + * convex hull of two spheres. The capsule shape is defined by its radius (radius of the two spheres of the capsule) and + * its height (distance between the centers of the two spheres). This collision shape does not have an explicit object + * margin distance. The margin is implicitly the radius and height of the shape. Therefore, no need to specify an object + * margin for a capsule shape. + * + * @author Jason Sorensen + */ +public class CapsuleShape extends ConvexShape { + // Half height of the capsule (height = distance between the centers of the two spheres) + private float halfHeight; + + // Copy-constructor + public CapsuleShape(final CapsuleShape shape) { + super(CollisionShapeType.CAPSULE, shape.margin); + this.halfHeight = shape.halfHeight; + } + + // Constructor + public CapsuleShape(final float radius, final float height) { + // TODO: Should radius really be the margin for a capsule? Seems like a bug. + super(CollisionShapeType.CAPSULE, radius); + this.halfHeight = height * 0.5f; + } + + @Override + public CapsuleShape clone() { + return new CapsuleShape(this); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + // The inertia tensor formula for a capsule can be found in : Game Engine Gems, Volume 1 + final float height = this.halfHeight + this.halfHeight; + final float radiusSquare = this.margin * this.margin; + final float heightSquare = height * height; + final float radiusSquareDouble = radiusSquare + radiusSquare; + final float factor1 = 2.0f * this.margin / (4.0f * this.margin + 3.0f * height); + final float factor2 = 3.0f * height / (4.0f * this.margin + 3.0f * height); + final float sum1 = 0.4f * radiusSquareDouble; + final float sum2 = 0.75f * height * this.margin + 0.5f * heightSquare; + final float sum3 = 0.25f * radiusSquare + 1.0f / 12.0f * heightSquare; + final float IxxAndzz = factor1 * _mass * (sum1 + sum2) + factor2 * _mass * sum3; + final float Iyy = factor1 * _mass * sum1 + factor2 * _mass * 0.25f * radiusSquareDouble; + _tensor.set(IxxAndzz, 0.0f, 0.0f, 0.0f, Iyy, 0.0f, 0.0f, 0.0f, IxxAndzz); + } + + // Return the height of the capsule + public float getHeight() { + return this.halfHeight + this.halfHeight; + } + + @Override + public void getLocalBounds(final Vector3f min, final Vector3f max) { + // Maximum bounds + max.setX(this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(this.margin); + // Minimum bounds + min.setX(-max.x); + min.setY(-max.y); + min.setZ(-max.z); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + // Support point top sphere + final float dotProductTop = this.halfHeight * _direction.y; + // Support point bottom sphere + final float dotProductBottom = -this.halfHeight * _direction.y; + // Return the point with the maximum dot product + if (dotProductTop > dotProductBottom) { + return new Vector3f(0, this.halfHeight, 0); + } + return new Vector3f(0, -this.halfHeight, 0); + } + + // Get the radius of the capsule + public float getRadius() { + return this.margin; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f n = _ray.point2.lessNew(_ray.point1); + final float epsilon = 0.01f; + final Vector3f p = new Vector3f(0.0f, -this.halfHeight, 0.0f); + final Vector3f q = new Vector3f(0.0f, this.halfHeight, 0.0f); + final Vector3f d = q.lessNew(p); + final Vector3f m = _ray.point1.lessNew(p); + float t; + final float mDotD = m.dot(d); + final float nDotD = n.dot(d); + final float dDotD = d.dot(d); + // Test if the segment is outside the cylinder + final float vec1DotD = _ray.point1.lessNew(new Vector3f(0.0f, -this.halfHeight - this.margin, 0.0f)).dot(d); + if (vec1DotD < 0.0f && vec1DotD + nDotD < 0.0f) { + return false; + } + final float ddotDExtraCaps = 2.0f * this.margin * d.y; + if (vec1DotD > dDotD + ddotDExtraCaps && vec1DotD + nDotD > dDotD + ddotDExtraCaps) { + return false; + } + final float nDotN = n.dot(n); + final float mDotN = m.dot(n); + final float a = dDotD * nDotN - nDotD * nDotD; + final float k = m.dot(m) - this.margin * this.margin; + final float c = dDotD * k - mDotD * mDotD; + // If the ray is parallel to the capsule axis + if (FMath.abs(a) < epsilon) { + // If the origin is outside the surface of the capusle's cylinder, we return no hit + if (c > 0.0f) { + return false; + } + // Here we know that the segment longersect an endcap of the capsule + // If the ray longersects with the "p" endcap of the capsule + if (mDotD < 0.0f) { + // Check longersection between the ray and the "p" sphere endcap of the capsule + final Vector3f hitLocalPoint = new Vector3f(); + final Float hitFraction = 0.0f; + if (raycastWithSphereEndCap(_ray.point1, _ray.point2, p, _ray.maxFraction, hitLocalPoint, hitFraction)) { + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = hitFraction; + _raycastInfo.worldPoint = hitLocalPoint; + final Vector3f normalDirection = hitLocalPoint.lessNew(p); + _raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else if (mDotD > dDotD) { // If the ray longersects with the "q" endcap of the cylinder + // Check longersection between the ray and the "q" sphere endcap of the capsule + final Vector3f hitLocalPoint = new Vector3f(); + final Float hitFraction = 0.0f; + if (raycastWithSphereEndCap(_ray.point1, _ray.point2, q, _ray.maxFraction, hitLocalPoint, hitFraction)) { + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = hitFraction; + _raycastInfo.worldPoint = hitLocalPoint; + final Vector3f normalDirection = hitLocalPoint.lessNew(q); + _raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else { + // If the origin is inside the cylinder, we return no hit + return false; + } + } + final float b = dDotD * mDotN - nDotD * mDotD; + final float discriminant = b * b - a * c; + // If the discriminant is negative, no real roots and therfore, no hit + if (discriminant < 0.0f) { + return false; + } + // Compute the smallest root (first longersection along the ray) + final float t0 = t = (-b - FMath.sqrt(discriminant)) / a; + // If the longersection is outside the finite cylinder of the capsule on "p" endcap side + final float value = mDotD + t * nDotD; + if (value < 0.0f) { + // Check longersection between the ray and the "p" sphere endcap of the capsule + final Vector3f hitLocalPoint = new Vector3f(); + final Float hitFraction = 0.0f; + if (raycastWithSphereEndCap(_ray.point1, _ray.point2, p, _ray.maxFraction, hitLocalPoint, hitFraction)) { + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = hitFraction; + _raycastInfo.worldPoint = hitLocalPoint; + final Vector3f normalDirection = hitLocalPoint.lessNew(p); + _raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else if (value > dDotD) { // If the longersection is outside the finite cylinder on the "q" side + // Check longersection between the ray and the "q" sphere endcap of the capsule + final Vector3f hitLocalPoint = new Vector3f(); + final Float hitFraction = 0.0f; + if (raycastWithSphereEndCap(_ray.point1, _ray.point2, q, _ray.maxFraction, hitLocalPoint, hitFraction)) { + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = hitFraction; + _raycastInfo.worldPoint = hitLocalPoint; + final Vector3f normalDirection = hitLocalPoint.lessNew(q); + _raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } + t = t0; + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + final Vector3f v = localHitPoint.lessNew(p); + final Vector3f w = d.multiplyNew(v.dot(d) / d.length2()); + final Vector3f normalDirection = localHitPoint.lessNew(p.addNew(w)).safeNormalize(); + _raycastInfo.worldNormal = normalDirection; + return true; + } + + /** + * @brief Raycasting method between a ray one of the two spheres end cap of the capsule + */ + protected boolean raycastWithSphereEndCap(final Vector3f _point1, final Vector3f _point2, final Vector3f _sphereCenter, final float _maxFraction, Vector3f _hitLocalPoint, Float _hitFraction) { + final Vector3f m = _point1.lessNew(_sphereCenter); + final float c = m.dot(m) - this.margin * this.margin; + // If the origin of the ray is inside the sphere, we return no longersection + if (c < 0.0f) { + return false; + } + final Vector3f rayDirection = _point2.lessNew(_point1); + final float b = m.dot(rayDirection); + // If the origin of the ray is outside the sphere and the ray + // is pointing away from the sphere, there is no longersection + if (b > 0.0f) { + return false; + } + final float raySquareLength = rayDirection.length2(); + // Compute the discriminant of the quadratic equation + final float discriminant = b * b - raySquareLength * c; + // If the discriminant is negative or the ray length is very small, there is no longersection + if (discriminant < 0.0f || raySquareLength < Constant.FLOAT_EPSILON) { + return false; + } + // Compute the solution "t" closest to the origin + float t = -b - FMath.sqrt(discriminant); + assert (t >= 0.0f); + // If the hit point is withing the segment ray fraction + if (t < _maxFraction * raySquareLength) { + // Compute the longersection information + t /= raySquareLength; + _hitFraction = t; + _hitLocalPoint = rayDirection.multiplyNew(t).add(_point1); + return true; + } + return false; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y) * _scaling.y; + this.margin = (this.margin / this.scaling.x) * _scaling.x; + super.setLocalScaling(_scaling); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + final float diffYCenterSphere1 = _localPoint.y - this.halfHeight; + final float diffYCenterSphere2 = _localPoint.y + this.halfHeight; + final float xSquare = _localPoint.x * _localPoint.x; + final float zSquare = _localPoint.z * _localPoint.z; + final float squareRadius = this.margin * this.margin; + // Return true if the point is inside the cylinder or one of the two spheres of the capsule + return ((xSquare + zSquare) < squareRadius && _localPoint.y < this.halfHeight && _localPoint.y > -this.halfHeight) + || (xSquare + zSquare + diffYCenterSphere1 * diffYCenterSphere1) < squareRadius || (xSquare + zSquare + diffYCenterSphere2 * diffYCenterSphere2) < squareRadius; + } + +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/CollisionShape.java b/src/org/atriasoft/ephysics/collision/shapes/CollisionShape.java new file mode 100644 index 0000000..3406a7c --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/CollisionShape.java @@ -0,0 +1,126 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; + +/** + * This abstract class represents the collision shape associated with a + * body that is used during the narrow-phase collision detection. + */ +public abstract class CollisionShape { + /** + * @brief Get the maximum number of contact + * @return The maximum number of contact manifolds in an overlapping pair given two shape types + */ + public static int computeNbMaxContactManifolds(final CollisionShapeType _shapeType1, final CollisionShapeType _shapeType2) { + // If both shapes are convex + if (isConvex(_shapeType1) && isConvex(_shapeType2)) { + return Defaults.NB_MAX_CONTACT_MANIFOLDS_CONVEX_SHAPE; + } + // If there is at least one concave shape + return Defaults.NB_MAX_CONTACT_MANIFOLDS_CONCAVE_SHAPE; + } + + /** + * @brief Check if the shape is convex + * @param[in] _shapeType shape type + * @return true If the collision shape is convex + * @return false If it is concave + */ + public static boolean isConvex(final CollisionShapeType _shapeType) { + return _shapeType != CollisionShapeType.CONCAVE_MESH && _shapeType != CollisionShapeType.HEIGHTFIELD; + } + + protected CollisionShapeType type; //!< Type of the collision shape + protected Vector3f scaling; //!< Scaling vector of the collision shape + /// Constructor + + public CollisionShape(final CollisionShapeType type) { + this.type = type; + this.scaling = new Vector3f(1.0f, 1.0f, 1.0f); + } + + /** + * @brief Update the AABB of a body using its collision shape + * @param[out] _aabb The axis-aligned bounding box (AABB) of the collision shape computed in world-space coordinates + * @param[in] _transform Transform3D used to compute the AABB of the collision shape + */ + public void computeAABB(final AABB _aabb, final Transform3D _transform) { + // Get the local bounds in x,y and z direction + final Vector3f minBounds = new Vector3f(0, 0, 0); + final Vector3f maxBounds = new Vector3f(0, 0, 0); + getLocalBounds(minBounds, maxBounds); + // Rotate the local bounds according to the orientation of the body + final Matrix3f worldAxis = _transform.getOrientation().getMatrix().getAbsolute(); + final Vector3f worldMinBounds = new Vector3f(worldAxis.getColumn(0).dot(minBounds), worldAxis.getColumn(1).dot(minBounds), worldAxis.getColumn(2).dot(minBounds)); + final Vector3f worldMaxBounds = new Vector3f(worldAxis.getColumn(0).dot(maxBounds), worldAxis.getColumn(1).dot(maxBounds), worldAxis.getColumn(2).dot(maxBounds)); + // Compute the minimum and maximum coordinates of the rotated extents + final Vector3f minCoordinates = _transform.getPosition().addNew(worldMinBounds); + final Vector3f maxCoordinates = _transform.getPosition().addNew(worldMaxBounds); + // Update the AABB with the new minimum and maximum coordinates + _aabb.setMin(minCoordinates); + _aabb.setMax(maxCoordinates); + } + + /** + * @brief Compute the local inertia tensor of the sphere + * @param[out] _tensor The 3x3 inertia tensor matrix of the shape in local-space coordinates + * @param[in] _mass Mass to use to compute the inertia tensor of the collision shape + */ + public abstract void computeLocalInertiaTensor(Matrix3f _tensor, float _mass); + + /** + * @brief Get the local bounds of the shape in x, y and z directions. + * This method is used to compute the AABB of the box + * @param _min The minimum bounds of the shape in local-space coordinates + * @param _max The maximum bounds of the shape in local-space coordinates + */ + public abstract void getLocalBounds(Vector3f _min, Vector3f _max); + + /// Return the scaling vector of the collision shape + public Vector3f getScaling() { + return this.scaling; + } + + /** + * @brief Get the type of the collision shapes + * @return The type of the collision shape (box, sphere, cylinder, ...) + */ + public CollisionShapeType getType() { + return this.type; + } + + /** + * @brief Check if the shape is convex + * @return true If the collision shape is convex + * @return false If it is concave + */ + public abstract boolean isConvex(); + + /// Raycast method with feedback information + public abstract boolean raycast(Ray ray, RaycastInfo raycastInfo, ProxyShape proxyShape); + + /** + * @brief Set the scaling vector of the collision shape + */ + public void setLocalScaling(final Vector3f _scaling) { + this.scaling = _scaling; + } + + /// Return true if a point is inside the collision shape + public abstract boolean testPointInside(Vector3f worldPoint, ProxyShape proxyShape); +}; diff --git a/src/org/atriasoft/ephysics/collision/shapes/CollisionShapeType.java b/src/org/atriasoft/ephysics/collision/shapes/CollisionShapeType.java new file mode 100644 index 0000000..709af01 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/CollisionShapeType.java @@ -0,0 +1,58 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +/** + * Type of the collision shape + * + * @author Jason Sorensen + */ +public enum CollisionShapeType { + TRIANGLE(0), + BOX(1), + SPHERE(2), + CONE(3), + CYLINDER(4), + CAPSULE(5), + CONVEX_MESH(6), + CONCAVE_MESH(7), + HEIGHTFIELD(8); + + public static final int NB_COLLISION_SHAPE_TYPES = 9; + + public static CollisionShapeType getType(final int value) { + switch (value) { + case 0: + return TRIANGLE; + case 1: + return BOX; + case 2: + return SPHERE; + case 3: + return CONE; + case 4: + return CYLINDER; + case 5: + return CAPSULE; + case 6: + return CONVEX_MESH; + case 7: + return CONCAVE_MESH; + case 8: + return HEIGHTFIELD; + } + return null; + } + + public final int value; + + private CollisionShapeType(final int value) { + this.value = value; + } +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/ConcaveMeshShape.java b/src/org/atriasoft/ephysics/collision/shapes/ConcaveMeshShape.java new file mode 100644 index 0000000..40ce4ad --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/ConcaveMeshShape.java @@ -0,0 +1,210 @@ +package org.atriasoft.ephysics.collision.shapes; + +import java.util.ArrayList; +import java.util.List; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.Triangle; +import org.atriasoft.ephysics.collision.TriangleMesh; +import org.atriasoft.ephysics.collision.TriangleVertexArray; +import org.atriasoft.ephysics.collision.broadphase.CallbackOverlapping; +import org.atriasoft.ephysics.collision.broadphase.CallbackRaycast; +import org.atriasoft.ephysics.collision.broadphase.DTree; +import org.atriasoft.ephysics.collision.broadphase.DynamicAABBTree; +import org.atriasoft.ephysics.collision.shapes.TriangleShape.TriangleRaycastSide; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * Represents a static concave mesh shape. Note that collision detection + * with a concave mesh shape can be very expensive. You should use only use + * this shape for a static mesh. + */ +public class ConcaveMeshShape extends ConcaveShape { + class ConcaveMeshRaycastCallback { + private final List hitAABBNodes = new ArrayList<>(); + private final DynamicAABBTree dynamicAABBTree; + private final ConcaveMeshShape concaveMeshShape; + private final ProxyShape proxyShape; + private final RaycastInfo raycastInfo; + private final Ray ray; + private boolean isHit; + + // Constructor + ConcaveMeshRaycastCallback(final DynamicAABBTree _dynamicAABBTree, final ConcaveMeshShape _concaveMeshShape, final ProxyShape _proxyShape, final RaycastInfo _raycastInfo, final Ray _ray) { + this.dynamicAABBTree = _dynamicAABBTree; + this.concaveMeshShape = _concaveMeshShape; + this.proxyShape = _proxyShape; + this.raycastInfo = _raycastInfo; + this.ray = _ray; + this.isHit = false; + + } + + /// Return true if a raycast hit has been found + public boolean getIsHit() { + return this.isHit; + } + + /// Collect all the AABB nodes that are hit by the ray in the Dynamic AABB Tree + public float operator__parenthese(final DTree _node, final Ray _ray) { + // Add the id of the hit AABB node longo + this.hitAABBNodes.add(_node); + return _ray.maxFraction; + } + + /// Raycast all collision shapes that have been collected + public void raycastTriangles() { + float smallestHitFraction = this.ray.maxFraction; + for (final DTree it : this.hitAABBNodes) { + // Get the node data (triangle index and mesh subpart index) + final int data_0 = this.dynamicAABBTree.getNodeDataInt_0(it); + final int data_1 = this.dynamicAABBTree.getNodeDataInt_1(it); + // Get the triangle vertices for this node from the concave mesh shape + final Vector3f[] trianglePoints = new Vector3f[3]; + this.concaveMeshShape.getTriangleVerticesWithIndexPointer(data_0, data_1, trianglePoints); + // Create a triangle collision shape + final float margin = this.concaveMeshShape.getTriangleMargin(); + + final TriangleShape triangleShape = new TriangleShape(trianglePoints[0], trianglePoints[1], trianglePoints[2], margin); + triangleShape.setRaycastTestType(this.concaveMeshShape.getRaycastTestType()); + // Ray casting test against the collision shape + final RaycastInfo raycastInfo = new RaycastInfo(); + final boolean isTriangleHit = triangleShape.raycast(this.ray, raycastInfo, this.proxyShape); + // If the ray hit the collision shape + if (isTriangleHit && raycastInfo.hitFraction <= smallestHitFraction) { + assert (raycastInfo.hitFraction >= 0.0f); + this.raycastInfo.body = raycastInfo.body; + this.raycastInfo.proxyShape = raycastInfo.proxyShape; + this.raycastInfo.hitFraction = raycastInfo.hitFraction; + this.raycastInfo.worldPoint = raycastInfo.worldPoint; + this.raycastInfo.worldNormal = raycastInfo.worldNormal; + this.raycastInfo.meshSubpart = data_0; + this.raycastInfo.triangleIndex = data_1; + smallestHitFraction = raycastInfo.hitFraction; + this.isHit = true; + } + } + } + + }; + + protected TriangleMesh triangleMesh; //!< Triangle mesh + + protected DynamicAABBTree dynamicAABBTree; //!< Dynamic AABB tree to accelerate collision with the triangles + + public ConcaveMeshShape(final TriangleMesh _triangleMesh) { + super(CollisionShapeType.CONCAVE_MESH); + this.triangleMesh = _triangleMesh; + this.raycastTestType = TriangleRaycastSide.FRONT; + initBVHTree(); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + // Default inertia tensor + // Note that this is not very realistic for a concave triangle mesh. + // However, in most cases, it will only be used static bodies and therefore, + // the inertia tensor is not used. + _tensor.set(_mass, 0.0f, 0.0f, 0.0f, _mass, 0.0f, 0.0f, 0.0f, _mass); + } + + @Override + public void getLocalBounds(final Vector3f _min, final Vector3f _max) { + // Get the AABB of the whole tree + final AABB treeAABB = this.dynamicAABBTree.getRootAABB(); + _min.set(treeAABB.getMin()); + _max.set(treeAABB.getMax()); + } + + /// Return the three vertices coordinates (in the array outTriangleVertices) of a triangle + /// given the start vertex index pointer of the triangle. + protected void getTriangleVerticesWithIndexPointer(final int _subPart, final int _triangleIndex, final Vector3f[] _outTriangleVertices) { + assert (_outTriangleVertices != null); + // Get the triangle vertex array of the current sub-part + final TriangleVertexArray triangleVertexArray = this.triangleMesh.getSubpart(_subPart); + if (triangleVertexArray == null) { + Log.error("get null ..."); + } + final Triangle trianglePoints = triangleVertexArray.getTriangle(_triangleIndex); + _outTriangleVertices[0] = trianglePoints.get(0).multiplyNew(this.scaling); + _outTriangleVertices[1] = trianglePoints.get(1).multiplyNew(this.scaling); + _outTriangleVertices[2] = trianglePoints.get(2).multiplyNew(this.scaling); + } + + /// Insert all the triangles longo the dynamic AABB tree + protected void initBVHTree() { + // TODO : Try to randomly add the triangles into the tree to obtain a better tree + // For each sub-part of the mesh + for (int subPart = 0; subPart < this.triangleMesh.getNbSubparts(); subPart++) { + // Get the triangle vertex array of the current sub-part + final TriangleVertexArray triangleVertexArray = this.triangleMesh.getSubpart(subPart); + // For each triangle of the concave mesh + for (int iii = 0; iii < triangleVertexArray.getNbTriangles(); ++iii) { + final Triangle trianglePoints = triangleVertexArray.getTriangle(iii); + final Vector3f[] trianglePoints2 = new Vector3f[3]; + trianglePoints2[0] = trianglePoints.get(0); + trianglePoints2[1] = trianglePoints.get(1); + trianglePoints2[2] = trianglePoints.get(2); + // Create the AABB for the triangle + final AABB aabb = AABB.createAABBForTriangle(trianglePoints2); + aabb.inflate(this.triangleMargin, this.triangleMargin, this.triangleMargin); + // Add the AABB with the index of the triangle longo the dynamic AABB tree + this.dynamicAABBTree.addObject(aabb, subPart, iii); + } + } + } + + @Override + public boolean isConvex() { + return false; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + // Create the callback object that will compute ray casting against triangles + final ConcaveMeshRaycastCallback raycastCallback = new ConcaveMeshRaycastCallback(this.dynamicAABBTree, this, _proxyShape, _raycastInfo, _ray); + // Ask the Dynamic AABB Tree to report all AABB nodes that are hit by the ray. + // The raycastCallback object will then compute ray casting against the triangles + // in the hit AABBs. + this.dynamicAABBTree.raycast(_ray, new CallbackRaycast() { + + @Override + public float callback(final DTree _node, final Ray _ray) { + return raycastCallback.operator__parenthese(_node, _ray); + } + }); + raycastCallback.raycastTriangles(); + return raycastCallback.getIsHit(); + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + super.setLocalScaling(_scaling); + this.dynamicAABBTree.reset(); + initBVHTree(); + } + + @Override + public void testAllTriangles(final ConcaveShape.TriangleCallback _callback, final AABB _localAABB) { + // Ask the Dynamic AABB Tree to report all the triangles that are overlapping + // with the AABB of the convex shape. + final ConcaveMeshShape self = this; + this.dynamicAABBTree.reportAllShapesOverlappingWithAABB(_localAABB, new CallbackOverlapping() { + @Override + public void callback(final DTree _node) { + // Get the node data (triangle index and mesh subpart index) + final int data_0 = self.dynamicAABBTree.getNodeDataInt_0(_node); + final int data_1 = self.dynamicAABBTree.getNodeDataInt_1(_node); + // Get the triangle vertices for this node from the concave mesh shape + final Vector3f[] trianglePoints = new Vector3f[3]; + getTriangleVerticesWithIndexPointer(data_0, data_1, trianglePoints); + // Call the callback to test narrow-phase collision with this triangle + _callback.testTriangle(trianglePoints); + } + }); + } +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/ConcaveShape.java b/src/org/atriasoft/ephysics/collision/shapes/ConcaveShape.java new file mode 100644 index 0000000..643d678 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/ConcaveShape.java @@ -0,0 +1,88 @@ +/** @file + * Original ReactPhysics3D C++ library by Daniel Chappuis This code is re-licensed with permission from ReactPhysics3D author. + * @author Daniel CHAPPUIS + * @author Edouard DUPIN + * @copyright 2010-2016, Daniel Chappuis + * @copyright 2017-now, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.TriangleShape.TriangleRaycastSide; + +/** + * This abstract class represents a concave collision shape associated with a + * body that is used during the narrow-phase collision detection. + */ +public abstract class ConcaveShape extends CollisionShape { + + /** + * It is used to encapsulate a callback method for + * a single triangle of a ConcaveMesh. + */ + public interface TriangleCallback { + /// Report a triangle + public void testTriangle(Vector3f[] _trianglePoints); + } + + boolean isSmoothMeshCollisionEnabled; //!< True if the smooth mesh collision algorithm is enabled + + protected float triangleMargin; //!< Margin use for collision detection for each triangle + + protected TriangleRaycastSide raycastTestType; //!< Raycast test type for the triangle (front, back, front-back) + + /// Return true if the collision shape is convex, false if it is concave + public boolean isConvex; + + public ConcaveShape(final CollisionShapeType _type) { + super(_type); + this.isSmoothMeshCollisionEnabled = false; + this.triangleMargin = 0; + this.raycastTestType = TriangleRaycastSide.FRONT; + } + + /// Return true if the smooth mesh collision is enabled + public boolean getIsSmoothMeshCollisionEnabled() { + return this.isSmoothMeshCollisionEnabled; + } + + /// Return the raycast test type (front, back, front-back) + public TriangleRaycastSide getRaycastTestType() { + return this.raycastTestType; + } + + /// Return the triangle margin + public float getTriangleMargin() { + return this.triangleMargin; + } + + /** + * Enable/disable the smooth mesh collision algorithm + * + * Smooth mesh collision is used to avoid collisions against some longernal edges of the triangle mesh. + * If it is enabled, collsions with the mesh will be smoother but collisions computation is a bit more expensive. + */ + public void setIsSmoothMeshCollisionEnabled(final boolean _isEnabled) { + this.isSmoothMeshCollisionEnabled = _isEnabled; + } + + /** + * Set the raycast test type (front, back, front-back) + * @param testType Raycast test type for the triangle (front, back, front-back) + */ + public void setRaycastTestType(final TriangleRaycastSide _testType) { + this.raycastTestType = _testType; + } + + /// Use a callback method on all triangles of the concave shape inside a given AABB + public abstract void testAllTriangles(TriangleCallback _callback, AABB _localAABB); + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + return false; + } + +}; diff --git a/src/org/atriasoft/ephysics/collision/shapes/ConeShape.java b/src/org/atriasoft/ephysics/collision/shapes/ConeShape.java new file mode 100644 index 0000000..9ab61f6 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/ConeShape.java @@ -0,0 +1,255 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a cone collision shape centered at the + * origin and alligned with the Y axis. The cone is defined + * by its height and by the radius of its base. The center of the + * cone is at the half of the height. The "transform" of the + * corresponding rigid body gives an orientation and a position + * to the cone. This collision shape uses an extra margin distance around + * it for collision detection purpose. The default margin is 4cm (if your + * units are meters, which is recommended). In case, you want to simulate small + * objects (smaller than the margin distance), you might want to reduce the margin + * by specifying your own margin distance using the "margin" parameter in the + * ructor of the cone shape. Otherwise, it is recommended to use the + * default margin distance by not using the "margin" parameter in the ructor. + */ +public class ConeShape extends ConvexShape { + protected float radius; //!< Radius of the base + protected float halfHeight; //!< Half height of the cone + + protected float sinTheta; //!< sine of the semi angle at the apex point + + /** + * Constructor + * @param _radius Radius of the cone (in meters) + * @param _height Height of the cone (in meters) + * @param _margin Collision margin (in meters) around the collision shape + */ + public ConeShape(final float _radius, final float _height) { + this(_radius, _height, Defaults.OBJECT_MARGIN); + } + + public ConeShape(final float _radius, final float _height, final float _margin) { + super(CollisionShapeType.CONE, _margin); + this.radius = _radius; + this.halfHeight = _height * 0.5f; + // Compute the sine of the semi-angle at the apex point + this.sinTheta = this.radius / (FMath.sqrt(this.radius * this.radius + _height * _height)); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + final float rSquare = this.radius * this.radius; + final float diagXZ = 0.15f * _mass * (rSquare + this.halfHeight); + _tensor.set(diagXZ, 0.0f, 0.0f, 0.0f, 0.3f * _mass * rSquare, 0.0f, 0.0f, 0.0f, diagXZ); + } + + /** + * Return the height + * @return Height of the cone (in meters) + */ + public float getHeight() { + return 2.0f * this.halfHeight; + } + + @Override + public void getLocalBounds(final Vector3f min, final Vector3f max) { + // Maximum bounds + max.setX(this.radius + this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(this.radius + this.margin); + // Minimum bounds + min.setX(-max.x); + min.setY(-max.y); + min.setZ(-max.z); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + final Vector3f v = _direction; + final float sinThetaTimesLengthV = this.sinTheta * v.length(); + Vector3f supportPoint; + if (v.y > sinThetaTimesLengthV) { + supportPoint = new Vector3f(0.0f, this.halfHeight, 0.0f); + } else { + final float projectedLength = FMath.sqrt(v.x * v.x + v.z * v.z); + if (projectedLength > Constant.FLOAT_EPSILON) { + final float d = this.radius / projectedLength; + supportPoint = new Vector3f(v.x * d, -this.halfHeight, v.z * d); + } else { + supportPoint = new Vector3f(0.0f, -this.halfHeight, 0.0f); + } + } + return supportPoint; + } + + /** + * Return the radius + * @return Radius of the cone (in meters) + */ + public float getRadius() { + return this.radius; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f r = _ray.point2.lessNew(_ray.point1); + final float epsilon = 0.00001f; + final Vector3f V = new Vector3f(0, this.halfHeight, 0); + final Vector3f centerBase = new Vector3f(0, -this.halfHeight, 0); + final Vector3f axis = new Vector3f(0, -1.0f, 0); + final float heightSquare = 4.0f * this.halfHeight * this.halfHeight; + final float cosThetaSquare = heightSquare / (heightSquare + this.radius * this.radius); + final float factor = 1.0f - cosThetaSquare; + final Vector3f delta = _ray.point1.lessNew(V); + final float c0 = -cosThetaSquare * delta.x * delta.x + factor * delta.y * delta.y - cosThetaSquare * delta.z * delta.z; + final float c1 = -cosThetaSquare * delta.x * r.x + factor * delta.y * r.y - cosThetaSquare * delta.z * r.z; + final float c2 = -cosThetaSquare * r.x * r.x + factor * r.y * r.y - cosThetaSquare * r.z * r.z; + final float tHit[] = { -1.0f, -1.0f, -1.0f }; + final Vector3f[] localHitPoint = new Vector3f[3]; + final Vector3f[] localNormal = new Vector3f[3]; + // If c2 is different from zero + if (FMath.abs(c2) > Constant.FLOAT_EPSILON) { + final float gamma = c1 * c1 - c0 * c2; + // If there is no real roots in the quadratic equation + if (gamma < 0.0f) { + return false; + } else if (gamma > 0.0f) { // The equation has two real roots + // Compute two longersections + final float sqrRoot = FMath.sqrt(gamma); + tHit[0] = (-c1 - sqrRoot) / c2; + tHit[1] = (-c1 + sqrRoot) / c2; + } else { // If the equation has a single real root + // Compute the longersection + tHit[0] = -c1 / c2; + } + } else // If c2 == 0 + if (FMath.abs(c1) > Constant.FLOAT_EPSILON) { + // If c2 = 0 and c1 != 0 + tHit[0] = -c0 / (2.0f * c1); + } else { + // If c2 = c1 = 0 + // If c0 is different from zero, no solution and if c0 = 0, we have a + // degenerate case, the whole ray is contained in the cone side + // but we return no hit in this case + return false; + } + // If the origin of the ray is inside the cone, we return no hit + if (testPointInside(_ray.point1, null)) { + return false; + } + localHitPoint[0] = r.multiplyNew(tHit[0]).add(_ray.point1); + localHitPoint[1] = r.multiplyNew(tHit[1]).add(_ray.point1); + // Only keep hit points in one side of the double cone (the cone we are longerested in) + if (axis.dot(localHitPoint[0].lessNew(V)) < 0.0f) { + tHit[0] = -1.0f; + } + if (axis.dot(localHitPoint[1].lessNew(V)) < 0.0f) { + tHit[1] = -1.0f; + } + // Only keep hit points that are within the correct height of the cone + if (localHitPoint[0].y < -this.halfHeight) { + tHit[0] = -1.0f; + } + if (localHitPoint[1].y < -this.halfHeight) { + tHit[1] = -1.0f; + } + // If the ray is in direction of the base plane of the cone + if (r.y > epsilon) { + // Compute the longersection with the base plane of the cone + tHit[2] = (-_ray.point1.y - this.halfHeight) / (r.y); + // Only keep this longersection if it is inside the cone radius + localHitPoint[2] = r.multiplyNew(tHit[2]).add(_ray.point1); + if ((localHitPoint[2].lessNew(centerBase)).length2() > this.radius * this.radius) { + tHit[2] = -1.0f; + } + // Compute the normal direction + localNormal[2] = axis; + } + // Find the smallest positive t value + int hitIndex = -1; + float t = Float.MAX_VALUE; + for (int i = 0; i < 3; i++) { + if (tHit[i] < 0.0f) { + continue; + } + if (tHit[i] < t) { + hitIndex = i; + t = tHit[hitIndex]; + } + } + if (hitIndex < 0) { + return false; + } + if (t > _ray.maxFraction) { + return false; + } + // Compute the normal direction for hit against side of the cone + if (hitIndex != 2) { + final float h = 2.0f * this.halfHeight; + final float value1 = (localHitPoint[hitIndex].x * localHitPoint[hitIndex].x + localHitPoint[hitIndex].z * localHitPoint[hitIndex].z); + final float rOverH = this.radius / h; + final float value2 = 1.0f + rOverH * rOverH; + final float factor22 = 1.0f / FMath.sqrt(value1 * value2); + final float x = localHitPoint[hitIndex].x * factor22; + final float z = localHitPoint[hitIndex].z * factor22; + localNormal[hitIndex].setX(x); + localNormal[hitIndex].setY(FMath.sqrt(x * x + z * z) * rOverH); + localNormal[hitIndex].setZ(z); + } + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint[hitIndex]; + _raycastInfo.worldNormal = localNormal[hitIndex]; + return true; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y) * _scaling.y; + this.radius = (this.radius / this.scaling.x) * _scaling.x; + super.setLocalScaling(_scaling); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + final float radiusHeight = this.radius * (-_localPoint.y + this.halfHeight) / (this.halfHeight * 2.0f); + return (_localPoint.y < this.halfHeight && _localPoint.y > -this.halfHeight) && (_localPoint.x * _localPoint.x + _localPoint.z * _localPoint.z < radiusHeight * radiusHeight); + } + +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/shapes/ConvexMeshShape.java b/src/org/atriasoft/ephysics/collision/shapes/ConvexMeshShape.java new file mode 100644 index 0000000..cac50ac --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/ConvexMeshShape.java @@ -0,0 +1,343 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.collision.shapes; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.TriangleVertexArray; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.ephysics.mathematics.SetInteger; + +/** + * It represents a convex mesh shape. In order to create a convex mesh shape, you + * need to indicate the local-space position of the mesh vertices. You do it either by + * passing a vertices array to the ructor or using the addVertex method. Make sure + * that the set of vertices that you use to create the shape are indeed part of a convex + * mesh. The center of mass of the shape will be at the origin of the local-space geometry + * that you use to create the mesh. The method used for collision detection with a convex + * mesh shape has an O(n) running time with "n" beeing the number of vertices in the mesh. + * Therefore, you should try not to use too many vertices. However, it is possible to speed + * up the collision detection by using the edges information of your mesh. The running time + * of the collision detection that uses the edges is almost O(1) ant time at the cost + * of additional memory used to store the vertices. You can indicate edges information + * with the addEdge() method. Then, you must use the setIsEdgesInformationUsed(true) method + * in order to use the edges information for collision detection. + */ +public class ConvexMeshShape extends ConvexShape { + protected List vertices = new ArrayList<>(); //!< Array with the vertices of the mesh + protected int numberVertices = 0; //!< Number of vertices in the mesh + protected Vector3f minBounds = new Vector3f(); //!< Mesh minimum bounds in the three local x, y and z directions + protected Vector3f maxBounds = new Vector3f(); //!< Mesh maximum bounds in the three local x, y and z directions + protected boolean isEdgesInformationUsed; //!< True if the shape contains the edges of the convex mesh in order to make the collision detection faster + + protected Map edgesAdjacencyList = new HashMap<>(); //!< Adjacency list representing the edges of the mesh + + /** + * Constructor. + * If you use this ructor, you will need to set the vertices manually one by one using the addVertex method. + */ + public ConvexMeshShape() { + this(Defaults.OBJECT_MARGIN); + } + + public ConvexMeshShape(final float _margin) { + super(CollisionShapeType.CONVEX_MESH, _margin); + this.minBounds = new Vector3f(0, 0, 0); + this.maxBounds = new Vector3f(0, 0, 0); + this.numberVertices = 0; + this.isEdgesInformationUsed = false; + } + + /** + * Constructor to initialize with an array of 3D vertices. + * This method creates an longernal copy of the input vertices. + * @param _arrayVertices Array with the vertices of the convex mesh + * @param _nbVertices Number of vertices in the convex mesh + * @param _stride Stride between the beginning of two elements in the vertices array + * @param _margin Collision margin (in meters) around the collision shape + */ + public ConvexMeshShape(final float[] _arrayVertices, final int _nbVertices, final int _stride) { + this(_arrayVertices, _nbVertices, _stride, Defaults.OBJECT_MARGIN); + } + + public ConvexMeshShape(final float[] _arrayVertices, final int _nbVertices, final int _stride, final float _margin) { + super(CollisionShapeType.CONVEX_MESH, _margin); + this.numberVertices = _nbVertices; + this.minBounds = new Vector3f(0, 0, 0); + this.maxBounds = new Vector3f(0, 0, 0); + this.isEdgesInformationUsed = false; + int offset = 0; + // Copy all the vertices longo the longernal array + for (long iii = 0; iii < this.numberVertices; iii++) { + this.vertices.add(new Vector3f(_arrayVertices[offset], _arrayVertices[offset + 1], _arrayVertices[offset + 2])); + offset += _stride; + } + // Recalculate the bounds of the mesh + recalculateBounds(); + } + + /** + * Constructor to initialize with a triangle mesh + * This method creates an internal copy of the input vertices. + * @param _triangleVertexArray Array with the vertices and indices of the vertices and triangles of the mesh + * @param _isEdgesInformationUsed True if you want to use edges information for collision detection (faster but requires more memory) + * @param _margin Collision margin (in meters) around the collision shape + */ + public ConvexMeshShape(final TriangleVertexArray _triangleVertexArray) { + this(_triangleVertexArray, true); + } + + public ConvexMeshShape(final TriangleVertexArray _triangleVertexArray, final boolean _isEdgesInformationUsed) { + this(_triangleVertexArray, true, Defaults.OBJECT_MARGIN); + } + + public ConvexMeshShape(final TriangleVertexArray _triangleVertexArray, final boolean _isEdgesInformationUsed, final float _margin) { + super(CollisionShapeType.CONVEX_MESH, _margin); + this.minBounds = new Vector3f(0, 0, 0); + this.maxBounds = new Vector3f(0, 0, 0); + this.isEdgesInformationUsed = _isEdgesInformationUsed; + // For each vertex of the mesh + for (final Vector3f it : _triangleVertexArray.getVertices()) { + this.vertices.add(it.multiplyNew(this.scaling)); + } + // If we need to use the edges information of the mesh + if (this.isEdgesInformationUsed) { + // For each triangle of the mesh + for (int iii = 0; iii < _triangleVertexArray.getNbTriangles(); iii++) { + final int vertexIndex[] = { 0, 0, 0 }; + vertexIndex[0] = _triangleVertexArray.getIndices()[iii * 3]; + vertexIndex[1] = _triangleVertexArray.getIndices()[iii * 3 + 1]; + vertexIndex[2] = _triangleVertexArray.getIndices()[iii * 3 + 2]; + // Add information about the edges + addEdge(vertexIndex[0], vertexIndex[1]); + addEdge(vertexIndex[0], vertexIndex[2]); + addEdge(vertexIndex[1], vertexIndex[2]); + } + } + this.numberVertices = this.vertices.size(); + recalculateBounds(); + } + + /** + * Add an edge longo the convex mesh by specifying the two vertex indices of the edge. + * @note that the vertex indices start at zero and need to correspond to the order of + * the vertices in the vertices array in the ructor or the order of the calls + * of the addVertex methods that you use to add vertices longo the convex mesh. + * @param _v1 Index of the first vertex of the edge to add + * @param _v2 Index of the second vertex of the edge to add + */ + public void addEdge(final int _v1, final int _v2) { + // If the entry for vertex v1 does not exist in the adjacency list + if (!this.edgesAdjacencyList.containsKey(_v1)) { + this.edgesAdjacencyList.put(_v1, new SetInteger()); + } + // If the entry for vertex v2 does not exist in the adjacency list + if (!this.edgesAdjacencyList.containsKey(_v2)) { + this.edgesAdjacencyList.put(_v2, new SetInteger()); + } + // Add the edge in the adjacency list + this.edgesAdjacencyList.get(_v1).add(_v2); + this.edgesAdjacencyList.get(_v2).add(_v1); + } + + /** + * Add a vertex longo the convex mesh + * @param vertex Vertex to be added + */ + public void addVertex(final Vector3f _vertex) { + // Add the vertex in to vertices array + this.vertices.add(_vertex); + this.numberVertices++; + // Update the bounds of the mesh + if (_vertex.x * this.scaling.x > this.maxBounds.x) { + this.maxBounds.setX(_vertex.x * this.scaling.x); + } + if (_vertex.x * this.scaling.x < this.minBounds.x) { + this.minBounds.setX(_vertex.x * this.scaling.x); + } + if (_vertex.y * this.scaling.y > this.maxBounds.y) { + this.maxBounds.setY(_vertex.y * this.scaling.y); + } + if (_vertex.y * this.scaling.y < this.minBounds.y) { + this.minBounds.setY(_vertex.y * this.scaling.y); + } + if (_vertex.z * this.scaling.z > this.maxBounds.z) { + this.maxBounds.setZ(_vertex.z * this.scaling.z); + } + if (_vertex.z * this.scaling.z < this.minBounds.z) { + this.minBounds.setZ(_vertex.z * this.scaling.z); + } + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + final float factor = (1.0f / 3.0f) * _mass; + final Vector3f realExtent = this.maxBounds.lessNew(this.minBounds).multiply(0.5f); + assert (realExtent.x > 0 && realExtent.y > 0 && realExtent.z > 0); + final float xSquare = realExtent.x * realExtent.x; + final float ySquare = realExtent.y * realExtent.y; + final float zSquare = realExtent.z * realExtent.z; + _tensor.set(factor * (ySquare + zSquare), 0.0f, 0.0f, 0.0f, factor * (xSquare + zSquare), 0.0f, 0.0f, 0.0f, factor * (xSquare + ySquare)); + } + + @Override + public void getLocalBounds(final Vector3f _min, final Vector3f _max) { + _min.set(this.minBounds); + _max.set(this.maxBounds); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + assert (this.numberVertices == this.vertices.size()); + assert (_cachedCollisionData != null); + // Allocate memory for the cached collision data if not allocated yet + if (_cachedCollisionData.data == null) { + // TODO the data is nort set outside ==> find how ... !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + _cachedCollisionData.data = 0; + } + // If the edges information is used to speed up the collision detection + if (this.isEdgesInformationUsed) { + assert (this.edgesAdjacencyList.size() == this.numberVertices); + int maxVertex = (Integer) (_cachedCollisionData.data); + float maxDotProduct = _direction.dot(this.vertices.get(maxVertex)); + boolean isOptimal; + // Perform hill-climbing (local search) + do { + isOptimal = true; + assert (this.edgesAdjacencyList.get(maxVertex).size() > 0); + // For all neighbors of the current vertex + for (final Integer it : this.edgesAdjacencyList.get(maxVertex).getRaw()) { + // Compute the dot product + final float dotProduct = _direction.dot(this.vertices.get(it)); + // If the current vertex is a better vertex (larger dot product) + if (dotProduct > maxDotProduct) { + maxVertex = it; + maxDotProduct = dotProduct; + isOptimal = false; + } + } + } while (!isOptimal); + // Cache the support vertex + _cachedCollisionData.data = maxVertex; + // Return the support vertex + return this.vertices.get(maxVertex).multiplyNew(this.scaling); + } else { + // If the edges information is not used + double maxDotProduct = Float.MIN_VALUE; + int indexMaxDotProduct = 0; + // For each vertex of the mesh + for (int i = 0; i < this.numberVertices; i++) { + // Compute the dot product of the current vertex + final double dotProduct = _direction.dot(this.vertices.get(i)); + // If the current dot product is larger than the maximum one + if (dotProduct > maxDotProduct) { + indexMaxDotProduct = i; + maxDotProduct = dotProduct; + } + } + assert (maxDotProduct >= 0.0f); + // Return the vertex with the largest dot product in the support direction + return this.vertices.get(indexMaxDotProduct).multiplyNew(this.scaling); + } + } + + /** + * Return true if the edges information is used to speed up the collision detection + * @return True if the edges information is used and false otherwise + */ + public boolean isEdgesInformationUsed() { + return this.isEdgesInformationUsed; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + return _proxyShape.getBody().getWorld().getCollisionDetection().getNarrowPhaseGJKAlgorithm().raycast(_ray, _proxyShape, _raycastInfo); + } + + /// Recompute the bounds of the mesh + protected void recalculateBounds() { + // TODO : Only works if the local origin is inside the mesh + // => Make it more robust (init with first vertex of mesh instead) + this.minBounds.setZero(); + this.maxBounds.setZero(); + // For each vertex of the mesh + for (int i = 0; i < this.numberVertices; i++) { + if (this.vertices.get(i).x > this.maxBounds.x) { + this.maxBounds.setX(this.vertices.get(i).x); + } + if (this.vertices.get(i).x < this.minBounds.x) { + this.minBounds.setX(this.vertices.get(i).x); + } + if (this.vertices.get(i).y > this.maxBounds.y) { + this.maxBounds.setY(this.vertices.get(i).y); + } + if (this.vertices.get(i).y < this.minBounds.y) { + this.minBounds.setY(this.vertices.get(i).y); + } + if (this.vertices.get(i).z > this.maxBounds.z) { + this.maxBounds.setZ(this.vertices.get(i).z); + } + if (this.vertices.get(i).z < this.minBounds.z) { + this.minBounds.setZ(this.vertices.get(i).z); + } + } + // Apply the local scaling factor + this.maxBounds.multiply(this.scaling); + this.minBounds.multiply(this.scaling); + // Add the object margin to the bounds + this.maxBounds.add(this.margin); + this.minBounds.less(this.margin); + } + + /** + * Set the variable to know if the edges information is used to speed up the + * collision detection + * @param isEdgesUsed True if you want to use the edges information to speed up the collision detection with the convex mesh shape + */ + public void setIsEdgesInformationUsed(final boolean _isEdgesUsed) { + this.isEdgesInformationUsed = _isEdgesUsed; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + super.setLocalScaling(_scaling); + recalculateBounds(); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + // Use the GJK algorithm to test if the point is inside the convex mesh + return _proxyShape.getBody().getWorld().getCollisionDetection().getNarrowPhaseGJKAlgorithm().testPointInside(_localPoint, _proxyShape); + } +}; diff --git a/src/org/atriasoft/ephysics/collision/shapes/ConvexShape.java b/src/org/atriasoft/ephysics/collision/shapes/ConvexShape.java new file mode 100644 index 0000000..69cff0a --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/ConvexShape.java @@ -0,0 +1,56 @@ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.Vector3f; + +public abstract class ConvexShape extends CollisionShape { + protected float margin; //!< Margin used for the GJK collision detection algorithm + /// Constructor + + public ConvexShape(final CollisionShapeType _type, final float _margin) { + super(_type); + this.margin = _margin; + } + + // Return a local support point in a given direction with the object margin + public Vector3f getLocalSupportPointWithMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + ////Log.error(" -> getLocalSupportPointWithMargin(" + _direction); + // Get the support point without margin + final Vector3f supportPoint = getLocalSupportPointWithoutMargin(_direction, _cachedCollisionData); + ////Log.error(" -> supportPoint = " + supportPoint); + ////Log.error(" -> margin = " + FMath.floatToString(this.margin)); + if (this.margin != 0.0f) { + // Add the margin to the support point + Vector3f unitVec = new Vector3f(0.0f, -1.0f, 0.0f); + ////Log.error(" -> _direction.length2()=" + FMath.floatToString(_direction.length2())); + ////Log.error(" -> Constant.FLOAT_EPSILON=" + FMath.floatToString(Constant.FLOAT_EPSILON)); + if (_direction.length2() > Constant.FLOAT_EPSILON * Constant.FLOAT_EPSILON) { + unitVec = _direction.safeNormalizeNew(); + ////Log.error(" -> unitVec= " + unitVec); + } + supportPoint.add(unitVec.multiplyNew(this.margin)); + } + ////Log.error(" -> ==> supportPoint = " + supportPoint); + return supportPoint; + } + + /// Return a local support point in a given direction without the object margin + public abstract Vector3f getLocalSupportPointWithoutMargin(Vector3f _direction, CacheData _cachedCollisionData); + + /** + * @brief Get the current object margin + * @return The margin (in meters) around the collision shape + */ + public float getMargin() { + return this.margin; + } + + @Override + public boolean isConvex() { + return true; + } + + @Override + public abstract boolean testPointInside(Vector3f _worldPoint, ProxyShape _proxyShape); +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/CylinderShape.java b/src/org/atriasoft/ephysics/collision/shapes/CylinderShape.java new file mode 100644 index 0000000..20bbf04 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/CylinderShape.java @@ -0,0 +1,297 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a cylinder collision shape around the Y axis and centered at the origin. The cylinder is + * defined by its height and the radius of its base. The "transform" of the corresponding rigid body gives an + * orientation and a position to the cylinder. This collision shape uses an extra margin distance around it for + * collision detection purpose. The default margin is 4cm (if your units are meters, which is recommended). In case, you + * want to simulate small objects (smaller than the margin distance), you might want to reduce the margin by specifying + * your own margin distance using the "margin" parameter in the constructor of the cylinder shape. Otherwise, it is + * recommended to use the default margin distance by not using the "margin" parameter in the constructor. + * + * @author Jason Sorensen + */ +/** + * It represents a cylinder collision shape around the Y axis + * and centered at the origin. The cylinder is defined by its height + * and the radius of its base. The "transform" of the corresponding + * rigid body gives an orientation and a position to the cylinder. + * This collision shape uses an extra margin distance around it for collision + * detection purpose. The default margin is 4cm (if your units are meters, + * which is recommended). In case, you want to simulate small objects + * (smaller than the margin distance), you might want to reduce the margin by + * specifying your own margin distance using the "margin" parameter in the + * ructor of the cylinder shape. Otherwise, it is recommended to use the + * default margin distance by not using the "margin" parameter in the ructor. + */ +public class CylinderShape extends ConvexShape { + protected float radius; //!< Radius of the base + protected float halfHeight; //!< Half height of the cylinder + + /** + * Contructor + * @param radius Radius of the cylinder (in meters) + * @param height Height of the cylinder (in meters) + * @param margin Collision margin (in meters) around the collision shape + */ + public CylinderShape(final float _radius, final float _height) { + this(_radius, _height, Defaults.OBJECT_MARGIN); + } + + public CylinderShape(final float _radius, final float _height, final float _margin) { + super(CollisionShapeType.CYLINDER, _margin); + this.radius = _radius; + this.halfHeight = _height / 2.0f; + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + final float height = 2.0f * this.halfHeight; + final float diag = (1.0f / 12.0f) * _mass * (3.0f * this.radius * this.radius + height * height); + _tensor.set(diag, 0.0f, 0.0f, 0.0f, 0.5f * _mass * this.radius * this.radius, 0.0f, 0.0f, 0.0f, diag); + } + + /** + * Get the Shape height + * @return Height of the cylinder (in meters) + */ + public float getHeight() { + return this.halfHeight + this.halfHeight; + } + + @Override + public void getLocalBounds(final Vector3f min, final Vector3f max) { + // Maximum bounds + max.setX(this.radius + this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(this.radius + this.margin); + // Minimum bounds + min.setX(-max.x); + min.setY(-max.y); + min.setZ(-max.z); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + final Vector3f supportPoint = new Vector3f(0.0f, 0.0f, 0.0f); + final float uDotv = _direction.y; + final Vector3f w = new Vector3f(_direction.x, 0.0f, _direction.z); + final float lengthW = FMath.sqrt(_direction.x * _direction.x + _direction.z * _direction.z); + if (lengthW > Constant.FLOAT_EPSILON) { + if (uDotv < 0.0f) { + supportPoint.setY(-this.halfHeight); + } else { + supportPoint.setY(this.halfHeight); + } + supportPoint.add(w.multiply(this.radius / lengthW)); + } else if (uDotv < 0.0f) { + supportPoint.setY(-this.halfHeight); + } else { + supportPoint.setY(this.halfHeight); + } + return supportPoint; + } + + /** + * Get the Shape radius + * @return Radius of the cylinder (in meters) + */ + public float getRadius() { + return this.radius; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f n = _ray.point2.lessNew(_ray.point1); + final float epsilon = 0.01f; + final Vector3f p = new Vector3f(0.0f, -this.halfHeight, 0.0f); + final Vector3f q = new Vector3f(0.0f, this.halfHeight, 0.0f); + final Vector3f d = q.lessNew(p); + final Vector3f m = _ray.point1.lessNew(p); + float t; + final float mDotD = m.dot(d); + final float nDotD = n.dot(d); + final float dDotD = d.dot(d); + // Test if the segment is outside the cylinder + if (mDotD < 0.0f && mDotD + nDotD < 0.0f) { + return false; + } + + if (mDotD > dDotD && mDotD + nDotD > dDotD) { + return false; + } + final float nDotN = n.dot(n); + final float mDotN = m.dot(n); + final float a = dDotD * nDotN - nDotD * nDotD; + final float k = m.dot(m) - this.radius * this.radius; + final float c = dDotD * k - mDotD * mDotD; + // If the ray is parallel to the cylinder axis + if (FMath.abs(a) < epsilon) { + // If the origin is outside the surface of the cylinder, we return no hit + if (c > 0.0f) { + return false; + } + // Here we know that the segment longersect an endcap of the cylinder + // If the ray longersects with the "p" endcap of the cylinder + if (mDotD < 0.0f) { + t = -mDotN / nDotN; + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + final Vector3f normalDirection = new Vector3f(0.0f, -1.0f, 0.0f); + _raycastInfo.worldNormal = normalDirection; + return true; + } + // If the ray longersects with the "q" endcap of the cylinder + if (mDotD > dDotD) { + t = (nDotD - mDotN) / nDotN; + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + _raycastInfo.worldNormal = new Vector3f(0, 1.0f, 0); + return true; + } + // If the origin is inside the cylinder, we return no hit + return false; + } + final float b = dDotD * mDotN - nDotD * mDotD; + final float discriminant = b * b - a * c; + // If the discriminant is negative, no real roots and therfore, no hit + if (discriminant < 0.0f) { + return false; + } + // Compute the smallest root (first longersection along the ray) + final float t0 = t = (-b - FMath.sqrt(discriminant)) / a; + // If the longersection is outside the cylinder on "p" endcap side + final float value = mDotD + t * nDotD; + if (value < 0.0f) { + // If the ray is pointing away from the "p" endcap, we return no hit + if (nDotD <= 0.0f) { + return false; + } + // Compute the longersection against the "p" endcap (longersection agains whole plane) + t = -mDotD / nDotD; + // Keep the longersection if the it is inside the cylinder radius + if (k + t * (2.0f * mDotN + t) > 0.0f) { + return false; + } + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + _raycastInfo.worldNormal = new Vector3f(0, -1.0f, 0); + return true; + } + // If the longersection is outside the cylinder on the "q" side + if (value > dDotD) { + // If the ray is pointing away from the "q" endcap, we return no hit + if (nDotD >= 0.0f) { + return false; + } + // Compute the longersection against the "q" endcap (longersection against whole plane) + t = (dDotD - mDotD) / nDotD; + // Keep the longersection if it is inside the cylinder radius + if (k + dDotD - 2.0f * mDotD + t * (2.0f * (mDotN - nDotD) + t) > 0.0f) { + return false; + } + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + _raycastInfo.worldNormal = new Vector3f(0, 1.0f, 0); + return true; + } + t = t0; + // If the longersection is behind the origin of the ray or beyond the maximum + // raycasting distance, we return no hit + if (t < 0.0f || t > _ray.maxFraction) { + return false; + } + // Compute the hit information + final Vector3f localHitPoint = n.multiplyNew(t).add(_ray.point1); + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = localHitPoint; + final Vector3f v = localHitPoint.lessNew(p); + final Vector3f w = d.multiplyNew(v.dot(d) / d.length2()); + final Vector3f normalDirection = localHitPoint.lessNew(p.addNew(w)); + _raycastInfo.worldNormal = normalDirection; + return true; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y) * _scaling.y; + this.radius = (this.radius / this.scaling.x) * _scaling.x; + super.setLocalScaling(_scaling); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + return ((_localPoint.x * _localPoint.x + _localPoint.z * _localPoint.z) < this.radius * this.radius && _localPoint.y < this.halfHeight && _localPoint.y > -this.halfHeight); + } +} \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/shapes/HeightFieldShape.java b/src/org/atriasoft/ephysics/collision/shapes/HeightFieldShape.java new file mode 100644 index 0000000..290d931 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/HeightFieldShape.java @@ -0,0 +1,305 @@ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.mathematics.Ray; + +/** + * This class represents a static height field that can be used to represent + * a terrain. The height field is made of a grid with rows and columns with a + * height value at each grid point. Note that the height values are not copied longo the shape + * but are shared instead. The height values can be of type longeger, float or double. + * When creating a HeightFieldShape, you need to specify the minimum and maximum height value of + * your height field. Note that the HeightFieldShape will be re-centered based on its AABB. It means + * that for instance, if the minimum height value is -200 and the maximum value is 400, the final + * minimum height of the field in the simulation will be -300 and the maximum height will be 300. + */ +public class HeightFieldShape extends ConcaveShape { + + /** + * This class is used for testing AABB and triangle overlap for raycasting + */ + public class TriangleOverlapCallback implements TriangleCallback { + protected Ray ray; + protected ProxyShape proxyShape; + protected RaycastInfo raycastInfo; + protected boolean isHit; + protected float smallestHitFraction; + protected HeightFieldShape heightFieldShape; + + public TriangleOverlapCallback(final Ray _ray, final ProxyShape _proxyShape, final RaycastInfo _raycastInfo, final HeightFieldShape _heightFieldShape) { + this.ray = _ray; + this.proxyShape = _proxyShape; + this.raycastInfo = _raycastInfo; + this.heightFieldShape = _heightFieldShape; + this.isHit = false; + this.smallestHitFraction = this.ray.maxFraction; + } + + public boolean getIsHit() { + return this.isHit; + } + + /// Raycast test between a ray and a triangle of the heightfield + @Override + public void testTriangle(final Vector3f[] _trianglePoints) { + // Create a triangle collision shape + final float margin = this.heightFieldShape.getTriangleMargin(); + final TriangleShape triangleShape = new TriangleShape(_trianglePoints[0], _trianglePoints[1], _trianglePoints[2], margin); + triangleShape.setRaycastTestType(this.heightFieldShape.getRaycastTestType()); + // Ray casting test against the collision shape + final RaycastInfo raycastInfo = new RaycastInfo(); + final boolean isTriangleHit = triangleShape.raycast(this.ray, raycastInfo, this.proxyShape); + // If the ray hit the collision shape + if (isTriangleHit && raycastInfo.hitFraction <= this.smallestHitFraction) { + assert (raycastInfo.hitFraction >= 0.0f); + this.raycastInfo.body = raycastInfo.body; + this.raycastInfo.proxyShape = raycastInfo.proxyShape; + this.raycastInfo.hitFraction = raycastInfo.hitFraction; + this.raycastInfo.worldPoint = raycastInfo.worldPoint; + this.raycastInfo.worldNormal = raycastInfo.worldNormal; + this.raycastInfo.meshSubpart = -1; + this.raycastInfo.triangleIndex = -1; + this.smallestHitFraction = raycastInfo.hitFraction; + this.isHit = true; + } + } + }; + + protected int numberColumns; //!< Number of columns in the grid of the height field + protected int numberRows; //!< Number of rows in the grid of the height field + + protected float width; //!< Height field width + protected float length; //!< Height field length + protected float minHeight; //!< Minimum height of the height field + protected float maxHeight; //!< Maximum height of the height field + protected int upAxis; //!< Up axis direction (0 => x, 1 => y, 2 => z) + protected float[] heightFieldData; //!< Array of data with all the height values of the height field + protected AABB AABB; //!< Local AABB of the height field (without scaling) + + /** + * Contructor + * @param nbGridColumns Number of columns in the grid of the height field + * @param nbGridRows Number of rows in the grid of the height field + * @param minHeight Minimum height value of the height field + * @param maxHeight Maximum height value of the height field + * @param heightFieldData Pointer to the first height value data (note that values are shared and not copied) + * @param dataType Data type for the height values (long, float, double) + * @param upAxis Integer representing the up axis direction (0 for x, 1 for y and 2 for z) + * @param longegerHeightScale Scaling factor used to scale the height values (only when height values type is longeger) + */ + public HeightFieldShape(final int _nbGridColumns, final int _nbGridRows, final float _minHeight, final float _maxHeight, final float[] _heightFieldData) { + this(_nbGridColumns, _nbGridRows, _minHeight, _maxHeight, _heightFieldData, 1); + } + + /// Insert all the triangles longo the dynamic AABB tree + //protected void initBVHTree(); + + public HeightFieldShape(final int _nbGridColumns, final int _nbGridRows, final float _minHeight, final float _maxHeight, final float[] _heightFieldData, final int _upAxis) { + super(CollisionShapeType.HEIGHTFIELD); + this.numberColumns = _nbGridColumns; + this.numberRows = _nbGridRows; + this.width = _nbGridColumns - 1; + this.length = _nbGridRows - 1; + this.minHeight = _minHeight; + this.maxHeight = _maxHeight; + this.upAxis = _upAxis; + assert (_nbGridColumns >= 2); + assert (_nbGridRows >= 2); + assert (this.width >= 1); + assert (this.length >= 1); + assert (_minHeight <= _maxHeight); + assert (_upAxis == 0 || _upAxis == 1 || _upAxis == 2); + this.heightFieldData = _heightFieldData; + final float halfHeight = (this.maxHeight - this.minHeight) * 0.5f; + assert (halfHeight >= 0); + // Compute the local AABB of the height field + if (this.upAxis == 0) { + this.AABB.setMin(new Vector3f(-halfHeight, -this.width * 0.5f, -this.length * 0.5f)); + this.AABB.setMax(new Vector3f(halfHeight, this.width * 0.5f, this.length * 0.5f)); + } else if (this.upAxis == 1) { + this.AABB.setMin(new Vector3f(-this.width * 0.5f, -halfHeight, -this.length * 0.5f)); + this.AABB.setMax(new Vector3f(this.width * 0.5f, halfHeight, this.length * 0.5f)); + } else if (this.upAxis == 2) { + this.AABB.setMin(new Vector3f(-this.width * 0.5f, -this.length * 0.5f, -halfHeight)); + this.AABB.setMax(new Vector3f(this.width * 0.5f, this.length * 0.5f, halfHeight)); + } + } + + /// Return the closest inside longeger grid value of a given floating grid value + protected int computeIntegerGridValue(final float _value) { + return (int) ((_value < 0.0f) ? _value - 0.5f : _value + 0.5f); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + // Default inertia tensor + // Note that this is not very realistic for a concave triangle mesh. + // However, in most cases, it will only be used static bodies and therefore, + // the inertia tensor is not used. + _tensor.set(_mass, 0.0f, 0.0f, 0.0f, _mass, 0.0f, 0.0f, 0.0f, _mass); + } + + /// Compute the min/max grid coords corresponding to the longersection of the AABB of the height field and the AABB to collide + protected void computeMinMaxGridCoordinates(final Vector3f _minCoords, final Vector3f _maxCoords, final AABB _aabbToCollide) { + // Clamp the min/max coords of the AABB to collide inside the height field AABB + Vector3f minPoint = FMath.max(_aabbToCollide.getMin(), this.AABB.getMin()); + minPoint = FMath.min(minPoint, this.AABB.getMax()); + Vector3f maxPoint = FMath.min(_aabbToCollide.getMax(), this.AABB.getMax()); + maxPoint = FMath.max(maxPoint, this.AABB.getMin()); + // Translate the min/max points such that the we compute grid points from [0 ... mNbWidthGridPoints] + // and from [0 ... mNbLengthGridPoints] because the AABB coordinates range are [-mWdith/2 ... this.width/2] + // and [-this.length/2 ... this.length/2] + final Vector3f translateVec = this.AABB.getExtent().multiplyNew(0.5f); + minPoint.add(translateVec); + maxPoint.add(translateVec); + // Convert the floating min/max coords of the AABB longo closest longeger + // grid values (note that we use the closest grid coordinate that is out + // of the AABB) + _minCoords.set(computeIntegerGridValue(minPoint.x) - 1, computeIntegerGridValue(minPoint.y) - 1, computeIntegerGridValue(minPoint.z) - 1); + _maxCoords.set(computeIntegerGridValue(maxPoint.x) + 1, computeIntegerGridValue(maxPoint.y) + 1, computeIntegerGridValue(maxPoint.z) + 1); + } + + /// Return the height of a given (x,y) point in the height field + protected float getHeightAt(final int _xxx, final int _yyy) { + return this.heightFieldData[_yyy * this.numberColumns + _xxx]; + } + + @Override + public void getLocalBounds(final Vector3f _min, final Vector3f _max) { + _min.set(this.AABB.getMin().multiplyNew(this.scaling)); + _max.set(this.AABB.getMax().multiplyNew(this.scaling)); + } + + /// Return the number of columns in the height field + public long getNbColumns() { + return this.numberColumns; + } + + /// Return the number of rows in the height field + public long getNbRows() { + return this.numberRows; + } + + /// Return the three vertices coordinates (in the array outTriangleVertices) of a triangle + /// given the start vertex index pointer of the triangle. + /* + protected void getTriangleVerticesWithIndexPointer(final long _subPart, + final long _triangleIndex, + Vector3f* _outTriangleVertices) ; + */ + /// Return the vertex (local-coordinates) of the height field at a given (x,y) position + protected Vector3f getVertexAt(final int _xxx, final int _yyy) { + // Get the height value + final float height = getHeightAt(_xxx, _yyy); + // Height values origin + final float heightOrigin = -(this.maxHeight - this.minHeight) * 0.5f - this.minHeight; + Vector3f vertex = null; + switch (this.upAxis) { + case 0: + vertex = new Vector3f(heightOrigin + height, -this.width * 0.5f + _xxx, -this.length * 0.5f + _yyy); + break; + case 1: + vertex = new Vector3f(-this.width * 0.5f + _xxx, heightOrigin + height, -this.length * 0.5f + _yyy); + break; + case 2: + vertex = new Vector3f(-this.width * 0.5f + _xxx, -this.length * 0.5f + _yyy, heightOrigin + height); + break; + default: + assert (false); + } + assert (this.AABB.contains(vertex)); + return vertex.multiply(this.scaling); + } + + @Override + public boolean isConvex() { + return false; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + // TODO : Implement raycasting without using an AABB for the ray + // but using a dynamic AABB tree or octree instead + + final TriangleOverlapCallback triangleCallback = new TriangleOverlapCallback(_ray, _proxyShape, _raycastInfo, this); + // Compute the AABB for the ray + final Vector3f rayEnd = _ray.point2.lessNew(_ray.point1).multiply(_ray.maxFraction).add(_ray.point1); + + final AABB rayAABB = new AABB(FMath.min(_ray.point1, rayEnd), FMath.max(_ray.point1, rayEnd)); + testAllTriangles(triangleCallback, rayAABB); + return triangleCallback.getIsHit(); + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + super.setLocalScaling(_scaling); + } + + @Override + public void testAllTriangles(final TriangleCallback _callback, final AABB _localAABB) { + + // Compute the non-scaled AABB + final Vector3f inverseScaling = new Vector3f(1.0f / this.scaling.x, 1.0f / this.scaling.y, 1.0f / this.scaling.z); + + final AABB aabb = new AABB(_localAABB.getMin().multiplyNew(inverseScaling), _localAABB.getMax().multiplyNew(inverseScaling)); + // Compute the longeger grid coordinates inside the area we need to test for collision + final Vector3f minGridCoords = new Vector3f(); + final Vector3f maxGridCoords = new Vector3f(); + computeMinMaxGridCoordinates(minGridCoords, maxGridCoords, aabb); + // Compute the starting and ending coords of the sub-grid according to the up axis + int iMin = 0; + int iMax = 0; + int jMin = 0; + int jMax = 0; + switch (this.upAxis) { + case 0: + iMin = FMath.clamp((int) minGridCoords.y, 0, this.numberColumns - 1); + iMax = FMath.clamp((int) maxGridCoords.y, 0, this.numberColumns - 1); + jMin = FMath.clamp((int) minGridCoords.z, 0, this.numberRows - 1); + jMax = FMath.clamp((int) maxGridCoords.z, 0, this.numberRows - 1); + break; + case 1: + iMin = FMath.clamp((int) minGridCoords.x, 0, this.numberColumns - 1); + iMax = FMath.clamp((int) maxGridCoords.x, 0, this.numberColumns - 1); + jMin = FMath.clamp((int) minGridCoords.z, 0, this.numberRows - 1); + jMax = FMath.clamp((int) maxGridCoords.z, 0, this.numberRows - 1); + break; + case 2: + iMin = FMath.clamp((int) minGridCoords.x, 0, this.numberColumns - 1); + iMax = FMath.clamp((int) maxGridCoords.x, 0, this.numberColumns - 1); + jMin = FMath.clamp((int) minGridCoords.y, 0, this.numberRows - 1); + jMax = FMath.clamp((int) maxGridCoords.y, 0, this.numberRows - 1); + break; + } + assert (iMin >= 0 && iMin < this.numberColumns); + assert (iMax >= 0 && iMax < this.numberColumns); + assert (jMin >= 0 && jMin < this.numberRows); + assert (jMax >= 0 && jMax < this.numberRows); + // For each sub-grid points (except the last ones one each dimension) + for (int i = iMin; i < iMax; i++) { + for (int j = jMin; j < jMax; j++) { + // Compute the four point of the current quad + final Vector3f p1 = getVertexAt(i, j); + final Vector3f p2 = getVertexAt(i, j + 1); + final Vector3f p3 = getVertexAt(i + 1, j); + final Vector3f p4 = getVertexAt(i + 1, j + 1); + // Generate the first triangle for the current grid rectangle + final Vector3f[] trianglePoints = { p1, p2, p3 }; + // Test collision against the first triangle + _callback.testTriangle(trianglePoints); + // Generate the second triangle for the current grid rectangle + trianglePoints[0] = p3; + trianglePoints[1] = p2; + trianglePoints[2] = p4; + // Test collision against the second triangle + _callback.testTriangle(trianglePoints); + } + } + } + +} diff --git a/src/org/atriasoft/ephysics/collision/shapes/SphereShape.java b/src/org/atriasoft/ephysics/collision/shapes/SphereShape.java new file mode 100644 index 0000000..c9a27a9 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/SphereShape.java @@ -0,0 +1,142 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * Represents a sphere collision shape that is centered + * at the origin and defined by its radius. This collision shape does not + * have an explicit object margin distance. The margin is implicitly the + * radius of the sphere. Therefore, no need to specify an object margin + * for a sphere shape. + */ +public class SphereShape extends ConvexShape { + /** + * Constructor + * @param radius Radius of the sphere (in meters) + */ + public SphereShape(final float _radius) { + super(CollisionShapeType.SPHERE, _radius); + } + + @Override + public void computeAABB(final AABB _aabb, final Transform3D _transform) { + + // Get the local extents in x,y and z direction + final Vector3f extents = new Vector3f(this.margin, this.margin, this.margin); + // Update the AABB with the new minimum and maximum coordinates + _aabb.setMin(_transform.getPosition().lessNew(extents)); + _aabb.setMax(_transform.getPosition().addNew(extents)); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + final float diag = 0.4f * _mass * this.margin * this.margin; + _tensor.set(diag, 0.0f, 0.0f, 0.0f, diag, 0.0f, 0.0f, 0.0f, diag); + } + + @Override + public void getLocalBounds(final Vector3f min, final Vector3f max) { + // Maximum bounds + max.setX(this.margin); + max.setY(this.margin); + max.setZ(this.margin); + // Minimum bounds + min.setX(-max.x); + min.setY(-max.y); + min.setZ(-max.z); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + return new Vector3f(0.0f, 0.0f, 0.0f); + } + + /** + * Get the radius of the sphere + * @return Radius of the sphere (in meters) + */ + public float getRadius() { + return this.margin; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f m = _ray.point1; + final float c = m.dot(m) - this.margin * this.margin; + // If the origin of the ray is inside the sphere, we return no longersection + if (c < 0.0f) { + return false; + } + final Vector3f rayDirection = _ray.point2.lessNew(_ray.point1); + final float b = m.dot(rayDirection); + // If the origin of the ray is outside the sphere and the ray + // is pointing away from the sphere, there is no longersection + if (b > 0.0f) { + return false; + } + final float raySquareLength = rayDirection.length2(); + // Compute the discriminant of the quadratic equation + final float discriminant = b * b - raySquareLength * c; + // If the discriminant is negative or the ray length is very small, there is no longersection + if (discriminant < 0.0f || raySquareLength < Constant.FLOAT_EPSILON) { + return false; + } + // Compute the solution "t" closest to the origin + float t = -b - FMath.sqrt(discriminant); + assert (t >= 0.0f); + // If the hit point is withing the segment ray fraction + if (t < _ray.maxFraction * raySquareLength) { + // Compute the longersection information + t /= raySquareLength; + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.hitFraction = t; + _raycastInfo.worldPoint = rayDirection.multiplyNew(t).add(_ray.point1); + _raycastInfo.worldNormal = _raycastInfo.worldPoint; + return true; + } + return false; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.margin = (this.margin / this.scaling.x) * _scaling.x; + super.setLocalScaling(_scaling); + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + return (_localPoint.length2() < this.margin * this.margin); + } +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/collision/shapes/TriangleShape.java b/src/org/atriasoft/ephysics/collision/shapes/TriangleShape.java new file mode 100644 index 0000000..15aa904 --- /dev/null +++ b/src/org/atriasoft/ephysics/collision/shapes/TriangleShape.java @@ -0,0 +1,195 @@ +package org.atriasoft.ephysics.collision.shapes; + +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.RaycastInfo; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.configuration.Defaults; +import org.atriasoft.ephysics.mathematics.Ray; + +/** + * This class represents a triangle collision shape that is centered + * at the origin and defined three points. + */ +public class TriangleShape extends ConvexShape { + /** + * Raycast test side for the triangle + */ + public enum TriangleRaycastSide { + FRONT, //!< Raycast against front triangle + BACK, //!< Raycast against back triangle + FRONT_AND_BACK //!< Raycast against front and back triangle + }; + + protected Vector3f[] points = new Vector3f[3]; //!< Three points of the triangle + protected TriangleRaycastSide raycastTestType; //!< Raycast test type for the triangle (front, back, front-back) + + public TriangleShape(final Vector3f _point1, final Vector3f _point2, final Vector3f _point3) { + super(CollisionShapeType.TRIANGLE, Defaults.OBJECT_MARGIN); + this.points[0] = _point1; + this.points[1] = _point2; + this.points[2] = _point3; + this.raycastTestType = TriangleRaycastSide.FRONT; + } + + /** + * Constructor + * @param _point1 First point of the triangle + * @param _point2 Second point of the triangle + * @param _point3 Third point of the triangle + * @param _margin The collision margin (in meters) around the collision shape + */ + public TriangleShape(final Vector3f _point1, final Vector3f _point2, final Vector3f _point3, final float _margin) { + super(CollisionShapeType.TRIANGLE, _margin); + this.points[0] = _point1; + this.points[1] = _point2; + this.points[2] = _point3; + this.raycastTestType = TriangleRaycastSide.FRONT; + } + + @Override + public void computeAABB(final AABB aabb, final Transform3D _transform) { + final Vector3f worldPoint1 = _transform.multiplyNew(this.points[0]); + final Vector3f worldPoint2 = _transform.multiplyNew(this.points[1]); + final Vector3f worldPoint3 = _transform.multiplyNew(this.points[2]); + final Vector3f xAxis = new Vector3f(worldPoint1.x, worldPoint2.x, worldPoint3.x); + final Vector3f yAxis = new Vector3f(worldPoint1.y, worldPoint2.y, worldPoint3.y); + final Vector3f zAxis = new Vector3f(worldPoint1.z, worldPoint2.z, worldPoint3.z); + aabb.setMin(new Vector3f(xAxis.getMin(), yAxis.getMin(), zAxis.getMin())); + aabb.setMax(new Vector3f(xAxis.getMax(), yAxis.getMax(), zAxis.getMax())); + } + + @Override + public void computeLocalInertiaTensor(final Matrix3f _tensor, final float _mass) { + _tensor.setZero(); + } + + @Override + public void getLocalBounds(final Vector3f min, final Vector3f max) { + final Vector3f xAxis = new Vector3f(this.points[0].x, this.points[1].x, this.points[2].x); + final Vector3f yAxis = new Vector3f(this.points[0].y, this.points[1].y, this.points[2].y); + final Vector3f zAxis = new Vector3f(this.points[0].z, this.points[1].z, this.points[2].z); + min.setValue(xAxis.getMin() - this.margin, yAxis.getMin() - this.margin, zAxis.getMin() - this.margin); + max.setValue(xAxis.getMax() + this.margin, yAxis.getMax() + this.margin, zAxis.getMax() + this.margin); + } + + @Override + public Vector3f getLocalSupportPointWithoutMargin(final Vector3f _direction, final CacheData _cachedCollisionData) { + final Vector3f dotProducts = new Vector3f(_direction.dot(this.points[0]), _direction.dot(this.points[1]), _direction.dot(this.points[2])); + return this.points[dotProducts.getMaxAxis()]; + } + + /// Return the raycast test type (front, back, front-back) + public TriangleRaycastSide getRaycastTestType() { + return this.raycastTestType; + } + + /** + * Return the coordinates of a given vertex of the triangle + * @param _index Index (0 to 2) of a vertex of the triangle + */ + public Vector3f getVertex(final int _index) { + assert (_index >= 0 && _index < 3); + return this.points[_index]; + } + + @Override + public boolean raycast(final Ray _ray, final RaycastInfo _raycastInfo, final ProxyShape _proxyShape) { + final Vector3f pq = _ray.point2.lessNew(_ray.point1); + final Vector3f pa = this.points[0].lessNew(_ray.point1); + final Vector3f pb = this.points[1].lessNew(_ray.point1); + final Vector3f pc = this.points[2].lessNew(_ray.point1); + // Test if the line PQ is inside the eges BC, CA and AB. We use the triple + // product for this test. + final Vector3f m = pq.cross(pc); + float u = pb.dot(m); + if (this.raycastTestType == TriangleRaycastSide.FRONT) { + if (u < 0.0f) { + return false; + } + } else if (this.raycastTestType == TriangleRaycastSide.BACK) { + if (u > 0.0f) { + return false; + } + } + float v = -pa.dot(m); + if (this.raycastTestType == TriangleRaycastSide.FRONT) { + if (v < 0.0f) { + return false; + } + } else if (this.raycastTestType == TriangleRaycastSide.BACK) { + if (v > 0.0f) { + return false; + } + } else if (this.raycastTestType == TriangleRaycastSide.FRONT_AND_BACK) { + if (!FMath.sameSign(u, v)) { + return false; + } + } + float w = pa.dot(pq.cross(pb)); + if (this.raycastTestType == TriangleRaycastSide.FRONT) { + if (w < 0.0f) { + return false; + } + } else if (this.raycastTestType == TriangleRaycastSide.BACK) { + if (w > 0.0f) { + return false; + } + } else if (this.raycastTestType == TriangleRaycastSide.FRONT_AND_BACK) { + if (!FMath.sameSign(u, w)) { + return false; + } + } + // If the line PQ is in the triangle plane (case where u=v=w=0) + if (FMath.approxEqual(u, 0.0f) && FMath.approxEqual(v, 0) && FMath.approxEqual(w, 0)) { + return false; + } + // Compute the barycentric coordinates (u, v, w) to determine the + // longersection point R, R = u * a + v * b + w * c + final float denom = 1.0f / (u + v + w); + u *= denom; + v *= denom; + w *= denom; + // Compute the local hit point using the barycentric coordinates + final Vector3f localHitPoint = this.points[0].multiplyNew(u).add(this.points[1].multiplyNew(v)).add(this.points[2].multiplyNew(w)); + final float hitFraction = localHitPoint.lessNew(_ray.point1).length() / pq.length(); + if (hitFraction < 0.0f || hitFraction > _ray.maxFraction) { + return false; + } + final Vector3f localHitNormal = this.points[1].lessNew(this.points[0]).cross(this.points[2].lessNew(this.points[0])); + if (localHitNormal.dot(pq) > 0.0f) { + localHitNormal.less(localHitNormal); + } + _raycastInfo.body = _proxyShape.getBody(); + _raycastInfo.proxyShape = _proxyShape; + _raycastInfo.worldPoint = localHitPoint; + _raycastInfo.hitFraction = hitFraction; + _raycastInfo.worldNormal = localHitNormal; + return true; + } + + @Override + public void setLocalScaling(final Vector3f _scaling) { + this.points[0].divide(this.scaling).multiply(_scaling); + this.points[1].divide(this.scaling).multiply(_scaling); + this.points[2].divide(this.scaling).multiply(_scaling); + super.setLocalScaling(_scaling); + } + + /** + * Set the raycast test type (front, back, front-back) + * @param _testType Raycast test type for the triangle (front, back, front-back) + */ + public void setRaycastTestType(final TriangleRaycastSide _testType) { + this.raycastTestType = _testType; + } + + @Override + public boolean testPointInside(final Vector3f _localPoint, final ProxyShape _proxyShape) { + return false; + } + +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/configuration/Defaults.java b/src/org/atriasoft/ephysics/configuration/Defaults.java new file mode 100644 index 0000000..d4c4ed4 --- /dev/null +++ b/src/org/atriasoft/ephysics/configuration/Defaults.java @@ -0,0 +1,86 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.configuration; + +import org.atriasoft.etk.math.Constant; + +/** + * + * @author Jason Sorensen + */ +public class Defaults { + + // Smallest decimal value (negative) + public static final float DECIMAL_SMALLEST = Float.MIN_VALUE; + + // Maximum decimal value + public static final float DECIMAL_LARGEST = Float.MAX_VALUE; + + // Default internal constant timestep in seconds + public static final float DEFAULT_TIMESTEP = 1.0f / 60.0f; + + // Default friction coefficient for a rigid body + public static final float DEFAULT_FRICTION_COEFFICIENT = 0.3f; + + // Default bounciness factor for a rigid body + public static final float DEFAULT_BOUNCINESS = 0.5f; + + // True if the spleeping technique is enabled + public static final boolean SPLEEPING_ENABLED = true; + + // Object margin for collision detection in meters (for the GJK-EPA Algorithm) + public static final float OBJECT_MARGIN = 0.04f; + + // Distance threshold for two contact points for a valid persistent contact (in meters) + public static final float PERSISTENT_CONTACT_DIST_THRESHOLD = 0.03f; + + // Velocity threshold for contact velocity restitution + public static final float RESTITUTION_VELOCITY_THRESHOLD = 1.0f; + + // Number of iterations when solving the velocity constraints of the Sequential Impulse technique + public static final int DEFAULT_VELOCITY_SOLVER_NUM_ITERATIONS = 10; + + // Number of iterations when solving the position constraints of the Sequential Impulse technique + public static final int DEFAULT_POSITION_SOLVER_NUM_ITERATIONS = 5; + + // Time (in seconds) that a body must stay still to be considered sleeping + public static final float DEFAULT_TIME_BEFORE_SLEEP = 1.0f; + + // A body with a linear velocity smaller than the sleep linear velocity (in m/s) + // might enter sleeping mode. + public static final float DEFAULT_SLEEP_LINEAR_VELOCITY = 0.02f; + + // A body with angular velocity smaller than the sleep angular velocity (in rad/s) + // might enter sleeping mode + public static final float DEFAULT_SLEEP_ANGULAR_VELOCITY = 3.0f * (Constant.PI / 180.0f); + + /// Maximum number of contact manifolds in an overlapping pair that involves two + /// convex collision shapes. + public static final int NB_MAX_CONTACT_MANIFOLDS_CONVEX_SHAPE = 1; + + /// Maximum number of contact manifolds in an overlapping pair that involves at + /// least one concave collision shape. + public static final int NB_MAX_CONTACT_MANIFOLDS_CONCAVE_SHAPE = 3; +} diff --git a/src/org/atriasoft/ephysics/constraint/BallAndSocketJoint.java b/src/org/atriasoft/ephysics/constraint/BallAndSocketJoint.java new file mode 100644 index 0000000..51d5eeb --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/BallAndSocketJoint.java @@ -0,0 +1,215 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.engine.ConstraintSolverData; + +public class BallAndSocketJoint extends Joint { + + private static float BETA = 0.2f; //!< Beta value for the bias factor of position correction + private Vector3f localAnchorPointBody1 = new Vector3f(0, 0, 0); //!< Anchor point of body 1 (in local-space coordinates of body 1) + private Vector3f localAnchorPointBody2 = new Vector3f(0, 0, 0); //!< Anchor point of body 2 (in local-space coordinates of body 2) + private Vector3f r1World = new Vector3f(0, 0, 0); //!< Vector from center of body 2 to anchor point in world-space + private Vector3f r2World = new Vector3f(0, 0, 0); //!< Vector from center of body 2 to anchor point in world-space + private Matrix3f i1 = new Matrix3f(); //!< Inertia tensor of body 1 (in world-space coordinates) + private Matrix3f i2 = new Matrix3f(); //!< Inertia tensor of body 2 (in world-space coordinates) + private Vector3f biasVector = new Vector3f(0, 0, 0); //!< Bias vector for the raint + private Matrix3f inverseMassMatrix = new Matrix3f(); //!< Inverse mass matrix K=JM^-1J^-t of the raint + private final Vector3f impulse = new Vector3f(0, 0, 0); //!< Accumulated impulse + /// Constructor + + public BallAndSocketJoint(final BallAndSocketJointInfo jointInfo) { + super(jointInfo); + // Compute the local-space anchor point for each body + this.localAnchorPointBody1 = this.body1.getTransform().inverseNew().multiply(jointInfo.anchorPointWorldSpace); + this.localAnchorPointBody2 = this.body2.getTransform().inverseNew().multiply(jointInfo.anchorPointWorldSpace); + } + + @Override + public void initBeforeSolve(final ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body1); + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body2); + + // Get the bodies center of mass and orientations + final Vector3f x1 = this.body1.centerOfMassWorld; + final Vector3f x2 = this.body2.centerOfMassWorld; + final Quaternion orientationBody1 = this.body1.getTransform().getOrientation(); + final Quaternion orientationBody2 = this.body2.getTransform().getOrientation(); + + // Get the inertia tensor of bodies + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = orientationBody1.multiply(this.localAnchorPointBody1); + this.r2World = orientationBody2.multiply(this.localAnchorPointBody2); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the matrix K=JM^-1J^t (3x3 matrix) + final float inverseMassBodies = this.body1.massInverse + this.body2.massInverse; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).multiply(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).multiply(skewSymmetricMatrixU2.transposeNew())); + + // Compute the inverse mass matrix K^-1 + this.inverseMassMatrix.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrix = massMatrix.inverseNew(); + } + + // Compute the bias "b" of the raint + this.biasVector.setZero(); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + final float biasFactor = (BETA / raintSolverData.timeStep); + this.biasVector = (x2.addNew(this.r2World).less(x1).less(this.r1World)).multiply(biasFactor); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + + // Reset the accumulated impulse + this.impulse.setZero(); + } + } + + @Override + public void solvePositionConstraint(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Compute J*v + final Vector3f Jv = v2.addNew(w2.cross(this.r2World)).less(v1).less(w1.cross(this.r1World)); + + // Compute the Lagrange multiplier lambda + final Vector3f deltaLambda = this.inverseMassMatrix.multiplyNew(Jv.multiplyNew(-1).less(this.biasVector)); + this.impulse.add(deltaLambda); + + // Compute the impulse P=J^T * lambda for the body 1 + final Vector3f linearImpulseBody1 = deltaLambda.multiplyNew(-1); + final Vector3f angularImpulseBody1 = deltaLambda.cross(this.r1World); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(this.body1.massInverse)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the body 2 + final Vector3f angularImpulseBody2 = deltaLambda.cross(this.r2World).multiplyNew(-1); + + // Apply the impulse to the body 2 + v2.add(deltaLambda.multiplyNew(this.body2.massInverse)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + } + + @Override + public void solveVelocityConstraint(final ConstraintSolverData raintSolverData) { + + // If the error position correction technique is not the non-linear-gauss-seidel, we do + // do not execute this method + if (this.positionCorrectionTechnique != JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL) { + return; + } + + // Get the bodies center of mass and orientations + final Vector3f x1 = raintSolverData.positions[this.indexBody1]; + final Vector3f x2 = raintSolverData.positions[this.indexBody2]; + final Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + final Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Recompute the inverse inertia tensors + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = q1.multiply(this.localAnchorPointBody1); + this.r2World = q2.multiply(this.localAnchorPointBody2); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Recompute the inverse mass matrix K=J^TM^-1J of of the 3 translation raints + final float inverseMassBodies = inverseMassBody1 + inverseMassBody2; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).multiplyNew(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).multiplyNew(skewSymmetricMatrixU2.transposeNew())); + this.inverseMassMatrix.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrix = massMatrix.inverseNew(); + } + + // Compute the raint error (value of the C(x) function) + final Vector3f raintError = x2.addNew(this.r2World).less(x1).less(this.r1World); + + // Compute the Lagrange multiplier lambda + // TODO : Do not solve the system by computing the inverse each time and multiplying with the + // right-hand side vector but instead use a method to directly solve the linear system. + final Vector3f lambda = this.inverseMassMatrix.multiplyNew(raintError.multiplyNew(-1)); + + // Compute the impulse of body 1 + final Vector3f linearImpulseBody1 = lambda.multiplyNew(-1); + final Vector3f angularImpulseBody1 = lambda.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + final Vector3f v1 = linearImpulseBody1.multiplyNew(inverseMassBody1); + final Vector3f w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body center of mass and orientation of body 1 + x1.add(v1); + q1.add((new Quaternion(0, w1)).multiplyNew(q1).multiplyNew(0.5f)); + q1.normalize(); + + // Compute the impulse of body 2 + final Vector3f angularImpulseBody2 = lambda.cross(this.r2World).multiplyNew(-1); + + // Compute the pseudo velocity of body 2 + final Vector3f v2 = lambda.multiplyNew(inverseMassBody2); + final Vector3f w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + x2.add(v2); + q2.add((new Quaternion(0, w2)).multiplyNew(q2).multiply(0.5f)); + q2.normalize(); + } + + @Override + public void warmstart(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Compute the impulse P=J^T * lambda for the body 1 + final Vector3f linearImpulseBody1 = this.impulse.multiplyNew(-1); + final Vector3f angularImpulseBody1 = this.impulse.cross(this.r1World); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(this.body1.massInverse)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the body 2 + final Vector3f angularImpulseBody2 = this.impulse.cross(this.r2World).multiplyNew(-1); + + // Apply the impulse to the body to the body 2 + v2.add(this.impulse.multiplyNew(this.body2.massInverse)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + } + +} diff --git a/src/org/atriasoft/ephysics/constraint/BallAndSocketJointInfo.java b/src/org/atriasoft/ephysics/constraint/BallAndSocketJointInfo.java new file mode 100644 index 0000000..e17e4a2 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/BallAndSocketJointInfo.java @@ -0,0 +1,26 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * It is used to gather the information needed to create a ball-and-socket + * joint. This structure will be used to create the actual ball-and-socket joint. + */ +public class BallAndSocketJointInfo extends JointInfo { + + public Vector3f anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + + /** + * Constructor + * @param _rigidBody1 Pointer to the first body of the joint + * @param _rigidBody2 Pointer to the second body of the joint + * @param _initAnchorPointWorldSpace The anchor point in world-space coordinates + */ + public BallAndSocketJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace) { + super(_rigidBody1, _rigidBody2, JointType.BALLSOCKETJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + + } +} diff --git a/src/org/atriasoft/ephysics/constraint/ContactPoint.java b/src/org/atriasoft/ephysics/constraint/ContactPoint.java new file mode 100644 index 0000000..204f7eb --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/ContactPoint.java @@ -0,0 +1,174 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.CollisionBody; + +/** + * This class represents a collision contact point between two + * bodies in the physics engine. + */ +public class ContactPoint { + + private final CollisionBody body1; //!< First rigid body of the contact + private final CollisionBody body2; //!< Second rigid body of the contact + private final Vector3f normal; //!< Normalized normal vector of the contact (from body1 toward body2) in world space + + private float penetrationDepth; //!< Penetration depth + private final Vector3f localPointOnBody1; //!< Contact point on body 1 in local space of body 1 + private final Vector3f localPointOnBody2; //!< Contact point on body 2 in local space of body 2 + private Vector3f worldPointOnBody1; //!< Contact point on body 1 in world space + private Vector3f worldPointOnBody2; //!< Contact point on body 2 in world space + private boolean isRestingContact; //!< True if the contact is a resting contact (exists for more than one time step) + private Vector3f frictionVectors_1; //!< Two orthogonal vectors that span the tangential friction plane + private Vector3f frictionVectors_2; //!< Two orthogonal vectors that span the tangential friction plane + private float penetrationImpulse; //!< Cached penetration impulse + private float frictionImpulse1; //!< Cached first friction impulse + private float frictionImpulse2; //!< Cached second friction impulse + private Vector3f rollingResistanceImpulse; //!< Cached rolling resistance impulse + /// Constructor + + public ContactPoint(final ContactPointInfo contactInfo) { + this.body1 = contactInfo.shape1.getBody(); + this.body2 = contactInfo.shape2.getBody(); + this.normal = contactInfo.normal.clone(); + this.penetrationDepth = contactInfo.penetrationDepth; + this.localPointOnBody1 = contactInfo.localPoint1.clone(); + this.localPointOnBody2 = contactInfo.localPoint2.clone(); + this.worldPointOnBody1 = contactInfo.shape1.getBody().getTransform().multiplyNew(contactInfo.shape1.getLocalToBodyTransform().multiply(contactInfo.localPoint1)); + this.worldPointOnBody2 = contactInfo.shape2.getBody().getTransform().multiplyNew(contactInfo.shape2.getLocalToBodyTransform().multiply(contactInfo.localPoint2)); + this.isRestingContact = false; + + this.frictionVectors_1 = new Vector3f(0, 0, 0); + this.frictionVectors_2 = new Vector3f(0, 0, 0); + + assert (this.penetrationDepth >= 0.0f); + + } + + /// Return the reference to the body 1 + public CollisionBody getBody1() { + return this.body1; + } + + /// Return the reference to the body 2 + public CollisionBody getBody2() { + return this.body2; + } + + /// Return the cached first friction impulse + public float getFrictionImpulse1() { + return this.frictionImpulse1; + } + + /// Return the cached second friction impulse + public float getFrictionImpulse2() { + return this.frictionImpulse2; + } + + /// Get the second friction vector + public Vector3f getFrictionvec2() { + return this.frictionVectors_2; + } + + /// Get the first friction vector + public Vector3f getFrictionVector1() { + return this.frictionVectors_1; + } + + /// Return true if the contact is a resting contact + public boolean getIsRestingContact() { + return this.isRestingContact; + } + + /// Return the contact local point on body 1 + public Vector3f getLocalPointOnBody1() { + return this.localPointOnBody1; + } + + /// Return the contact local point on body 2 + public Vector3f getLocalPointOnBody2() { + return this.localPointOnBody2; + } + + /// Return the normal vector of the contact + public Vector3f getNormal() { + return this.normal; + } + + /// Return the penetration depth + public float getPenetrationDepth() { + return this.penetrationDepth; + } + + /// Return the cached penetration impulse + public float getPenetrationImpulse() { + return this.penetrationImpulse; + } + + /// Return the cached rolling resistance impulse + public Vector3f getRollingResistanceImpulse() { + return this.rollingResistanceImpulse; + } + + /// Return the contact world point on body 1 + public Vector3f getWorldPointOnBody1() { + return this.worldPointOnBody1; + } + + /// Return the contact world point on body 2 + public Vector3f getWorldPointOnBody2() { + return this.worldPointOnBody2; + } + + /// Set the first cached friction impulse + public void setFrictionImpulse1(final float impulse) { + this.frictionImpulse1 = impulse; + } + + /// Set the second cached friction impulse + public void setFrictionImpulse2(final float impulse) { + this.frictionImpulse2 = impulse; + } + + /// Set the second friction vector + public void setFrictionvec2(final Vector3f frictionvec2) { + this.frictionVectors_2 = frictionvec2; + } + + /// Set the first friction vector + public void setFrictionVector1(final Vector3f frictionVector1) { + this.frictionVectors_1 = frictionVector1; + } + + /// Set the this.isRestingContact variable + public void setIsRestingContact(final boolean isRestingContact) { + this.isRestingContact = isRestingContact; + } + + /// Set the penetration depth of the contact + public void setPenetrationDepth(final float penetrationDepth) { + this.penetrationDepth = penetrationDepth; + } + + /// Set the cached penetration impulse + public void setPenetrationImpulse(final float impulse) { + this.penetrationImpulse = impulse; + } + + /// Set the cached rolling resistance impulse + public void setRollingResistanceImpulse(final Vector3f impulse) { + this.rollingResistanceImpulse = impulse; + } + + /// Set the contact world point on body 1 + public void setWorldPointOnBody1(final Vector3f worldPoint) { + this.worldPointOnBody1 = worldPoint; + } + + /// Set the contact world point on body 2 + public void setWorldPointOnBody2(final Vector3f worldPoint) { + this.worldPointOnBody2 = worldPoint; + } + +} diff --git a/src/org/atriasoft/ephysics/constraint/ContactPointInfo.java b/src/org/atriasoft/ephysics/constraint/ContactPointInfo.java new file mode 100644 index 0000000..eac4710 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/ContactPointInfo.java @@ -0,0 +1,54 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.shapes.CollisionShape; + +/** + * This structure contains informations about a collision contact + * computed during the narrow-phase collision detection. Those + * informations are used to compute the contact set for a contact + * between two bodies. + */ +public class ContactPointInfo { + public ProxyShape shape1; //!< First proxy shape of the contact + public ProxyShape shape2; //!< Second proxy shape of the contact + public CollisionShape collisionShape1; //!< First collision shape + public CollisionShape collisionShape2; //!< Second collision shape + public Vector3f normal; //!< Normalized normal vector of the collision contact in world space + public float penetrationDepth; //!< Penetration depth of the contact + public Vector3f localPoint1; //!< Contact point of body 1 in local space of body 1 + public Vector3f localPoint2; //!< Contact point of body 2 in local space of body 2 + + public ContactPointInfo() { + this.shape1 = null; + this.shape2 = null; + this.collisionShape1 = null; + this.collisionShape2 = null; + } + + public ContactPointInfo(final ContactPointInfo obj) { + this.shape1 = obj.shape1; + this.shape2 = obj.shape2; + this.collisionShape1 = obj.collisionShape1; + this.collisionShape2 = obj.collisionShape2; + this.normal = obj.normal; + this.penetrationDepth = obj.penetrationDepth; + this.localPoint1 = obj.localPoint1; + this.localPoint2 = obj.localPoint2; + } + + public ContactPointInfo(final ProxyShape _proxyShape1, final ProxyShape _proxyShape2, final CollisionShape _collShape1, final CollisionShape _collShape2, final Vector3f _normal, + final float _penetrationDepth, final Vector3f _localPoint1, final Vector3f _localPoint2) { + this.shape1 = _proxyShape1; + this.shape2 = _proxyShape2; + this.collisionShape1 = _collShape1; + this.collisionShape2 = _collShape2; + this.normal = _normal; + this.penetrationDepth = _penetrationDepth; + this.localPoint1 = _localPoint1; + this.localPoint2 = _localPoint2; + + } +}; \ No newline at end of file diff --git a/src/org/atriasoft/ephysics/constraint/ContactsPositionCorrectionTechnique.java b/src/org/atriasoft/ephysics/constraint/ContactsPositionCorrectionTechnique.java new file mode 100644 index 0000000..4e7775a --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/ContactsPositionCorrectionTechnique.java @@ -0,0 +1,12 @@ +package org.atriasoft.ephysics.constraint; + +/// Position correction technique used in the contact solver (for contacts) +/// BAUMGARTE_CONTACTS : Faster but can be innacurate and can lead to unexpected bounciness +/// in some situations (due to error correction factor being added to +/// the bodies momentum). +/// SPLIT_IMPULSES : A bit slower but the error correction factor is not added to the +/// bodies momentum. This is the option used by default. +public enum ContactsPositionCorrectionTechnique { + BAUMGARTE_CONTACTS, + SPLIT_IMPULSES +} diff --git a/src/org/atriasoft/ephysics/constraint/FixedJoint.java b/src/org/atriasoft/ephysics/constraint/FixedJoint.java new file mode 100644 index 0000000..e94b76c --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/FixedJoint.java @@ -0,0 +1,313 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.engine.ConstraintSolverData; + +public class FixedJoint extends Joint { + + protected static float BETA = 0.2f; //!< Beta value for the bias factor of position correction + protected Vector3f localAnchorPointBody1 = new Vector3f(0, 0, 0); //!< Anchor point of body 1 (in local-space coordinates of body 1) + protected Vector3f localAnchorPointBody2 = new Vector3f(0, 0, 0); //!< Anchor point of body 2 (in local-space coordinates of body 2) + protected Vector3f r1World = new Vector3f(0, 0, 0); //!< Vector from center of body 2 to anchor point in world-space + protected Vector3f r2World = new Vector3f(0, 0, 0); //!< Vector from center of body 2 to anchor point in world-space + protected Matrix3f i1 = new Matrix3f(); //!< Inertia tensor of body 1 (in world-space coordinates) + protected Matrix3f i2 = new Matrix3f(); //!< Inertia tensor of body 2 (in world-space coordinates) + protected Vector3f impulseTranslation = new Vector3f(0, 0, 0); //!< Accumulated impulse for the 3 translation raints + protected Vector3f impulseRotation = new Vector3f(0, 0, 0); //!< Accumulate impulse for the 3 rotation raints + protected Matrix3f inverseMassMatrixTranslation = new Matrix3f(); //!< Inverse mass matrix K=JM^-1J^-t of the 3 translation raints (3x3 matrix) + protected Matrix3f inverseMassMatrixRotation = new Matrix3f(); //!< Inverse mass matrix K=JM^-1J^-t of the 3 rotation raints (3x3 matrix) + protected Vector3f biasTranslation = new Vector3f(0, 0, 0); //!< Bias vector for the 3 translation raints + protected Vector3f biasRotation = new Vector3f(0, 0, 0); //!< Bias vector for the 3 rotation raints + protected Quaternion initOrientationDifferenceInv = new Quaternion(); //!< Inverse of the initial orientation difference between the two bodies + + /// Constructor + public FixedJoint(final FixedJointInfo jointInfo) { + super(jointInfo); + // Compute the local-space anchor point for each body + final Transform3D transform1 = this.body1.getTransform(); + final Transform3D transform2 = this.body2.getTransform(); + this.localAnchorPointBody1 = transform1.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + this.localAnchorPointBody2 = transform2.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation().multiplyNew(transform1.getOrientation().inverseNew()); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); + } + + @Override + public void initBeforeSolve(final ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body1); + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body2); + + // Get the bodies positions and orientations + final Vector3f x1 = this.body1.centerOfMassWorld; + final Vector3f x2 = this.body2.centerOfMassWorld; + final Quaternion orientationBody1 = this.body1.getTransform().getOrientation(); + final Quaternion orientationBody2 = this.body2.getTransform().getOrientation(); + + // Get the inertia tensor of bodies + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = orientationBody1.multiply(this.localAnchorPointBody1); + this.r2World = orientationBody2.multiply(this.localAnchorPointBody2); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + final float inverseMassBodies = this.body1.massInverse + this.body2.massInverse; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).multiply(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).multiply(skewSymmetricMatrixU2.transposeNew())); + + // Compute the inverse mass matrix K^-1 for the 3 translation raints + this.inverseMassMatrixTranslation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.inverseNew(); + } + + // Compute the bias "b" of the raint for the 3 translation raints + final float biasFactor = (BETA / raintSolverData.timeStep); + this.biasTranslation.setZero(); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.biasTranslation = x2.addNew(this.r2World).less(x1).less(this.r1World).multiply(biasFactor); + } + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotation = this.i1.addNew(this.i2); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotation = this.inverseMassMatrixRotation.inverseNew(); + } + + // Compute the bias "b" for the 3 rotation raints + this.biasRotation.setZero(); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + final Quaternion currentOrientationDifference = orientationBody2.multiplyNew(orientationBody1.inverseNew()); + currentOrientationDifference.normalize(); + final Quaternion qError = currentOrientationDifference.multiplyNew(this.initOrientationDifferenceInv); + this.biasRotation = qError.getVectorV().multiply(biasFactor * 2.0f); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + // Reset the accumulated impulses + this.impulseTranslation.setZero(); + this.impulseRotation.setZero(); + } + } + + @Override + public void solvePositionConstraint(final ConstraintSolverData raintSolverData) { + + // If the error position correction technique is not the non-linear-gauss-seidel, we do + // do not execute this method + if (this.positionCorrectionTechnique != JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL) { + return; + } + + // Get the bodies positions and orientations + final Vector3f x1 = raintSolverData.positions[this.indexBody1]; + final Vector3f x2 = raintSolverData.positions[this.indexBody2]; + final Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + final Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Recompute the inverse inertia tensors + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = q1.multiply(this.localAnchorPointBody1); + this.r2World = q2.multiply(this.localAnchorPointBody2); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // --------------- Translation Constraints --------------- // + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + final float inverseMassBodies = this.body1.massInverse + this.body2.massInverse; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).add(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).add(skewSymmetricMatrixU2.transposeNew())); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.inverseNew(); + } + + // Compute position error for the 3 translation raints + final Vector3f errorTranslation = x2.addNew(this.r2World).less(x1).less(this.r1World); + + // Compute the Lagrange multiplier lambda + final Vector3f lambdaTranslation = this.inverseMassMatrixTranslation.multiplyNew(errorTranslation.multiplyNew(-1)); + + // Compute the impulse of body 1 + final Vector3f linearImpulseBody1 = lambdaTranslation.multiplyNew(-1); + Vector3f angularImpulseBody1 = lambdaTranslation.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + final Vector3f v1 = linearImpulseBody1.multiplyNew(inverseMassBody1); + Vector3f w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + x1.add(v1); + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse of body 2 + final Vector3f angularImpulseBody2 = lambdaTranslation.cross(this.r2World).multiply(-1); + + // Compute the pseudo velocity of body 2 + final Vector3f v2 = lambdaTranslation.multiplyNew(inverseMassBody2); + Vector3f w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + x2.add(v2); + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + + // --------------- Rotation Constraints --------------- // + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotation = this.i1.addNew(this.i2); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotation = this.inverseMassMatrixRotation.inverseNew(); + } + + // Compute the position error for the 3 rotation raints + final Quaternion currentOrientationDifference = q2.multiplyNew(q1.inverseNew()); + currentOrientationDifference.normalize(); + final Quaternion qError = currentOrientationDifference.multiplyNew(this.initOrientationDifferenceInv); + final Vector3f errorRotation = qError.getVectorV().multiply(2.0f); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + final Vector3f lambdaRotation = this.inverseMassMatrixRotation.multiplyNew(errorRotation.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = lambdaRotation.multiplyNew(-1); + + // Compute the pseudo velocity of body 1 + w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the pseudo velocity of body 2 + w2 = this.i2.multiplyNew(lambdaRotation); + + // Update the body position/orientation of body 2 + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + } + + @Override + public void solveVelocityConstraint(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v for the 3 translation raints + final Vector3f JvTranslation = v2.addNew(w2.cross(this.r2World)).less(v1).less(w1.cross(this.r1World)); + + // Compute the Lagrange multiplier lambda + final Vector3f deltaLambda = this.inverseMassMatrixTranslation.multiplyNew(JvTranslation.multiplyNew(-1).less(this.biasTranslation)); + this.impulseTranslation.add(deltaLambda); + + // Compute the impulse P=J^T * lambda for body 1 + final Vector3f linearImpulseBody1 = deltaLambda.multiplyNew(-1); + Vector3f angularImpulseBody1 = deltaLambda.cross(this.r1World); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for body 2 + final Vector3f angularImpulseBody2 = deltaLambda.cross(this.r2World).multiply(-1); + + // Apply the impulse to the body 2 + v2.add(deltaLambda.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 3 rotation raints + final Vector3f JvRotation = w2.lessNew(w1); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + final Vector3f deltaLambda2 = this.inverseMassMatrixRotation.multiplyNew(JvRotation.multiplyNew(-1).less(this.biasRotation)); + this.impulseRotation.add(deltaLambda2); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 1 + angularImpulseBody1 = deltaLambda2.multiplyNew(-1); + + // Apply the impulse to the body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(deltaLambda2)); + } + + @Override + public void warmstart(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Compute the impulse P=J^T * lambda for the 3 translation raints for body 1 + final Vector3f linearImpulseBody1 = this.impulseTranslation.multiplyNew(-1); + final Vector3f angularImpulseBody1 = this.impulseTranslation.cross(this.r1World); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 1 + angularImpulseBody1.add(this.impulseRotation.multiplyNew(-1)); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 3 translation raints for body 2 + final Vector3f angularImpulseBody2 = this.impulseTranslation.cross(this.r2World).multiplyNew(-1); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 2 + angularImpulseBody2.add(this.impulseRotation); + + // Apply the impulse to the body 2 + v2.add(this.impulseTranslation.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + } + +} diff --git a/src/org/atriasoft/ephysics/constraint/FixedJointInfo.java b/src/org/atriasoft/ephysics/constraint/FixedJointInfo.java new file mode 100644 index 0000000..6a89b1a --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/FixedJointInfo.java @@ -0,0 +1,25 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * This structure is used to gather the information needed to create a fixed + * joint. This structure will be used to create the actual fixed joint. + */ +public class FixedJointInfo extends JointInfo { + + Vector3f anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + + /** + * Constructor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point of the joint in world-space coordinates + */ + FixedJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace) { + super(_rigidBody1, _rigidBody2, JointType.FIXEDJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + } +} diff --git a/src/org/atriasoft/ephysics/constraint/HingeJoint.java b/src/org/atriasoft/ephysics/constraint/HingeJoint.java new file mode 100644 index 0000000..790120f --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/HingeJoint.java @@ -0,0 +1,862 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector2f; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.engine.ConstraintSolverData; +import org.atriasoft.ephysics.mathematics.Matrix2f; + +/** + * It represents a hinge joint that allows arbitrary rotation + * between two bodies around a single axis. This joint has one degree of freedom. It + * can be useful to simulate doors or pendulumns. + */ +public class HingeJoint extends Joint { + + private static float BETA; //!< Beta value for the bias factor of position correction + private final Vector3f localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + private final Vector3f localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + private final Vector3f hingeLocalAxisBody1; //!< Hinge rotation axis (in local-space coordinates of body 1) + private final Vector3f hingeLocalAxisBody2; //!< Hinge rotation axis (in local-space coordiantes of body 2) + private Matrix3f i1; //!< Inertia tensor of body 1 (in world-space coordinates) + private Matrix3f i2; //!< Inertia tensor of body 2 (in world-space coordinates) + private Vector3f mA1; //!< Hinge rotation axis (in world-space coordinates) computed from body 1 + private Vector3f r1World; //!< Vector from center of body 2 to anchor point in world-space + private Vector3f r2World; //!< Vector from center of body 2 to anchor point in world-space + private Vector3f b2CrossA1; //!< Cross product of vector b2 and a1 + private Vector3f c2CrossA1; //!< Cross product of vector c2 and a1; + private final Vector3f impulseTranslation; //!< Impulse for the 3 translation raints + private final Vector2f impulseRotation; //!< Impulse for the 2 rotation raints + private float impulseLowerLimit; //!< Accumulated impulse for the lower limit raint + private float impulseUpperLimit; //!< Accumulated impulse for the upper limit raint + private float impulseMotor; //!< Accumulated impulse for the motor raint; + private Matrix3f inverseMassMatrixTranslation; //!< Inverse mass matrix K=JM^-1J^t for the 3 translation raints + private Matrix2f inverseMassMatrixRotation; //!< Inverse mass matrix K=JM^-1J^t for the 2 rotation raints + private float inverseMassMatrixLimitMotor; //!< Inverse of mass matrix K=JM^-1J^t for the limits and motor raints (1x1 matrix) + private float inverseMassMatrixMotor; //!< Inverse of mass matrix K=JM^-1J^t for the motor + private Vector3f bTranslation; //!< Bias vector for the error correction for the translation raints + private Vector2f bRotation; //!< Bias vector for the error correction for the rotation raints + private float bLowerLimit; //!< Bias of the lower limit raint + private float bUpperLimit; //!< Bias of the upper limit raint + private final Quaternion initOrientationDifferenceInv; //!< Inverse of the initial orientation difference between the bodies + private boolean isLimitEnabled; //!< True if the joint limits are enabled + private boolean isMotorEnabled; //!< True if the motor of the joint in enabled + private float lowerLimit; //!< Lower limit (minimum allowed rotation angle in radian) + private float upperLimit; //!< Upper limit (maximum translation distance) + private boolean isLowerLimitViolated; //!< True if the lower limit is violated + private boolean isUpperLimitViolated; //!< True if the upper limit is violated + private float motorSpeed; //!< Motor speed (in rad/s) + private float maxMotorTorque; //!< Maximum motor torque (in Newtons) that can be applied to reach to desired motor speed + /// Reset the limits + + /// Constructor + public HingeJoint(final HingeJointInfo jointInfo) { + super(jointInfo); + this.impulseTranslation = new Vector3f(0, 0, 0); + this.impulseRotation = new Vector2f(0, 0); + this.impulseLowerLimit = 0; + this.impulseUpperLimit = 0; + this.impulseMotor = 0; + this.isLimitEnabled = jointInfo.isLimitEnabled; + this.isMotorEnabled = jointInfo.isMotorEnabled; + this.lowerLimit = jointInfo.minAngleLimit; + this.upperLimit = jointInfo.maxAngleLimit; + + this.isLowerLimitViolated = false; + this.isUpperLimitViolated = false; + this.motorSpeed = jointInfo.motorSpeed; + this.maxMotorTorque = jointInfo.maxMotorTorque; + + assert (this.lowerLimit <= 0 && this.lowerLimit >= -2.0 * Constant.PI); + assert (this.upperLimit >= 0 && this.upperLimit <= 2.0 * Constant.PI); + + // Compute the local-space anchor point for each body + final Transform3D transform1 = this.body1.getTransform(); + final Transform3D transform2 = this.body2.getTransform(); + this.localAnchorPointBody1 = transform1.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + this.localAnchorPointBody2 = transform2.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + + // Compute the local-space hinge axis + this.hingeLocalAxisBody1 = transform1.getOrientation().inverseNew().multiply(jointInfo.rotationAxisWorld); + this.hingeLocalAxisBody2 = transform2.getOrientation().inverseNew().multiply(jointInfo.rotationAxisWorld); + this.hingeLocalAxisBody1.normalize(); + this.hingeLocalAxisBody2.normalize(); + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation().multiplyNew(transform1.getOrientation().inverseNew()); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); + } + + /// Given an "inputAngle" in the range [-pi, pi], this method returns an + /// angle (modulo 2*pi) in the range [-2*pi; 2*pi] that is closest to one of the + /// two angle limits in arguments. + private float computeCorrespondingAngleNearLimits(final float inputAngle, final float lowerLimitAngle, final float upperLimitAngle) { + if (upperLimitAngle <= lowerLimitAngle) { + return inputAngle; + } else if (inputAngle > upperLimitAngle) { + final float diffToUpperLimit = FMath.abs(computeNormalizedAngle(inputAngle - upperLimitAngle)); + final float diffToLowerLimit = FMath.abs(computeNormalizedAngle(inputAngle - lowerLimitAngle)); + return (diffToUpperLimit > diffToLowerLimit) ? (inputAngle - Constant.PI_2) : inputAngle; + } else if (inputAngle < lowerLimitAngle) { + final float diffToUpperLimit = FMath.abs(computeNormalizedAngle(upperLimitAngle - inputAngle)); + final float diffToLowerLimit = FMath.abs(computeNormalizedAngle(lowerLimitAngle - inputAngle)); + return (diffToUpperLimit > diffToLowerLimit) ? inputAngle : (inputAngle + Constant.PI_2); + } else { + return inputAngle; + } + } + + /// Compute the current angle around the hinge axis + private float computeCurrentHingeAngle(final Quaternion orientationBody1, final Quaternion orientationBody2) { + + float hingeAngle; + + // Compute the current orientation difference between the two bodies + final Quaternion currentOrientationDiff = orientationBody2.multiplyNew(orientationBody1.inverseNew()); + currentOrientationDiff.normalize(); + + // Compute the relative rotation idering the initial orientation difference + final Quaternion relativeRotation = currentOrientationDiff.multiplyNew(this.initOrientationDifferenceInv); + relativeRotation.normalize(); + + // A quaternion q = [cos(theta/2); sin(theta/2) * rotAxis] where rotAxis is a unit + // length vector. We can extract cos(theta/2) with q.w and we can extract |sin(theta/2)| with : + // |sin(theta/2)| = q.getVectorV().length() since rotAxis is unit length. Note that any + // rotation can be represented by a quaternion q and -q. Therefore, if the relative rotation + // axis is not pointing in the same direction as the hinge axis, we use the rotation -q which + // has the same |sin(theta/2)| value but the value cos(theta/2) is sign inverted. Some details + // about this trick is explained in the source code of OpenTissue (http://www.opentissue.org). + final float cosHalfAngle = relativeRotation.getW(); + final float sinHalfAngleAbs = relativeRotation.getVectorV().length(); + + // Compute the dot product of the relative rotation axis and the hinge axis + final float dotProduct = relativeRotation.getVectorV().dot(this.mA1); + + // If the relative rotation axis and the hinge axis are pointing the same direction + if (dotProduct >= 0.0f) { + hingeAngle = 2.0f * FMath.atan2(sinHalfAngleAbs, cosHalfAngle); + } else { + hingeAngle = 2.0f * FMath.atan2(sinHalfAngleAbs, -cosHalfAngle); + } + + // Convert the angle from range [-2*pi; 2*pi] into the range [-pi; pi] + hingeAngle = computeNormalizedAngle(hingeAngle); + + // Compute and return the corresponding angle near one the two limits + return computeCorrespondingAngleNearLimits(hingeAngle, this.lowerLimit, this.upperLimit); + } + + /// Given an angle in radian, this method returns the corresponding + /// angle in the range [-pi; pi] + private float computeNormalizedAngle(float angle) { + + // Convert it into the range [-2*pi; 2*pi] + angle = FMath.mod(angle, Constant.PI_2); + + // Convert it into the range [-pi; pi] + if (angle < -Constant.PI) { + return angle + Constant.PI_2; + } else if (angle > Constant.PI) { + return angle - Constant.PI_2; + } else { + return angle; + } + } + + /**Enable/Disable the limits of the joint + * @param isLimitEnabled True if you want to enable the limits of the joint and + * false otherwise + */ + public void enableLimit(final boolean isLimitEnabled) { + + if (this.isLimitEnabled != isLimitEnabled) { + + this.isLimitEnabled = isLimitEnabled; + + // Reset the limits + resetLimits(); + } + } + + /**Enable/Disable the motor of the joint + * @param isMotorEnabled True if you want to enable the motor of the joint and false otherwise + */ + public void enableMotor(final boolean isMotorEnabled) { + + this.isMotorEnabled = isMotorEnabled; + this.impulseMotor = 0.0f; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + + /// Return the maximum angle limit + /** + * @return The maximum limit angle of the joint (in radian) + */ + public float getMaxAngleLimit() { + return this.upperLimit; + } + + /// Return the maximum motor torque + /** + * @return The maximum torque of the joint motor (in Newtons) + */ + public float getMaxMotorTorque() { + return this.maxMotorTorque; + } + + /// Return the minimum angle limit + /** + * @return The minimum limit angle of the joint (in radian) + */ + public float getMinAngleLimit() { + return this.lowerLimit; + } + + /// Return the motor speed + /** + * @return The current speed of the joint motor (in radian per second) + */ + public float getMotorSpeed() { + return this.motorSpeed; + } + + /// Return the intensity of the current torque applied for the joint motor + /** + * @param timeStep The current time step (in seconds) + * @return The intensity of the current torque (in Newtons) of the joint motor + */ + public float getMotorTorque(final float timeStep) { + return this.impulseMotor / timeStep; + } + + @Override + public void initBeforeSolve(final ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body1); + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body2); + + // Get the bodies positions and orientations + final Vector3f x1 = this.body1.centerOfMassWorld; + final Vector3f x2 = this.body2.centerOfMassWorld; + final Quaternion orientationBody1 = this.body1.getTransform().getOrientation(); + final Quaternion orientationBody2 = this.body2.getTransform().getOrientation(); + + // Get the inertia tensor of bodies + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = orientationBody1.multiply(this.localAnchorPointBody1); + this.r2World = orientationBody2.multiply(this.localAnchorPointBody2); + + // Compute the current angle around the hinge axis + final float hingeAngle = computeCurrentHingeAngle(orientationBody1, orientationBody2); + + // Check if the limit raints are violated or not + final float lowerLimitError = hingeAngle - this.lowerLimit; + final float upperLimitError = this.upperLimit - hingeAngle; + final boolean oldIsLowerLimitViolated = this.isLowerLimitViolated; + this.isLowerLimitViolated = lowerLimitError <= 0; + if (this.isLowerLimitViolated != oldIsLowerLimitViolated) { + this.impulseLowerLimit = 0.0f; + } + final boolean oldIsUpperLimitViolated = this.isUpperLimitViolated; + this.isUpperLimitViolated = upperLimitError <= 0; + if (this.isUpperLimitViolated != oldIsUpperLimitViolated) { + this.impulseUpperLimit = 0.0f; + } + + // Compute vectors needed in the Jacobian + this.mA1 = orientationBody1.multiply(this.hingeLocalAxisBody1); + final Vector3f a2 = orientationBody2.multiply(this.hingeLocalAxisBody2); + this.mA1.normalize(); + a2.normalize(); + final Vector3f b2 = a2.getOrthoVector(); + final Vector3f c2 = a2.cross(b2); + this.b2CrossA1 = b2.cross(this.mA1); + this.c2CrossA1 = c2.cross(this.mA1); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the inverse mass matrix K=JM^-1J^t for the 3 translation raints (3x3 matrix) + final float inverseMassBodies = this.body1.massInverse + this.body2.massInverse; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).multiply(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).multiply(skewSymmetricMatrixU2.transposeNew())); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.inverseNew(); + } + + // Compute the bias "b" of the translation raints + this.bTranslation.setZero(); + final float biasFactor = (BETA / raintSolverData.timeStep); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bTranslation = x2.addNew(this.r2World).less(x1).less(this.r1World).multiply(biasFactor); + } + + // Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation raints (2x2 matrix) + final Vector3f I1B2CrossA1 = this.i1.multiplyNew(this.b2CrossA1); + final Vector3f I1C2CrossA1 = this.i1.multiplyNew(this.c2CrossA1); + final Vector3f I2B2CrossA1 = this.i2.multiplyNew(this.b2CrossA1); + final Vector3f I2C2CrossA1 = this.i2.multiplyNew(this.c2CrossA1); + final float el11 = this.b2CrossA1.dot(I1B2CrossA1) + this.b2CrossA1.dot(I2B2CrossA1); + final float el12 = this.b2CrossA1.dot(I1C2CrossA1) + this.b2CrossA1.dot(I2C2CrossA1); + final float el21 = this.c2CrossA1.dot(I1B2CrossA1) + this.c2CrossA1.dot(I2B2CrossA1); + final float el22 = this.c2CrossA1.dot(I1C2CrossA1) + this.c2CrossA1.dot(I2C2CrossA1); + + final Matrix2f matrixKRotation = new Matrix2f(el11, el12, el21, el22); + this.inverseMassMatrixRotation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotation = matrixKRotation.inverseNew(); + } + + // Compute the bias "b" of the rotation raints + this.bRotation.setZero(); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bRotation = new Vector2f(this.mA1.dot(b2) * biasFactor, this.mA1.dot(c2) * biasFactor); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + + // Reset all the accumulated impulses + this.impulseTranslation.setZero(); + this.impulseRotation.setZero(); + this.impulseLowerLimit = 0.0f; + this.impulseUpperLimit = 0.0f; + this.impulseMotor = 0.0f; + } + + // If the motor or limits are enabled + if (this.isMotorEnabled || (this.isLimitEnabled && (this.isLowerLimitViolated || this.isUpperLimitViolated))) { + + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits and motor (1x1 matrix) + this.inverseMassMatrixLimitMotor = this.mA1.dot(this.i1.multiplyNew(this.mA1)) + this.mA1.dot(this.i2.multiplyNew(this.mA1)); + this.inverseMassMatrixLimitMotor = (this.inverseMassMatrixLimitMotor > 0.0f) ? 1.0f / this.inverseMassMatrixLimitMotor : 0.0f; + + if (this.isLimitEnabled) { + + // Compute the bias "b" of the lower limit raint + this.bLowerLimit = 0.0f; + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bLowerLimit = biasFactor * lowerLimitError; + } + + // Compute the bias "b" of the upper limit raint + this.bUpperLimit = 0.0f; + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bUpperLimit = biasFactor * upperLimitError; + } + } + } + + } + + /// Return true if the limits or the joint are enabled + /** + * @return True if the limits of the joint are enabled and false otherwise + */ + public boolean isLimitEnabled() { + return this.isLimitEnabled; + } + + /// Return true if the motor of the joint is enabled + /** + * @return True if the motor of joint is enabled and false otherwise + */ + public boolean isMotorEnabled() { + return this.isMotorEnabled; + } + + private void resetLimits() { + + // Reset the accumulated impulses for the limits + this.impulseLowerLimit = 0.0f; + this.impulseUpperLimit = 0.0f; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + + /// Set the maximum angle limit + /** + * @param upperLimit The maximum limit angle of the joint (in radian) + */ + public void setMaxAngleLimit(final float upperLimit) { + + assert (upperLimit >= 0 && upperLimit <= 2.0 * Constant.PI); + + if (upperLimit != this.upperLimit) { + + this.upperLimit = upperLimit; + + // Reset the limits + resetLimits(); + } + } + + /// Set the maximum motor torque + /** + * @param maxMotorTorque The maximum torque (in Newtons) of the joint motor + */ + public void setMaxMotorTorque(final float maxMotorTorque) { + + if (maxMotorTorque != this.maxMotorTorque) { + + assert (this.maxMotorTorque >= 0.0f); + this.maxMotorTorque = maxMotorTorque; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + } + + /// Set the minimum angle limit + /** + * @param lowerLimit The minimum limit angle of the joint (in radian) + */ + public void setMinAngleLimit(final float lowerLimit) { + + assert (this.lowerLimit <= 0 && this.lowerLimit >= -2.0 * Constant.PI); + + if (lowerLimit != this.lowerLimit) { + + this.lowerLimit = lowerLimit; + + // Reset the limits + resetLimits(); + } + } + + /// Set the motor speed + public void setMotorSpeed(final float motorSpeed) { + + if (motorSpeed != this.motorSpeed) { + + this.motorSpeed = motorSpeed; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + } + + @Override + public void solvePositionConstraint(final ConstraintSolverData raintSolverData) { + // If the error position correction technique is not the non-linear-gauss-seidel, we do + // do not execute this method + if (this.positionCorrectionTechnique != JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL) { + return; + } + + // Get the bodies positions and orientations + final Vector3f x1 = raintSolverData.positions[this.indexBody1]; + final Vector3f x2 = raintSolverData.positions[this.indexBody2]; + final Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + final Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Recompute the inverse inertia tensors + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Compute the vector from body center to the anchor point in world-space + this.r1World = q1.multiply(this.localAnchorPointBody1); + this.r2World = q2.multiply(this.localAnchorPointBody2); + + // Compute the current angle around the hinge axis + final float hingeAngle = computeCurrentHingeAngle(q1, q2); + + // Check if the limit raints are violated or not + final float lowerLimitError = hingeAngle - this.lowerLimit; + final float upperLimitError = this.upperLimit - hingeAngle; + this.isLowerLimitViolated = lowerLimitError <= 0; + this.isUpperLimitViolated = upperLimitError <= 0; + + // Compute vectors needed in the Jacobian + this.mA1 = q1.multiply(this.hingeLocalAxisBody1); + final Vector3f a2 = q2.multiply(this.hingeLocalAxisBody2); + this.mA1.normalize(); + a2.normalize(); + final Vector3f b2 = a2.getOrthoVector(); + final Vector3f c2 = a2.cross(b2); + this.b2CrossA1 = b2.cross(this.mA1); + this.c2CrossA1 = c2.cross(this.mA1); + + // Compute the corresponding skew-symmetric matrices + final Matrix3f skewSymmetricMatrixU1 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r1World); + final Matrix3f skewSymmetricMatrixU2 = Matrix3f.computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // --------------- Translation Constraints --------------- // + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + final float inverseMassBodies = this.body1.massInverse + this.body2.massInverse; + final Matrix3f massMatrix = new Matrix3f(inverseMassBodies, 0, 0, 0, inverseMassBodies, 0, 0, 0, inverseMassBodies); + massMatrix.add(skewSymmetricMatrixU1.multiplyNew(this.i1).multiply(skewSymmetricMatrixU1.transposeNew())); + massMatrix.add(skewSymmetricMatrixU2.multiplyNew(this.i2).multiply(skewSymmetricMatrixU2.transposeNew())); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.inverseNew(); + } + + // Compute position error for the 3 translation raints + final Vector3f errorTranslation = x2.addNew(this.r2World).less(x1).less(this.r1World); + + // Compute the Lagrange multiplier lambda + final Vector3f lambdaTranslation = this.inverseMassMatrixTranslation.multiplyNew(errorTranslation.multiplyNew(-1)); + + // Compute the impulse of body 1 + final Vector3f linearImpulseBody1 = lambdaTranslation.multiplyNew(-1); + Vector3f angularImpulseBody1 = lambdaTranslation.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + final Vector3f v1 = linearImpulseBody1.multiplyNew(inverseMassBody1); + Vector3f w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + x1.add(v1); + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse of body 2 + Vector3f angularImpulseBody2 = lambdaTranslation.cross(this.r2World).multiplyNew(-1); + + // Compute the pseudo velocity of body 2 + final Vector3f v2 = lambdaTranslation.multiplyNew(inverseMassBody2); + Vector3f w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + x2.add(v2); + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + + // --------------- Rotation Constraints --------------- // + + // Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation raints (2x2 matrix) + final Vector3f I1B2CrossA1 = this.i1.multiplyNew(this.b2CrossA1); + final Vector3f I1C2CrossA1 = this.i1.multiplyNew(this.c2CrossA1); + final Vector3f I2B2CrossA1 = this.i2.multiplyNew(this.b2CrossA1); + final Vector3f I2C2CrossA1 = this.i2.multiplyNew(this.c2CrossA1); + final float el11 = this.b2CrossA1.dot(I1B2CrossA1) + this.b2CrossA1.dot(I2B2CrossA1); + final float el12 = this.b2CrossA1.dot(I1C2CrossA1) + this.b2CrossA1.dot(I2C2CrossA1); + final float el21 = this.c2CrossA1.dot(I1B2CrossA1) + this.c2CrossA1.dot(I2B2CrossA1); + final float el22 = this.c2CrossA1.dot(I1C2CrossA1) + this.c2CrossA1.dot(I2C2CrossA1); + + final Matrix2f matrixKRotation = new Matrix2f(el11, el12, el21, el22); + this.inverseMassMatrixRotation.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotation = matrixKRotation.inverseNew(); + } + + // Compute the position error for the 3 rotation raints + final Vector2f errorRotation = new Vector2f(this.mA1.dot(b2), this.mA1.dot(c2)); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + final Vector2f lambdaRotation = this.inverseMassMatrixRotation.multiplyNew(errorRotation.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = this.b2CrossA1.multiplyNew(-1).multiply(lambdaRotation.x).less(this.c2CrossA1.multiplyNew(lambdaRotation.y)); + + // Compute the pseudo velocity of body 1 + w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse of body 2 + angularImpulseBody2 = this.b2CrossA1.multiplyNew(lambdaRotation.x).add(this.c2CrossA1.multiplyNew(lambdaRotation.y)); + + // Compute the pseudo velocity of body 2 + w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + if (this.isLowerLimitViolated || this.isUpperLimitViolated) { + + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits (1x1 matrix) + this.inverseMassMatrixLimitMotor = this.mA1.dot(this.i1.multiplyNew(this.mA1)) + this.mA1.dot(this.i2.multiplyNew(this.mA1)); + this.inverseMassMatrixLimitMotor = (this.inverseMassMatrixLimitMotor > 0.0f) ? 1.0f / this.inverseMassMatrixLimitMotor : 0.0f; + } + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute the Lagrange multiplier lambda for the lower limit raint + final float lambdaLowerLimit = this.inverseMassMatrixLimitMotor * (-lowerLimitError); + + // Compute the impulse P=J^T * lambda of body 1 + final Vector3f angularImpulseBody1_tmp = this.mA1.multiplyNew(lambdaLowerLimit); + + // Compute the pseudo velocity of body 1 + final Vector3f w1_tmp = this.i1.multiplyNew(angularImpulseBody1_tmp); + + // Update the body position/orientation of body 1 + q1.add((new Quaternion(0, w1_tmp)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda of body 2 + final Vector3f angularImpulseBody2_tmp = this.mA1.multiplyNew(lambdaLowerLimit); + + // Compute the pseudo velocity of body 2 + final Vector3f w2_tmp = this.i2.multiplyNew(angularImpulseBody2_tmp); + + // Update the body position/orientation of body 2 + q2.add((new Quaternion(0, w2_tmp)).multiply(q2).multiply(0.5f)); + q2.normalize(); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute the Lagrange multiplier lambda for the upper limit raint + final float lambdaUpperLimit = this.inverseMassMatrixLimitMotor * (-upperLimitError); + + // Compute the impulse P=J^T * lambda of body 1 + final Vector3f angularImpulseBody1_tmp = this.mA1.multiplyNew(lambdaUpperLimit); + + // Compute the pseudo velocity of body 1 + final Vector3f w1_tmp = this.i1.multiplyNew(angularImpulseBody1_tmp); + + // Update the body position/orientation of body 1 + q1.add((new Quaternion(0, w1_tmp)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda of body 2 + final Vector3f angularImpulseBody2_tmp = this.mA1.multiplyNew(-lambdaUpperLimit); + + // Compute the pseudo velocity of body 2 + final Vector3f w2_tmp = this.i2.multiplyNew(angularImpulseBody2_tmp); + + // Update the body position/orientation of body 2 + q2.add((new Quaternion(0, w2_tmp)).multiply(q2).multiply(0.5f)); + q2.normalize(); + } + } + } + + @Override + public void solveVelocityConstraint(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v + final Vector3f JvTranslation = v2.addNew(w2.cross(this.r2World)).less(v1).less(w1.cross(this.r1World)); + + // Compute the Lagrange multiplier lambda + final Vector3f deltaLambdaTranslation = this.inverseMassMatrixTranslation.multiplyNew(JvTranslation.multiplyNew(-1).less(this.bTranslation)); + this.impulseTranslation.add(deltaLambdaTranslation); + + // Compute the impulse P=J^T * lambda of body 1 + final Vector3f linearImpulseBody1 = deltaLambdaTranslation.multiplyNew(-1); + Vector3f angularImpulseBody1 = deltaLambdaTranslation.cross(this.r1World); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda of body 2 + Vector3f angularImpulseBody2 = deltaLambdaTranslation.cross(this.r2World).multiplyNew(-1); + + // Apply the impulse to the body 2 + v2.add(deltaLambdaTranslation.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 2 rotation raints + final Vector2f JvRotation = new Vector2f(-this.b2CrossA1.dot(w1) + this.b2CrossA1.dot(w2), -this.c2CrossA1.dot(w1) + this.c2CrossA1.dot(w2)); + + // Compute the Lagrange multiplier lambda for the 2 rotation raints + final Vector2f deltaLambdaRotation = this.inverseMassMatrixRotation.multiplyNew(JvRotation.multiplyNew(-1).less(this.bRotation)); + this.impulseRotation.add(deltaLambdaRotation); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 1 + angularImpulseBody1 = this.b2CrossA1.multiplyNew(-1).multiply(deltaLambdaRotation.x).less(this.c2CrossA1.multiplyNew(deltaLambdaRotation.y)); + + // Apply the impulse to the body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 2 + angularImpulseBody2 = this.b2CrossA1.multiplyNew(deltaLambdaRotation.x).add(this.c2CrossA1.multiplyNew(deltaLambdaRotation.y)); + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute J*v for the lower limit raint + final float JvLowerLimit = w2.lessNew(w1).dot(this.mA1); + + // Compute the Lagrange multiplier lambda for the lower limit raint + float deltaLambdaLower = this.inverseMassMatrixLimitMotor * (-JvLowerLimit - this.bLowerLimit); + final float lambdaTemp = this.impulseLowerLimit; + this.impulseLowerLimit = FMath.max(this.impulseLowerLimit + deltaLambdaLower, 0.0f); + deltaLambdaLower = this.impulseLowerLimit - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 1 + final Vector3f angularImpulseBody1_tmp = this.mA1.multiplyNew(-deltaLambdaLower); + + // Apply the impulse to the body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1_tmp)); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + final Vector3f angularImpulseBody2_tmp = this.mA1.multiplyNew(deltaLambdaLower); + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(angularImpulseBody2_tmp)); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute J*v for the upper limit raint + final float JvUpperLimit = w2.lessNew(w1).multiplyNew(-1).dot(this.mA1); + + // Compute the Lagrange multiplier lambda for the upper limit raint + float deltaLambdaUpper = this.inverseMassMatrixLimitMotor * (-JvUpperLimit - this.bUpperLimit); + final float lambdaTemp = this.impulseUpperLimit; + this.impulseUpperLimit = FMath.max(this.impulseUpperLimit + deltaLambdaUpper, 0.0f); + deltaLambdaUpper = this.impulseUpperLimit - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 1 + final Vector3f angularImpulseBody1_tmp = this.mA1.multiplyNew(deltaLambdaUpper); + + // Apply the impulse to the body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1_tmp)); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + final Vector3f angularImpulseBody2_tmp = this.mA1.multiplyNew(-deltaLambdaUpper); + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(angularImpulseBody2_tmp)); + } + } + + // --------------- Motor --------------- // + + // If the motor is enabled + if (this.isMotorEnabled) { + + // Compute J*v for the motor + final float JvMotor = this.mA1.dot(w1.lessNew(w2)); + + // Compute the Lagrange multiplier lambda for the motor + final float maxMotorImpulse = this.maxMotorTorque * raintSolverData.timeStep; + float deltaLambdaMotor = this.inverseMassMatrixLimitMotor * (-JvMotor - this.motorSpeed); + final float lambdaTemp = this.impulseMotor; + this.impulseMotor = FMath.clamp(this.impulseMotor + deltaLambdaMotor, -maxMotorImpulse, maxMotorImpulse); + deltaLambdaMotor = this.impulseMotor - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the motor of body 1 + final Vector3f angularImpulseBody1_tmp = this.mA1.multiplyNew(-deltaLambdaMotor); + + // Apply the impulse to the body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1_tmp)); + + // Compute the impulse P=J^T * lambda for the motor of body 2 + final Vector3f angularImpulseBody2_tmp = this.mA1.multiplyNew(deltaLambdaMotor); + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(angularImpulseBody2_tmp)); + } + + } + + @Override + public void warmstart(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Compute the impulse P=J^T * lambda for the 2 rotation raints + final Vector3f rotationImpulse = this.b2CrossA1.multiplyNew(-1).multiply(this.impulseRotation.x).less(this.c2CrossA1.multiplyNew(this.impulseRotation.y)); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints + final Vector3f limitsImpulse = this.mA1.multiplyNew(this.impulseUpperLimit - this.impulseLowerLimit); + + // Compute the impulse P=J^T * lambda for the motor raint + final Vector3f motorImpulse = this.mA1.multiplyNew(-this.impulseMotor); + + // Compute the impulse P=J^T * lambda for the 3 translation raints of body 1 + final Vector3f linearImpulseBody1 = this.impulseTranslation.multiplyNew(-1); + final Vector3f angularImpulseBody1 = this.impulseTranslation.cross(this.r1World); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 1 + angularImpulseBody1.add(rotationImpulse); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + angularImpulseBody1.add(limitsImpulse); + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + angularImpulseBody1.add(motorImpulse); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 3 translation raints of body 2 + final Vector3f angularImpulseBody2 = this.impulseTranslation.cross(this.r2World).multiplyNew(-1); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 2 + angularImpulseBody2.add(rotationImpulse.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 2 + angularImpulseBody2.add(limitsImpulse.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the motor raint of body 2 + angularImpulseBody2.add(motorImpulse.multiplyNew(-1)); + + // Apply the impulse to the body 2 + v2.add(this.impulseTranslation.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + } + +} diff --git a/src/org/atriasoft/ephysics/constraint/HingeJointInfo.java b/src/org/atriasoft/ephysics/constraint/HingeJointInfo.java new file mode 100644 index 0000000..28bba00 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/HingeJointInfo.java @@ -0,0 +1,89 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * It is used to gather the information needed to create a hinge joint. + * This structure will be used to create the actual hinge joint. + */ +public class HingeJointInfo extends JointInfo { + + public Vector3f anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + public Vector3f rotationAxisWorld; //!< Hinge rotation axis (in world-space coordinates) + public boolean isLimitEnabled; //!< True if the hinge joint limits are enabled + public boolean isMotorEnabled; //!< True if the hinge joint motor is enabled + public float minAngleLimit; //!< Minimum allowed rotation angle (in radian) if limits are enabled. The angle must be in the range [-2*pi, 0] + public float maxAngleLimit; //!< Maximum allowed rotation angle (in radian) if limits are enabled. The angle must be in the range [0, 2*pi] + public float motorSpeed; //!< Motor speed (in radian/second) + public float maxMotorTorque; //!< Maximum motor torque (in Newtons * meters) that can be applied to reach to desired motor speed + + /** + * Constructor without limits and without motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space coordinates + * @param _initRotationAxisWorld The initial rotation axis in world-space coordinates + */ + public HingeJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initRotationAxisWorld) { + super(_rigidBody1, _rigidBody2, JointType.HINGEJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.rotationAxisWorld = _initRotationAxisWorld; + this.isLimitEnabled = false; + this.isMotorEnabled = false; + this.minAngleLimit = -1; + this.maxAngleLimit = 1; + this.motorSpeed = 0; + this.maxMotorTorque = 0; + + } + + /** + * Constructor with limits but without motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space coordinates + * @param _initRotationAxisWorld The intial rotation axis in world-space coordinates + * @param _initMinAngleLimit The initial minimum limit angle (in radian) + * @param _initMaxAngleLimit The initial maximum limit angle (in radian) + */ + public HingeJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initRotationAxisWorld, final float _initMinAngleLimit, + final float _initMaxAngleLimit) { + super(_rigidBody1, _rigidBody2, JointType.HINGEJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.rotationAxisWorld = _initRotationAxisWorld; + this.isLimitEnabled = true; + this.isMotorEnabled = false; + this.minAngleLimit = _initMinAngleLimit; + this.maxAngleLimit = _initMaxAngleLimit; + this.motorSpeed = 0; + this.maxMotorTorque = 0; + + } + + /** + * Constructor with limits and motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space + * @param _initRotationAxisWorld The initial rotation axis in world-space + * @param _initMinAngleLimit The initial minimum limit angle (in radian) + * @param _initMaxAngleLimit The initial maximum limit angle (in radian) + * @param _initMotorSpeed The initial motor speed of the joint (in radian per second) + * @param _initMaxMotorTorque The initial maximum motor torque (in Newtons) + */ + public HingeJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initRotationAxisWorld, final float _initMinAngleLimit, + final float _initMaxAngleLimit, final float _initMotorSpeed, final float _initMaxMotorTorque) { + super(_rigidBody1, _rigidBody2, JointType.HINGEJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.rotationAxisWorld = _initRotationAxisWorld; + this.isLimitEnabled = true; + this.isMotorEnabled = false; + this.minAngleLimit = _initMinAngleLimit; + this.maxAngleLimit = _initMaxAngleLimit; + this.motorSpeed = _initMotorSpeed; + this.maxMotorTorque = _initMaxMotorTorque; + + } +}; diff --git a/src/org/atriasoft/ephysics/constraint/Joint.java b/src/org/atriasoft/ephysics/constraint/Joint.java new file mode 100644 index 0000000..2d305b2 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/Joint.java @@ -0,0 +1,80 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.ephysics.body.RigidBody; +import org.atriasoft.ephysics.engine.ConstraintSolverData; + +/** + * It represents a joint between two bodies. + */ +public abstract class Joint { + + protected RigidBody body1; //!< Pointer to the first body of the joint + protected RigidBody body2; //!< Pointer to the second body of the joint + protected JointType type; //!< Type of the joint + protected int indexBody1; //!< Body 1 index in the velocity array to solve the raint + protected int indexBody2; //!< Body 2 index in the velocity array to solve the raint + protected JointsPositionCorrectionTechnique positionCorrectionTechnique; //!< Position correction technique used for the raint (used for joints) + protected boolean isCollisionEnabled; //!< True if the two bodies of the raint are allowed to collide with each other + public boolean isAlreadyInIsland; //!< True if the joint has already been added into an island + + /// Constructor + public Joint(final JointInfo jointInfo) { + this.body1 = jointInfo.body1; + this.body2 = jointInfo.body2; + this.type = jointInfo.type; + this.positionCorrectionTechnique = jointInfo.positionCorrectionTechnique; + this.isCollisionEnabled = jointInfo.isCollisionEnabled; + this.isAlreadyInIsland = false; + assert (this.body1 != null); + assert (this.body2 != null); + } + + /// Return the reference to the body 1 + public RigidBody getBody1() { + return this.body1; + } + + /// Return the reference to the body 2 + public RigidBody getBody2() { + return this.body2; + } + + /** Return the type of the raint + * @return The type of the joint + */ + public JointType getType() { + return this.type; + } + + /// Initialize before solving the joint + public abstract void initBeforeSolve(final ConstraintSolverData raintSolverData); + + /** Return true if the joint is active + * @return True if the joint is active + */ + public boolean isActive() { + return (this.body1.isActive() && this.body2.isActive()); + } + + /// Return true if the joint has already been added into an island + public boolean isAlreadyInIsland() { + return this.isAlreadyInIsland; + } + + /**Return true if the collision between the two bodies of the joint is enabled + * @return True if the collision is enabled between the two bodies of the joint + * is enabled and false otherwise + */ + public boolean isCollisionEnabled() { + return this.isCollisionEnabled; + } + + /// Solve the position raint + public abstract void solvePositionConstraint(final ConstraintSolverData raintSolverData); + + /// Solve the velocity raint + public abstract void solveVelocityConstraint(final ConstraintSolverData raintSolverData); + + /// Warm start the joint (apply the previous impulse at the beginning of the step) + public abstract void warmstart(final ConstraintSolverData raintSolverData); +} diff --git a/src/org/atriasoft/ephysics/constraint/JointInfo.java b/src/org/atriasoft/ephysics/constraint/JointInfo.java new file mode 100644 index 0000000..501284a --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/JointInfo.java @@ -0,0 +1,37 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * It is used to gather the information needed to create a joint. + */ +public class JointInfo { + //!< First rigid body of the joint + public final RigidBody body1; + //!< Second rigid body of the joint + public final RigidBody body2; + //!< Type of the joint + public final JointType type; + //!< Position correction technique used for the raint (used for joints). By default, the BAUMGARTE technique is used + public final JointsPositionCorrectionTechnique positionCorrectionTechnique; + //!< True if the two bodies of the joint are allowed to collide with each other + public final boolean isCollisionEnabled; + + /// Constructor + JointInfo(final JointType _raintType) { + this.body1 = null; + this.body2 = null; + this.type = _raintType; + this.positionCorrectionTechnique = JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL; + this.isCollisionEnabled = true; + } + + /// Constructor + JointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final JointType _raintType) { + this.body1 = _rigidBody1; + this.body2 = _rigidBody2; + this.type = _raintType; + this.positionCorrectionTechnique = JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL; + this.isCollisionEnabled = true; + } +} diff --git a/src/org/atriasoft/ephysics/constraint/JointListElement.java b/src/org/atriasoft/ephysics/constraint/JointListElement.java new file mode 100644 index 0000000..d8289d8 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/JointListElement.java @@ -0,0 +1,16 @@ +package org.atriasoft.ephysics.constraint; + +public class JointListElement { + + public Joint joint; //!< Pointer to the actual joint + public JointListElement next; //!< Next element of the list + + /** + * Constructor + */ + public JointListElement(final Joint _initJoint, final JointListElement _initNext) { + this.joint = _initJoint; + this.next = _initNext; + + } +} diff --git a/src/org/atriasoft/ephysics/constraint/JointType.java b/src/org/atriasoft/ephysics/constraint/JointType.java new file mode 100644 index 0000000..c63b273 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/JointType.java @@ -0,0 +1,9 @@ +package org.atriasoft.ephysics.constraint; + +/// Enumeration for the type of a raint +public enum JointType { + BALLSOCKETJOINT, + SLIDERJOINT, + HINGEJOINT, + FIXEDJOINT +} diff --git a/src/org/atriasoft/ephysics/constraint/JointsPositionCorrectionTechnique.java b/src/org/atriasoft/ephysics/constraint/JointsPositionCorrectionTechnique.java new file mode 100644 index 0000000..bad9f47 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/JointsPositionCorrectionTechnique.java @@ -0,0 +1,9 @@ +package org.atriasoft.ephysics.constraint; + +/// Position correction technique used in the raint solver (for joints). +/// BAUMGARTE_JOINTS : Faster but can be innacurate in some situations. +/// NON_LINEAR_GAUSS_SEIDEL : Slower but more precise. This is the option used by default. +public enum JointsPositionCorrectionTechnique { + BAUMGARTE_JOINTS, + NON_LINEAR_GAUSS_SEIDEL +} diff --git a/src/org/atriasoft/ephysics/constraint/SliderJoint.java b/src/org/atriasoft/ephysics/constraint/SliderJoint.java new file mode 100644 index 0000000..6e19529 --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/SliderJoint.java @@ -0,0 +1,853 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector2f; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.engine.ConstraintSolverData; +import org.atriasoft.ephysics.mathematics.Matrix2f; + +public class SliderJoint extends Joint { + private static final float BETA = 0.2f; //!< Beta value for the position correction bias factor + private final Vector3f localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + private final Vector3f localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + private final Vector3f sliderAxisBody1; //!< Slider axis (in local-space coordinates of body 1) + private Matrix3f i1; //!< Inertia tensor of body 1 (in world-space coordinates) + private Matrix3f i2; //!< Inertia tensor of body 2 (in world-space coordinates) + private final Quaternion initOrientationDifferenceInv; //!< Inverse of the initial orientation difference between the two bodies + private Vector3f N1; //!< First vector orthogonal to the slider axis local-space of body 1 + private Vector3f N2; //!< Second vector orthogonal to the slider axis and N1 in local-space of body 1 + private Vector3f R1; //!< Vector r1 in world-space coordinates + private Vector3f R2; //!< Vector r2 in world-space coordinates + private Vector3f R2CrossN1; //!< Cross product of r2 and n1 + private Vector3f R2CrossN2; //!< Cross product of r2 and n2 + private Vector3f R2CrossSliderAxis; //!< Cross product of r2 and the slider axis + private Vector3f R1PlusUCrossN1; //!< Cross product of vector (r1 + u) and n1 + private Vector3f R1PlusUCrossN2; //!< Cross product of vector (r1 + u) and n2 + private Vector3f R1PlusUCrossSliderAxis; //!< Cross product of vector (r1 + u) and the slider axis + private Vector2f bTranslation; //!< Bias of the 2 translation raints + private Vector3f bRotation; //!< Bias of the 3 rotation raints + private float bLowerLimit; //!< Bias of the lower limit raint + private float bUpperLimit; //!< Bias of the upper limit raint + private Matrix2f inverseMassMatrixTranslationConstraint; //!< Inverse of mass matrix K=JM^-1J^t for the translation raint (2x2 matrix) + private Matrix3f inverseMassMatrixRotationConstraint; //!< Inverse of mass matrix K=JM^-1J^t for the rotation raint (3x3 matrix) + private float inverseMassMatrixLimit; //!< Inverse of mass matrix K=JM^-1J^t for the upper and lower limit raints (1x1 matrix) + private float inverseMassMatrixMotor; //!< Inverse of mass matrix K=JM^-1J^t for the motor + private final Vector2f impulseTranslation; //!< Accumulated impulse for the 2 translation raints + private final Vector3f impulseRotation; //!< Accumulated impulse for the 3 rotation raints + private float impulseLowerLimit; //!< Accumulated impulse for the lower limit raint + private float impulseUpperLimit; //!< Accumulated impulse for the upper limit raint + private float impulseMotor; //!< Accumulated impulse for the motor + private boolean isLimitEnabled; //!< True if the slider limits are enabled + private boolean isMotorEnabled; //!< True if the motor of the joint in enabled + private Vector3f sliderAxisWorld; //!< Slider axis in world-space coordinates + private float lowerLimit; //!< Lower limit (minimum translation distance) + private float upperLimit; //!< Upper limit (maximum translation distance) + private boolean isLowerLimitViolated; //!< True if the lower limit is violated + private boolean isUpperLimitViolated; //!< True if the upper limit is violated + private float motorSpeed; //!< Motor speed (in m/s) + private float maxMotorForce; //!< Maximum motor force (in Newtons) that can be applied to reach to desired motor speed + /// Constructor + + public SliderJoint(final SliderJointInfo jointInfo) { + super(jointInfo); + this.impulseTranslation = new Vector2f(0, 0); + this.impulseRotation = new Vector3f(0, 0, 0); + this.impulseLowerLimit = 0; + this.impulseUpperLimit = 0; + this.impulseMotor = 0; + this.isLimitEnabled = jointInfo.isLimitEnabled; + this.isMotorEnabled = jointInfo.isMotorEnabled; + this.lowerLimit = jointInfo.minTranslationLimit; + this.upperLimit = jointInfo.maxTranslationLimit; + this.isLowerLimitViolated = false; + this.isUpperLimitViolated = false; + this.motorSpeed = jointInfo.motorSpeed; + this.maxMotorForce = jointInfo.maxMotorForce; + + assert (this.upperLimit >= 0.0f); + assert (this.lowerLimit <= 0.0f); + assert (this.maxMotorForce >= 0.0f); + + // Compute the local-space anchor point for each body + final Transform3D transform1 = this.body1.getTransform(); + final Transform3D transform2 = this.body2.getTransform(); + this.localAnchorPointBody1 = transform1.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + this.localAnchorPointBody2 = transform2.inverseNew().multiply(jointInfo.anchorPointWorldSpace); + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation().multiplyNew(transform1.getOrientation().inverseNew()); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); + + // Compute the slider axis in local-space of body 1 + this.sliderAxisBody1 = this.body1.getTransform().getOrientation().inverseNew().multiply(jointInfo.sliderAxisWorldSpace); + this.sliderAxisBody1.normalize(); + } + + /// Enable/Disable the limits of the joint + /** + * @param isLimitEnabled True if you want to enable the joint limits and false + * otherwise + */ + public void enableLimit(final boolean isLimitEnabled) { + + if (isLimitEnabled != this.isLimitEnabled) { + + this.isLimitEnabled = isLimitEnabled; + + // Reset the limits + resetLimits(); + } + } + + /// Enable/Disable the motor of the joint + /** + * @param isMotorEnabled True if you want to enable the joint motor and false + * otherwise + */ + public void enableMotor(final boolean isMotorEnabled) { + + this.isMotorEnabled = isMotorEnabled; + this.impulseMotor = 0.0f; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + + /// Return the maximum motor force + /** + * @return The maximum force of the joint motor (in Newton x meters) + */ + public float getMaxMotorForce() { + return this.maxMotorForce; + } + + /// Return the maximum translation limit + /** + * @return The maximum translation limit of the joint (in meters) + */ + public float getMaxTranslationLimit() { + return this.upperLimit; + } + + /// Return the minimum translation limit + /** + * @return The minimum translation limit of the joint (in meters) + */ + public float getMinTranslationLimit() { + return this.lowerLimit; + } + + /// Return the intensity of the current force applied for the joint motor + /** + * @param timeStep Time step (in seconds) + * @return The current force of the joint motor (in Newton x meters) + */ + public float getMotorForce(final float timeStep) { + return this.impulseMotor / timeStep; + } + + /// Return the motor speed + /** + * @return The current motor speed of the joint (in meters per second) + */ + public float getMotorSpeed() { + return this.motorSpeed; + } + + /// Return the current translation value of the joint + /** + * @return The current translation distance of the joint (in meters) + */ + public float getTranslation() { + + // TODO : Check if we need to compare rigid body position or center of mass here + + // Get the bodies positions and orientations + final Vector3f x1 = this.body1.getTransform().getPosition(); + final Vector3f x2 = this.body2.getTransform().getPosition(); + final Quaternion q1 = this.body1.getTransform().getOrientation(); + final Quaternion q2 = this.body2.getTransform().getOrientation(); + + // Compute the two anchor points in world-space coordinates + final Vector3f anchorBody1 = x1.addNew(q1.multiply(this.localAnchorPointBody1)); + final Vector3f anchorBody2 = x2.addNew(q2.multiply(this.localAnchorPointBody2)); + + // Compute the vector u (difference between anchor points) + final Vector3f u = anchorBody2.lessNew(anchorBody1); + + // Compute the slider axis in world-space + final Vector3f sliderAxisWorld = q1.multiply(this.sliderAxisBody1); + sliderAxisWorld.normalize(); + + // Compute and return the translation value + return u.dot(sliderAxisWorld); + } + + @Override + public void initBeforeSolve(final ConstraintSolverData raintSolverData) { + { + + // Initialize the bodies index in the veloc ity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body1); + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.get(this.body2); + + // Get the bodies positions and orientations + final Vector3f x1 = this.body1.centerOfMassWorld; + final Vector3f x2 = this.body2.centerOfMassWorld; + final Quaternion orientationBody1 = this.body1.getTransform().getOrientation(); + final Quaternion orientationBody2 = this.body2.getTransform().getOrientation(); + + // Get the inertia tensor of bodies + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Vector from body center to the anchor point + this.R1 = orientationBody1.multiply(this.localAnchorPointBody1); + this.R2 = orientationBody2.multiply(this.localAnchorPointBody2); + + // Compute the vector u (difference between anchor points) + final Vector3f u = x2.addNew(this.R2).less(x1).less(this.R1); + + // Compute the two orthogonal vectors to the slider axis in world-space + this.sliderAxisWorld = orientationBody1.multiply(this.sliderAxisBody1); + this.sliderAxisWorld.normalize(); + this.N1 = this.sliderAxisWorld.getOrthoVector(); + this.N2 = this.sliderAxisWorld.cross(this.N1); + + // Check if the limit raints are violated or not + final float uDotSliderAxis = u.dot(this.sliderAxisWorld); + final float lowerLimitError = uDotSliderAxis - this.lowerLimit; + final float upperLimitError = this.upperLimit - uDotSliderAxis; + final boolean oldIsLowerLimitViolated = this.isLowerLimitViolated; + this.isLowerLimitViolated = lowerLimitError <= 0; + if (this.isLowerLimitViolated != oldIsLowerLimitViolated) { + this.impulseLowerLimit = 0.0f; + } + final boolean oldIsUpperLimitViolated = this.isUpperLimitViolated; + this.isUpperLimitViolated = upperLimitError <= 0; + if (this.isUpperLimitViolated != oldIsUpperLimitViolated) { + this.impulseUpperLimit = 0.0f; + } + + // Compute the cross products used in the Jacobians + this.R2CrossN1 = this.R2.cross(this.N1); + this.R2CrossN2 = this.R2.cross(this.N2); + this.R2CrossSliderAxis = this.R2.cross(this.sliderAxisWorld); + final Vector3f r1PlusU = this.R1.addNew(u); + this.R1PlusUCrossN1 = (r1PlusU).cross(this.N1); + this.R1PlusUCrossN2 = (r1PlusU).cross(this.N2); + this.R1PlusUCrossSliderAxis = (r1PlusU).cross(this.sliderAxisWorld); + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 2 translation + // raints (2x2 matrix) + final float sumInverseMass = this.body1.massInverse + this.body2.massInverse; + final Vector3f I1R1PlusUCrossN1 = this.i1.multiplyNew(this.R1PlusUCrossN1); + final Vector3f I1R1PlusUCrossN2 = this.i1.multiplyNew(this.R1PlusUCrossN2); + final Vector3f I2R2CrossN1 = this.i2.multiplyNew(this.R2CrossN1); + final Vector3f I2R2CrossN2 = this.i2.multiplyNew(this.R2CrossN2); + final float el11 = sumInverseMass + this.R1PlusUCrossN1.dot(I1R1PlusUCrossN1) + this.R2CrossN1.dot(I2R2CrossN1); + final float el12 = this.R1PlusUCrossN1.dot(I1R1PlusUCrossN2) + this.R2CrossN1.dot(I2R2CrossN2); + final float el21 = this.R1PlusUCrossN2.dot(I1R1PlusUCrossN1) + this.R2CrossN2.dot(I2R2CrossN1); + final float el22 = sumInverseMass + this.R1PlusUCrossN2.dot(I1R1PlusUCrossN2) + this.R2CrossN2.dot(I2R2CrossN2); + + final Matrix2f matrixKTranslation = new Matrix2f(el11, el12, el21, el22); + this.inverseMassMatrixTranslationConstraint.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslationConstraint = matrixKTranslation.inverseNew(); + } + + // Compute the bias "b" of the translation raint + this.bTranslation.setZero(); + final float biasFactor = (BETA / raintSolverData.timeStep); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bTranslation.setX(u.dot(this.N1)); + this.bTranslation.setY(u.dot(this.N2)); + this.bTranslation.multiply(biasFactor); + } + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotationConstraint = this.i1.addNew(this.i2); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotationConstraint = this.inverseMassMatrixRotationConstraint.inverseNew(); + } + + // Compute the bias "b" of the rotation raint + this.bRotation.setZero(); + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + final Quaternion currentOrientationDifference = orientationBody2.multiplyNew(orientationBody1.inverseNew()); + currentOrientationDifference.normalize(); + final Quaternion qError = currentOrientationDifference.multiplyNew(this.initOrientationDifferenceInv); + this.bRotation = qError.getVectorV().multiplyNew(biasFactor * 2.0f); + } + // If the limits are enabled + if (this.isLimitEnabled && (this.isLowerLimitViolated || this.isUpperLimitViolated)) { + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits (1x1 matrix) + this.inverseMassMatrixLimit = this.body1.massInverse + this.body2.massInverse + this.R1PlusUCrossSliderAxis.dot(this.i1.multiplyNew(this.R1PlusUCrossSliderAxis)) + + this.R2CrossSliderAxis.dot(this.i2.multiplyNew(this.R2CrossSliderAxis)); + this.inverseMassMatrixLimit = (this.inverseMassMatrixLimit > 0.0f) ? 1.0f / this.inverseMassMatrixLimit : 0.0f; + // Compute the bias "b" of the lower limit raint + this.bLowerLimit = 0.0f; + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bLowerLimit = biasFactor * lowerLimitError; + } + // Compute the bias "b" of the upper limit raint + this.bUpperLimit = 0.0f; + if (this.positionCorrectionTechnique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.bUpperLimit = biasFactor * upperLimitError; + } + } + + // If the motor is enabled + if (this.isMotorEnabled) { + // Compute the inverse of mass matrix K=JM^-1J^t for the motor (1x1 matrix) + this.inverseMassMatrixMotor = this.body1.massInverse + this.body2.massInverse; + this.inverseMassMatrixMotor = (this.inverseMassMatrixMotor > 0.0f) ? 1.0f / this.inverseMassMatrixMotor : 0.0f; + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + // Reset all the accumulated impulses + this.impulseTranslation.setZero(); + this.impulseRotation.setZero(); + this.impulseLowerLimit = 0.0f; + this.impulseUpperLimit = 0.0f; + this.impulseMotor = 0.0f; + } + } + } + + /// Return true if the limits or the joint are enabled + /** + * @return True if the joint limits are enabled + */ + public boolean isLimitEnabled() { + return this.isLimitEnabled; + } + + /// Return true if the motor of the joint is enabled + /** + * @return True if the joint motor is enabled + */ + public boolean isMotorEnabled() { + return this.isMotorEnabled; + } + + /// Reset the limits + private void resetLimits() { + + // Reset the accumulated impulses for the limits + this.impulseLowerLimit = 0.0f; + this.impulseUpperLimit = 0.0f; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + + /// Set the maximum motor force + /** + * @param maxMotorForce The maximum force of the joint motor (in Newton x meters) + */ + public void setMaxMotorForce(final float maxMotorForce) { + + if (maxMotorForce != this.maxMotorForce) { + + assert (this.maxMotorForce >= 0.0f); + this.maxMotorForce = maxMotorForce; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + } + + /// Set the maximum translation limit + /** + * @param lowerLimit The maximum translation limit of the joint (in meters) + */ + public void setMaxTranslationLimit(final float upperLimit) { + + assert (this.lowerLimit <= upperLimit); + + if (upperLimit != this.upperLimit) { + + this.upperLimit = upperLimit; + + // Reset the limits + resetLimits(); + } + } + + /// Set the minimum translation limit + /** + * @param lowerLimit The minimum translation limit of the joint (in meters) + */ + public void setMinTranslationLimit(final float lowerLimit) { + + assert (lowerLimit <= this.upperLimit); + + if (lowerLimit != this.lowerLimit) { + + this.lowerLimit = lowerLimit; + + // Reset the limits + resetLimits(); + } + } + + /// Set the motor speed + /** + * @param motorSpeed The speed of the joint motor (in meters per second) + */ + public void setMotorSpeed(final float motorSpeed) { + + if (motorSpeed != this.motorSpeed) { + + this.motorSpeed = motorSpeed; + + // Wake up the two bodies of the joint + this.body1.setIsSleeping(false); + this.body2.setIsSleeping(false); + } + } + + @Override + public void solvePositionConstraint(final ConstraintSolverData raintSolverData) { + + // If the error position correction technique is not the non-linear-gauss-seidel, we do + // do not execute this method + if (this.positionCorrectionTechnique != JointsPositionCorrectionTechnique.NON_LINEAR_GAUSS_SEIDEL) { + return; + } + + // Get the bodies positions and orientations + final Vector3f x1 = raintSolverData.positions[this.indexBody1]; + final Vector3f x2 = raintSolverData.positions[this.indexBody2]; + final Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + final Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Recompute the inertia tensor of bodies + this.i1 = this.body1.getInertiaTensorInverseWorld(); + this.i2 = this.body2.getInertiaTensorInverseWorld(); + + // Vector from body center to the anchor point + this.R1 = q1.multiply(this.localAnchorPointBody1); + this.R2 = q2.multiply(this.localAnchorPointBody2); + + // Compute the vector u (difference between anchor points) + final Vector3f u = x2.addNew(this.R2).less(x1).less(this.R1); + + // Compute the two orthogonal vectors to the slider axis in world-space + this.sliderAxisWorld = q1.multiply(this.sliderAxisBody1); + this.sliderAxisWorld.normalize(); + this.N1 = this.sliderAxisWorld.getOrthoVector(); + this.N2 = this.sliderAxisWorld.cross(this.N1); + + // Check if the limit raints are violated or not + final float uDotSliderAxis = u.dot(this.sliderAxisWorld); + final float lowerLimitError = uDotSliderAxis - this.lowerLimit; + final float upperLimitError = this.upperLimit - uDotSliderAxis; + this.isLowerLimitViolated = lowerLimitError <= 0; + this.isUpperLimitViolated = upperLimitError <= 0; + + // Compute the cross products used in the Jacobians + this.R2CrossN1 = this.R2.cross(this.N1); + this.R2CrossN2 = this.R2.cross(this.N2); + this.R2CrossSliderAxis = this.R2.cross(this.sliderAxisWorld); + final Vector3f r1PlusU = this.R1.addNew(u); + this.R1PlusUCrossN1 = (r1PlusU).cross(this.N1); + this.R1PlusUCrossN2 = (r1PlusU).cross(this.N2); + this.R1PlusUCrossSliderAxis = (r1PlusU).cross(this.sliderAxisWorld); + + // --------------- Translation Constraints --------------- // + + // Recompute the inverse of the mass matrix K=JM^-1J^t for the 2 translation + // raints (2x2 matrix) + final float sumInverseMass = this.body1.massInverse + this.body2.massInverse; + final Vector3f I1R1PlusUCrossN1 = this.i1.multiplyNew(this.R1PlusUCrossN1); + final Vector3f I1R1PlusUCrossN2 = this.i1.multiplyNew(this.R1PlusUCrossN2); + final Vector3f I2R2CrossN1 = this.i2.multiplyNew(this.R2CrossN1); + final Vector3f I2R2CrossN2 = this.i2.multiplyNew(this.R2CrossN2); + final float el11 = sumInverseMass + this.R1PlusUCrossN1.dot(I1R1PlusUCrossN1) + this.R2CrossN1.dot(I2R2CrossN1); + final float el12 = this.R1PlusUCrossN1.dot(I1R1PlusUCrossN2) + this.R2CrossN1.dot(I2R2CrossN2); + final float el21 = this.R1PlusUCrossN2.dot(I1R1PlusUCrossN1) + this.R2CrossN2.dot(I2R2CrossN1); + final float el22 = sumInverseMass + this.R1PlusUCrossN2.dot(I1R1PlusUCrossN2) + this.R2CrossN2.dot(I2R2CrossN2); + + final Matrix2f matrixKTranslation = new Matrix2f(el11, el12, el21, el22); + this.inverseMassMatrixTranslationConstraint.setZero(); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixTranslationConstraint = matrixKTranslation.inverseNew(); + } + + // Compute the position error for the 2 translation raints + final Vector2f translationError = new Vector2f(u.dot(this.N1), u.dot(this.N2)); + + // Compute the Lagrange multiplier lambda for the 2 translation raints + final Vector2f lambdaTranslation = this.inverseMassMatrixTranslationConstraint.multiplyNew(translationError.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + final Vector3f linearImpulseBody1 = this.N1.multiplyNew(-1).multiplyNew(lambdaTranslation.x).lessNew(this.N2.multiplyNew(lambdaTranslation.y)); + Vector3f angularImpulseBody1 = this.R1PlusUCrossN1.multiplyNew(-1).multiplyNew(lambdaTranslation.x).lessNew(this.R1PlusUCrossN2.multiplyNew(lambdaTranslation.y)); + + // Apply the impulse to the body 1 + final Vector3f v1 = linearImpulseBody1.multiplyNew(inverseMassBody1); + Vector3f w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + x1.add(v1); + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + final Vector3f linearImpulseBody2 = this.N1.multiplyNew(lambdaTranslation.x).addNew(this.N2.multiplyNew(lambdaTranslation.y)); + Vector3f angularImpulseBody2 = this.R2CrossN1.multiplyNew(lambdaTranslation.x).add(this.R2CrossN2.multiplyNew(lambdaTranslation.y)); + + // Apply the impulse to the body 2 + final Vector3f v2 = linearImpulseBody2.multiplyNew(inverseMassBody2); + Vector3f w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + x2.add(v2); + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + + // --------------- Rotation Constraints --------------- // + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotationConstraint = this.i1.addNew(this.i2); + if (this.body1.getType() == BodyType.DYNAMIC || this.body2.getType() == BodyType.DYNAMIC) { + this.inverseMassMatrixRotationConstraint = this.inverseMassMatrixRotationConstraint.inverseNew(); + } + + // Compute the position error for the 3 rotation raints + final Quaternion currentOrientationDifference = q2.multiplyNew(q1.inverseNew()); + currentOrientationDifference.normalize(); + final Quaternion qError = currentOrientationDifference.multiplyNew(this.initOrientationDifferenceInv); + final Vector3f errorRotation = qError.getVectorV().multiply(2.0f); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + final Vector3f lambdaRotation = this.inverseMassMatrixRotationConstraint.multiplyNew(errorRotation.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = lambdaRotation.multiplyNew(-1); + + // Apply the impulse to the body 1 + w1 = this.i1.multiplyNew(angularImpulseBody1); + + // Update the body position/orientation of body 1 + q1.add((new Quaternion(0, w1)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 2 + angularImpulseBody2 = lambdaRotation; + + // Apply the impulse to the body 2 + w2 = this.i2.multiplyNew(angularImpulseBody2); + + // Update the body position/orientation of body 2 + q2.add((new Quaternion(0, w2)).multiply(q2).multiply(0.5f)); + q2.normalize(); + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + if (this.isLowerLimitViolated || this.isUpperLimitViolated) { + + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits (1x1 matrix) + this.inverseMassMatrixLimit = this.body1.massInverse + this.body2.massInverse + this.R1PlusUCrossSliderAxis.dot(this.i1.multiplyNew(this.R1PlusUCrossSliderAxis)) + + this.R2CrossSliderAxis.dot(this.i2.multiplyNew(this.R2CrossSliderAxis)); + this.inverseMassMatrixLimit = (this.inverseMassMatrixLimit > 0.0f) ? 1.0f / this.inverseMassMatrixLimit : 0.0f; + } + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute the Lagrange multiplier lambda for the lower limit raint + final float lambdaLowerLimit = this.inverseMassMatrixLimit * (-lowerLimitError); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 1 + final Vector3f linearImpulseBody1_tmp = this.sliderAxisWorld.multiplyNew(-lambdaLowerLimit); + final Vector3f angularImpulseBody1_tmp = this.R1PlusUCrossSliderAxis.multiplyNew(-lambdaLowerLimit); + + // Apply the impulse to the body 1 + final Vector3f v1_tmp = linearImpulseBody1_tmp.multiplyNew(inverseMassBody1); + final Vector3f w1_tmp = this.i1.multiplyNew(angularImpulseBody1_tmp); + + // Update the body position/orientation of body 1 + x1.add(v1_tmp); + q1.add((new Quaternion(0, w1_tmp)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + final Vector3f linearImpulseBody2_tmp = this.sliderAxisWorld.multiplyNew(lambdaLowerLimit); + final Vector3f angularImpulseBody2_tmp = this.R2CrossSliderAxis.multiplyNew(lambdaLowerLimit); + + // Apply the impulse to the body 2 + final Vector3f v2_tmp = linearImpulseBody2_tmp.multiplyNew(inverseMassBody2); + final Vector3f w2_tmp = this.i2.multiplyNew(angularImpulseBody2_tmp); + + // Update the body position/orientation of body 2 + x2.add(v2_tmp); + q2.add((new Quaternion(0, w2_tmp)).multiply(q2).multiply(0.5f)); + q2.normalize(); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute the Lagrange multiplier lambda for the upper limit raint + final float lambdaUpperLimit = this.inverseMassMatrixLimit * (-upperLimitError); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 1 + final Vector3f linearImpulseBody1_tmp = this.sliderAxisWorld.multiplyNew(lambdaUpperLimit); + final Vector3f angularImpulseBody1_tmp = this.R1PlusUCrossSliderAxis.multiplyNew(lambdaUpperLimit); + + // Apply the impulse to the body 1 + final Vector3f v1_tmp = linearImpulseBody1_tmp.multiplyNew(inverseMassBody1); + final Vector3f w1_tmp = this.i1.multiplyNew(angularImpulseBody1_tmp); + + // Update the body position/orientation of body 1 + x1.add(v1_tmp); + q1.add((new Quaternion(0, w1_tmp)).multiply(q1).multiply(0.5f)); + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + final Vector3f linearImpulseBody2_tmp = this.sliderAxisWorld.multiplyNew(-lambdaUpperLimit); + final Vector3f angularImpulseBody2_tmp = this.R2CrossSliderAxis.multiplyNew(-lambdaUpperLimit); + + // Apply the impulse to the body 2 + final Vector3f v2_tmp = linearImpulseBody2_tmp.multiplyNew(inverseMassBody2); + final Vector3f w2_tmp = this.i2.multiplyNew(angularImpulseBody2_tmp); + + // Update the body position/orientation of body 2 + x2.add(v2_tmp); + q2.add((new Quaternion(0, w2_tmp)).multiply(q2).multiply(0.5f)); + q2.normalize(); + } + } + } + + @Override + public void solveVelocityConstraint(final ConstraintSolverData raintSolverData) { + + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v for the 2 translation raints + final float el1 = -this.N1.dot(v1) - w1.dot(this.R1PlusUCrossN1) + this.N1.dot(v2) + w2.dot(this.R2CrossN1); + final float el2 = -this.N2.dot(v1) - w1.dot(this.R1PlusUCrossN2) + this.N2.dot(v2) + w2.dot(this.R2CrossN2); + + final Vector2f JvTranslation = new Vector2f(el1, el2); + + // Compute the Lagrange multiplier lambda for the 2 translation raints + final Vector2f deltaLambda = this.inverseMassMatrixTranslationConstraint.multiplyNew(JvTranslation.multiplyNew(-1).less(this.bTranslation)); + this.impulseTranslation.add(deltaLambda); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + final Vector3f linearImpulseBody1 = this.N1.multiplyNew(-1).multiply(deltaLambda.x).less(this.N2.multiplyNew(deltaLambda.y)); + Vector3f angularImpulseBody1 = this.R1PlusUCrossN1.multiplyNew(-1).multiply(deltaLambda.x).less(this.R1PlusUCrossN2.multiplyNew(deltaLambda.y)); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + final Vector3f linearImpulseBody2 = this.N1.multiplyNew(deltaLambda.x).add(this.N2.multiplyNew(deltaLambda.y)); + Vector3f angularImpulseBody2 = this.R2CrossN1.multiplyNew(deltaLambda.x).add(this.R2CrossN2.multiplyNew(deltaLambda.y)); + + // Apply the impulse to the body 2 + v2.add(linearImpulseBody2.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 3 rotation raints + final Vector3f JvRotation = w2.lessNew(w1); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + final Vector3f deltaLambda2 = this.inverseMassMatrixRotationConstraint.multiplyNew(JvRotation.multiplyNew(-1).less(this.bRotation)); + this.impulseRotation.add(deltaLambda2); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = deltaLambda2.multiplyNew(-1); + + // Apply the impulse to the body to body 1 + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 2 + angularImpulseBody2 = deltaLambda2; + + // Apply the impulse to the body 2 + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute J*v for the lower limit raint + final float JvLowerLimit = this.sliderAxisWorld.dot(v2) + this.R2CrossSliderAxis.dot(w2) - this.sliderAxisWorld.dot(v1) - this.R1PlusUCrossSliderAxis.dot(w1); + + // Compute the Lagrange multiplier lambda for the lower limit raint + float deltaLambdaLower = this.inverseMassMatrixLimit * (-JvLowerLimit - this.bLowerLimit); + final float lambdaTemp = this.impulseLowerLimit; + this.impulseLowerLimit = FMath.max(this.impulseLowerLimit + deltaLambdaLower, 0.0f); + deltaLambdaLower = this.impulseLowerLimit - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 1 + final Vector3f linearImpulseBody1_tmp = this.sliderAxisWorld.multiplyNew(-deltaLambdaLower); + final Vector3f angularImpulseBody1_tmp = this.R1PlusUCrossSliderAxis.multiplyNew(-deltaLambdaLower); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1_tmp.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1_tmp)); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + final Vector3f linearImpulseBody2_tmp = this.sliderAxisWorld.multiplyNew(deltaLambdaLower); + final Vector3f angularImpulseBody2_tmp = this.R2CrossSliderAxis.multiplyNew(deltaLambdaLower); + + // Apply the impulse to the body 2 + v2.add(linearImpulseBody2_tmp.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2_tmp)); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute J*v for the upper limit raint + final float JvUpperLimit = this.sliderAxisWorld.dot(v1) + this.R1PlusUCrossSliderAxis.dot(w1) - this.sliderAxisWorld.dot(v2) - this.R2CrossSliderAxis.dot(w2); + + // Compute the Lagrange multiplier lambda for the upper limit raint + float deltaLambdaUpper = this.inverseMassMatrixLimit * (-JvUpperLimit - this.bUpperLimit); + final float lambdaTemp = this.impulseUpperLimit; + this.impulseUpperLimit = FMath.max(this.impulseUpperLimit + deltaLambdaUpper, 0.0f); + deltaLambdaUpper = this.impulseUpperLimit - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 1 + final Vector3f linearImpulseBody1_tmp = this.sliderAxisWorld.multiplyNew(deltaLambdaUpper); + final Vector3f angularImpulseBody1_tmp = this.R1PlusUCrossSliderAxis.multiplyNew(deltaLambdaUpper); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1_tmp.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1_tmp)); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + final Vector3f linearImpulseBody2_tmp = this.sliderAxisWorld.multiplyNew(-deltaLambdaUpper); + final Vector3f angularImpulseBody2_tmp = this.R2CrossSliderAxis.multiplyNew(-deltaLambdaUpper); + + // Apply the impulse to the body 2 + v2.add(linearImpulseBody2_tmp.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2_tmp)); + } + } + + // --------------- Motor --------------- // + + if (this.isMotorEnabled) { + + // Compute J*v for the motor + final float JvMotor = this.sliderAxisWorld.dot(v1) - this.sliderAxisWorld.dot(v2); + + // Compute the Lagrange multiplier lambda for the motor + final float maxMotorImpulse = this.maxMotorForce * raintSolverData.timeStep; + float deltaLambdaMotor = this.inverseMassMatrixMotor * (-JvMotor - this.motorSpeed); + final float lambdaTemp = this.impulseMotor; + this.impulseMotor = FMath.clamp(this.impulseMotor + deltaLambdaMotor, -maxMotorImpulse, maxMotorImpulse); + deltaLambdaMotor = this.impulseMotor - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the motor of body 1 + final Vector3f linearImpulseBody1_tmp = this.sliderAxisWorld.multiplyNew(deltaLambdaMotor); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1_tmp.multiplyNew(inverseMassBody1)); + + // Compute the impulse P=J^T * lambda for the motor of body 2 + final Vector3f linearImpulseBody2_tmp = this.sliderAxisWorld.multiplyNew(-deltaLambdaMotor); + + // Apply the impulse to the body 2 + v2.add(linearImpulseBody2_tmp.multiplyNew(inverseMassBody2)); + } + } + + @Override + public void warmstart(final ConstraintSolverData raintSolverData) { + // Get the velocities + final Vector3f v1 = raintSolverData.linearVelocities[this.indexBody1]; + final Vector3f v2 = raintSolverData.linearVelocities[this.indexBody2]; + final Vector3f w1 = raintSolverData.angularVelocities[this.indexBody1]; + final Vector3f w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + final float inverseMassBody1 = this.body1.massInverse; + final float inverseMassBody2 = this.body2.massInverse; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + final float impulseLimits = this.impulseUpperLimit - this.impulseLowerLimit; + final Vector3f linearImpulseLimits = this.sliderAxisWorld.multiplyNew(impulseLimits); + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + final Vector3f impulseMotor = this.sliderAxisWorld.multiplyNew(this.impulseMotor); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + final Vector3f linearImpulseBody1 = this.N1.multiplyNew(-1).multiply(this.impulseTranslation.x).less(this.N2.multiplyNew(this.impulseTranslation.y)); + final Vector3f angularImpulseBody1 = this.R1PlusUCrossN1.multiplyNew(-1).multiply(this.impulseTranslation.x).less(this.R1PlusUCrossN2.multiplyNew(this.impulseTranslation.y)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1.add(this.impulseRotation.multiplyNew(-1)); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + linearImpulseBody1.add(linearImpulseLimits); + angularImpulseBody1.add(this.R1PlusUCrossSliderAxis.multiplyNew(impulseLimits)); + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + linearImpulseBody1.add(impulseMotor); + + // Apply the impulse to the body 1 + v1.add(linearImpulseBody1.multiplyNew(inverseMassBody1)); + w1.add(this.i1.multiplyNew(angularImpulseBody1)); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + final Vector3f linearImpulseBody2 = this.N1.multiplyNew(this.impulseTranslation.x).add(this.N2.multiplyNew(this.impulseTranslation.y)); + final Vector3f angularImpulseBody2 = this.R2CrossN1.multiplyNew(this.impulseTranslation.x).add(this.R2CrossN2.multiplyNew(this.impulseTranslation.y)); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 2 + angularImpulseBody2.add(this.impulseRotation); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 2 + linearImpulseBody2.less(linearImpulseLimits); + angularImpulseBody2.add(this.R2CrossSliderAxis.multiplyNew(-impulseLimits)); + + // Compute the impulse P=J^T * lambda for the motor raint of body 2 + linearImpulseBody2.add(impulseMotor.multiplyNew(-1)); + + // Apply the impulse to the body 2 + v2.add(linearImpulseBody2.multiplyNew(inverseMassBody2)); + w2.add(this.i2.multiplyNew(angularImpulseBody2)); + } + +} diff --git a/src/org/atriasoft/ephysics/constraint/SliderJointInfo.java b/src/org/atriasoft/ephysics/constraint/SliderJointInfo.java new file mode 100644 index 0000000..617d2bd --- /dev/null +++ b/src/org/atriasoft/ephysics/constraint/SliderJointInfo.java @@ -0,0 +1,89 @@ +package org.atriasoft.ephysics.constraint; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * This structure is used to gather the information needed to create a slider + * joint. This structure will be used to create the actual slider joint. + */ +public class SliderJointInfo extends JointInfo { + + public Vector3f anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + public Vector3f sliderAxisWorldSpace; //!< Slider axis (in world-space coordinates) + public boolean isLimitEnabled; //!< True if the slider limits are enabled + public boolean isMotorEnabled; //!< True if the slider motor is enabled + public float minTranslationLimit; //!< Mininum allowed translation if limits are enabled + public float maxTranslationLimit; //!< Maximum allowed translation if limits are enabled + public float motorSpeed; //!< Motor speed + public float maxMotorForce; //!< Maximum motor force (in Newtons) that can be applied to reach to desired motor speed + + /** + * Constructor without limits and without motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space + * @param _initSliderAxisWorldSpace The initial slider axis in world-space + */ + public SliderJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initSliderAxisWorldSpace) { + super(_rigidBody1, _rigidBody2, JointType.SLIDERJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.sliderAxisWorldSpace = _initSliderAxisWorldSpace; + this.isLimitEnabled = false; + this.isMotorEnabled = false; + this.minTranslationLimit = -1.0f; + this.maxTranslationLimit = 1.0f; + this.motorSpeed = 0; + this.maxMotorForce = 0; + + } + + /** + * Constructor with limits and no motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space + * @param _initSliderAxisWorldSpace The initial slider axis in world-space + * @param _initMinTranslationLimit The initial minimum translation limit (in meters) + * @param _initMaxTranslationLimit The initial maximum translation limit (in meters) + */ + public SliderJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initSliderAxisWorldSpace, + final float _initMinTranslationLimit, final float _initMaxTranslationLimit) { + super(_rigidBody1, _rigidBody2, JointType.SLIDERJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.sliderAxisWorldSpace = _initSliderAxisWorldSpace; + this.isLimitEnabled = true; + this.isMotorEnabled = false; + this.minTranslationLimit = _initMinTranslationLimit; + this.maxTranslationLimit = _initMaxTranslationLimit; + this.motorSpeed = 0; + this.maxMotorForce = 0; + + } + + /** + * Constructor with limits and motor + * @param _rigidBody1 The first body of the joint + * @param _rigidBody2 The second body of the joint + * @param _initAnchorPointWorldSpace The initial anchor point in world-space + * @param _initSliderAxisWorldSpace The initial slider axis in world-space + * @param _initMinTranslationLimit The initial minimum translation limit (in meters) + * @param _initMaxTranslationLimit The initial maximum translation limit (in meters) + * @param _initMotorSpeed The initial speed of the joint motor (in meters per second) + * @param _initMaxMotorForce The initial maximum motor force of the joint (in Newtons x meters) + */ + public SliderJointInfo(final RigidBody _rigidBody1, final RigidBody _rigidBody2, final Vector3f _initAnchorPointWorldSpace, final Vector3f _initSliderAxisWorldSpace, + final float _initMinTranslationLimit, final float _initMaxTranslationLimit, final float _initMotorSpeed, final float _initMaxMotorForce) { + super(_rigidBody1, _rigidBody2, JointType.SLIDERJOINT); + this.anchorPointWorldSpace = _initAnchorPointWorldSpace; + this.sliderAxisWorldSpace = _initSliderAxisWorldSpace; + this.isLimitEnabled = true; + this.isMotorEnabled = true; + this.minTranslationLimit = _initMinTranslationLimit; + this.maxTranslationLimit = _initMaxTranslationLimit; + this.motorSpeed = _initMotorSpeed; + this.maxMotorForce = _initMaxMotorForce; + + } +} diff --git a/src/org/atriasoft/ephysics/engine/CollisionCallback.java b/src/org/atriasoft/ephysics/engine/CollisionCallback.java new file mode 100644 index 0000000..7b78e50 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/CollisionCallback.java @@ -0,0 +1,17 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.ephysics.constraint.ContactPointInfo; + +/** + * This class can be used to register a callback for collision test queries. + * You should implement your own class inherited from this one and implement + * the notifyRaycastHit() method. This method will be called for each ProxyShape + * that is hit by the ray. + */ +public interface CollisionCallback { + /** + * This method will be called for contact. + * @param _contactPointInfo Contact information property. + */ + void notifyContact(ContactPointInfo _contactPointInfo); +} diff --git a/src/org/atriasoft/ephysics/engine/CollisionWorld.java b/src/org/atriasoft/ephysics/engine/CollisionWorld.java new file mode 100644 index 0000000..bcbdcb1 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/CollisionWorld.java @@ -0,0 +1,254 @@ +package org.atriasoft.ephysics.engine; + +import java.util.ArrayList; +import java.util.List; + +import org.atriasoft.ephysics.RaycastCallback; +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.collision.CollisionDetection; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.broadphase.DTree; +import org.atriasoft.ephysics.collision.narrowphase.CollisionDispatch; +import org.atriasoft.ephysics.collision.shapes.AABB; +import org.atriasoft.ephysics.mathematics.Ray; +import org.atriasoft.ephysics.mathematics.Set; +import org.atriasoft.etk.math.Transform3D; + +/** + * This class represent a world where it is possible to move bodies + * by hand and to test collision between each other. In this kind of + * world, the bodies movement is not computed using the laws of physics. + */ +public class CollisionWorld { + + public CollisionDetection collisionDetection; //!< Reference to the collision detection + protected Set bodies = new Set(Set.getCollisionBodyCoparator()); //!< All the bodies (rigid and soft) of the world + protected int currentBodyID; //!< Current body ID + protected List freeBodiesIDs = new ArrayList<>(); //!< List of free ID for rigid bodies + public EventListener eventListener; //!< Pointer to an event listener object + /// Return the next available body ID + + /// Constructor + public CollisionWorld() { + this.collisionDetection = new CollisionDetection(this); + this.currentBodyID = 0; + this.eventListener = null; + } + + int computeNextAvailableBodyID() { + // Compute the body ID + int bodyID; + if (!this.freeBodiesIDs.isEmpty()) { + bodyID = this.freeBodiesIDs.get(0); + this.freeBodiesIDs.remove(bodyID); + } else { + bodyID = this.currentBodyID; + this.currentBodyID++; + } + return bodyID; + } + + /* + public Set::Iterator getBodiesEndIterator() { + return this.bodies.end(); + } + */ + /** + * Create a collision body and add it to the world + * @param transform Transform3Dation mapping the local-space of the body to world-space + * @return A pointer to the body that has been created in the world + */ + public CollisionBody createCollisionBody(final Transform3D transform) { + // Get the next available body ID + final int bodyID = computeNextAvailableBodyID(); + // Largest index cannot be used (it is used for invalid index) + assert (bodyID < Integer.MAX_VALUE); + // Create the collision body + final CollisionBody collisionBody = new CollisionBody(transform, this, bodyID); + assert (collisionBody != null); + // Add the collision body to the world + this.bodies.add(collisionBody); + // Return the pointer to the rigid body + return collisionBody; + } + + /** + * Destroy a collision body + * @param collisionBody Pointer to the body to destroy + */ + public void destroyCollisionBody(CollisionBody collisionBody) { + // Remove all the collision shapes of the body + collisionBody.removeAllCollisionShapes(); + // Add the body ID to the list of free IDs + this.freeBodiesIDs.add(collisionBody.getID()); + // Remove the collision body from the list of bodies + this.bodies.erase(collisionBody); + collisionBody = null; + } + + /** + * Get an iterator to the beginning of the bodies of the physics world + * @return An starting iterator to the set of bodies of the world + */ + /* + public Set::Iterator getBodiesBeginIterator() { + return this.bodies.begin(); + } + */ + /** + * Get an iterator to the end of the bodies of the physics world + * @return An ending iterator to the set of bodies of the world + */ + + public CollisionDetection getCollisionDetection() { + return this.collisionDetection; + } + + /** + * Ray cast method + * @param _ray Ray to use for raycasting + * @param _raycastCallback Pointer to the class with the callback method + * @param _raycastWithCategoryMaskBits Bits mask corresponding to the category of bodies to be raycasted + */ + public void raycast(final Ray _ray, final RaycastCallback _raycastCallback) { + raycast(_ray, _raycastCallback, 0xFFFF); + } + + public void raycast(final Ray _ray, final RaycastCallback _raycastCallback, final int _raycastWithCategoryMaskBits) { + this.collisionDetection.raycast(_raycastCallback, _ray, _raycastWithCategoryMaskBits); + } + + /// Reset all the contact manifolds linked list of each body + void resetContactManifoldListsOfBodies() { + // For each rigid body of the world + for (final CollisionBody it : this.bodies) { + // Reset the contact manifold list of the body + it.resetContactManifoldsList(); + } + } + + /** + * Set the collision dispatch configuration + * This can be used to replace default collision detection algorithms by your + * custom algorithm for instance. + * @param _CollisionDispatch Pointer to a collision dispatch object describing + * which collision detection algorithm to use for two given collision shapes + */ + public void setCollisionDispatch(final CollisionDispatch _collisionDispatch) { + this.collisionDetection.setCollisionDispatch(_collisionDispatch); + } + + /** + * Test if the AABBs of two bodies overlap + * @param _body1 Pointer to the first body to test + * @param _body2 Pointer to the second body to test + * @return True if the AABBs of the two bodies overlap and false otherwise + */ + public boolean testAABBOverlap(final CollisionBody _body1, final CollisionBody _body2) { + // If one of the body is not active, we return no overlap + if (!_body1.isActive() || !_body2.isActive()) { + return false; + } + // Compute the AABBs of both bodies + final AABB body1AABB = _body1.getAABB(); + final AABB body2AABB = _body2.getAABB(); + // Return true if the two AABBs overlap + return body1AABB.testCollision(body2AABB); + } + + /** + * Test if the AABBs of two proxy shapes overlap + * @param _shape1 Pointer to the first proxy shape to test + * @param _shape2 Pointer to the second proxy shape to test + */ + public boolean testAABBOverlap(final ProxyShape _shape1, final ProxyShape _shape2) { + return this.collisionDetection.testAABBOverlap(_shape1, _shape2); + } + + /** + * Test and report collisions between two bodies + * @param _body1 Pointer to the first body to test + * @param _body2 Pointer to the second body to test + * @param _callback Pointer to the object with the callback method + */ + public void testCollision(final CollisionBody _body1, final CollisionBody _body2, final CollisionCallback _callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + for (ProxyShape shape = _body1.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes1.add(shape.broadPhaseID); + } + final Set shapes2 = new Set(Set.getDTreeCoparator()); + for (ProxyShape shape = _body2.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes2.add(shape.broadPhaseID); + } + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(_callback, shapes1, shapes2); + } + + /** + * Test and report collisions between a body and all the others bodies of the world. + * @param _body Pointer to the first body to test + * @param _callback Pointer to the object with the callback method + */ + public void testCollision(final CollisionBody _body, final CollisionCallback _callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + // For each shape of the body + for (ProxyShape shape = _body.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes1.add(shape.broadPhaseID); + } + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(_callback, shapes1, emptySet); + } + + /** + * Test and report collisions between all shapes of the world + * @param _callback Pointer to the object with the callback method + */ + public void testCollision(final CollisionCallback _callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(_callback, emptySet, emptySet); + } + + /** + * Test and report collisions between a given shape and all the others shapes of the world. + * @param _shape Pointer to the proxy shape to test + * @param _callback Pointer to the object with the callback method + */ + public void testCollision(final ProxyShape _shape, final CollisionCallback _callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + final Set shapes = new Set(Set.getDTreeCoparator()); + shapes.add(_shape.broadPhaseID); + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(_callback, shapes, emptySet); + } + + /** + * Test and report collisions between two given shapes + * @param _shape1 Pointer to the first proxy shape to test + * @param _shape2 Pointer to the second proxy shape to test + * @param _callback Pointer to the object with the callback method + */ + public void testCollision(final ProxyShape _shape1, final ProxyShape _shape2, final CollisionCallback _callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + shapes1.add(_shape1.broadPhaseID); + final Set shapes2 = new Set(Set.getDTreeCoparator()); + shapes2.add(_shape2.broadPhaseID); + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(_callback, shapes1, shapes2); + } +} diff --git a/src/org/atriasoft/ephysics/engine/ConstraintSolver.java b/src/org/atriasoft/ephysics/engine/ConstraintSolver.java new file mode 100644 index 0000000..af837f4 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/ConstraintSolver.java @@ -0,0 +1,163 @@ +package org.atriasoft.ephysics.engine; + +import java.util.List; +import java.util.Map; + +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; +import org.atriasoft.ephysics.constraint.Joint; + +/** + * This class represents the raint solver that is used to solve raints between + * the rigid bodies. The raint solver is based on the "Sequential Impulse" technique + * described by Erin Catto in his GDC slides (http://code.google.com/p/box2d/downloads/list). + * + * A raint between two bodies is represented by a function C(x) which is equal to zero + * when the raint is satisfied. The condition C(x)=0 describes a valid position and the + * condition dC(x)/dt=0 describes a valid velocity. We have dC(x)/dt = Jv + b = 0 where J is + * the Jacobian matrix of the raint, v is a vector that contains the velocity of both + * bodies and b is the raint bias. We are looking for a force Fc that will act on the + * bodies to keep the raint satisfied. Note that from the work principle, we have + * Fc = J^t * lambda where J^t is the transpose of the Jacobian matrix and lambda is a + * Lagrange multiplier. Therefore, finding the force Fc is equivalent to finding the Lagrange + * multiplier lambda. + + * An impulse P = F * dt where F is a force and dt is the timestep. We can apply impulses a + * body to change its velocity. The idea of the Sequential Impulse technique is to apply + * impulses to bodies of each raints in order to keep the raint satisfied. + * + * --- Step 1 --- + * + * First, we integrate the applied force Fa acting of each rigid body (like gravity, ...) and + * we obtain some new velocities v2' that tends to violate the raints. + * + * v2' = v1 + dt * M^-1 * Fa + * + * where M is a matrix that contains mass and inertia tensor information. + * + * --- Step 2 --- + * + * During the second step, we iterate over all the raints for a certain number of + * iterations and for each raint we compute the impulse to apply to the bodies needed + * so that the new velocity of the bodies satisfy Jv + b = 0. From the Newton law, we know that + * M * deltaV = Pc where M is the mass of the body, deltaV is the difference of velocity and + * Pc is the raint impulse to apply to the body. Therefore, we have + * v2 = v2' + M^-1 * Pc. For each raint, we can compute the Lagrange multiplier lambda + * using : lambda = -this.c (Jv2' + b) where this.c = 1 / (J * M^-1 * J^t). Now that we have the + * Lagrange multiplier lambda, we can compute the impulse Pc = J^t * lambda * dt to apply to + * the bodies to satisfy the raint. + * + * --- Step 3 --- + * + * In the third step, we integrate the new position x2 of the bodies using the new velocities + * v2 computed in the second step with : x2 = x1 + dt * v2. + * + * Note that in the following code (as it is also explained in the slides from Erin Catto), + * the value lambda is not only the lagrange multiplier but is the multiplication of the + * Lagrange multiplier with the timestep dt. Therefore, in the following code, when we use + * lambda, we mean (lambda * dt). + * + * We are using the accumulated impulse technique that is also described in the slides from + * Erin Catto. + * + * We are also using warm starting. The idea is to warm start the solver at the beginning of + * each step by applying the last impulstes for the raints that we already existing at the + * previous step. This allows the iterative solver to converge faster towards the solution. + * + * For contact raints, we are also using split impulses so that the position correction + * that uses Baumgarte stabilization does not change the momentum of the bodies. + * + * There are two ways to apply the friction raints. Either the friction raints are + * applied at each contact point or they are applied only at the center of the contact manifold + * between two bodies. If we solve the friction raints at each contact point, we need + * two raints (two tangential friction directions) and if we solve the friction + * raints at the center of the contact manifold, we need two raints for tangential + * friction but also another twist friction raint to prevent spin of the body around the + * contact manifold center. + */ +public class ConstraintSolver { + + private final Map mapBodyToConstrainedVelocityIndex; //!< Reference to the map that associates rigid body to their index in the rained velocities array + private float timeStep; //!< Current time step + private boolean isWarmStartingActive; //!< True if the warm starting of the solver is active + private final ConstraintSolverData raintSolverData; //!< Constraint solver data used to initialize and solve the raints + /// Constructor + + public ConstraintSolver(final Map mapBodyToVelocityIndex) { + this.mapBodyToConstrainedVelocityIndex = mapBodyToVelocityIndex; + this.isWarmStartingActive = true; + this.raintSolverData = new ConstraintSolverData(mapBodyToVelocityIndex); + + } + + /// Return true if the Non-Linear-Gauss-Seidel position correction technique is active + public boolean getIsNonLinearGaussSeidelPositionCorrectionActive() { + return this.isWarmStartingActive; + } + + /// Initialize the raint solver for a given island + public void initializeForIsland(final float dt, final Island island) { + assert (island != null); + assert (island.getNbBodies() > 0); + assert (island.getNbJoints() > 0); + // Set the current time step + this.timeStep = dt; + // Initialize the raint solver data used to initialize and solve the raints + this.raintSolverData.timeStep = this.timeStep; + this.raintSolverData.isWarmStartingActive = this.isWarmStartingActive; + // For each joint of the island + final List joints = island.getJoints(); + for (int iii = 0; iii < island.getNbJoints(); ++iii) { + // Initialize the raint before solving it + joints.get(iii).initBeforeSolve(this.raintSolverData); + // Warm-start the raint if warm-starting is enabled + if (this.isWarmStartingActive) { + joints.get(iii).warmstart(this.raintSolverData); + } + } + } + + /// Set the rained positions/orientations arrays + public void setConstrainedPositionsArrays(final Vector3f[] rainedPositions, final Quaternion[] rainedOrientations) { + assert (rainedPositions != null); + assert (rainedOrientations != null); + this.raintSolverData.positions = rainedPositions; + this.raintSolverData.orientations = rainedOrientations; + } + + /// Set the rained velocities arrays + public void setConstrainedVelocitiesArrays(final Vector3f[] rainedLinearVelocities, final Vector3f[] rainedAngularVelocities) { + assert (rainedLinearVelocities != null); + assert (rainedAngularVelocities != null); + this.raintSolverData.linearVelocities = rainedLinearVelocities; + this.raintSolverData.angularVelocities = rainedAngularVelocities; + } + + /// Enable/Disable the Non-Linear-Gauss-Seidel position correction technique. + public void setIsNonLinearGaussSeidelPositionCorrectionActive(final boolean isActive) { + this.isWarmStartingActive = isActive; + } + + /// Solve the position raints + public void solvePositionConstraints(final Island island) { + assert (island != null); + assert (island.getNbJoints() > 0); + final List joints = island.getJoints(); + for (int iii = 0; iii < joints.size(); ++iii) { + joints.get(iii).solvePositionConstraint(this.raintSolverData); + } + } + + /// Solve the raints + public void solveVelocityConstraints(final Island island) { + assert (island != null); + assert (island.getNbJoints() > 0); + // For each joint of the island + final List joints = island.getJoints(); + for (int iii = 0; iii < island.getNbJoints(); ++iii) { + joints.get(iii).solveVelocityConstraint(this.raintSolverData); + } + } +} diff --git a/src/org/atriasoft/ephysics/engine/ConstraintSolverData.java b/src/org/atriasoft/ephysics/engine/ConstraintSolverData.java new file mode 100644 index 0000000..3f75cbc --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/ConstraintSolverData.java @@ -0,0 +1,28 @@ +package org.atriasoft.ephysics.engine; + +import java.util.Map; + +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.body.RigidBody; + +/** + * This structure contains data from the raint solver that are used to solve + * each joint raint. + */ +public class ConstraintSolverData { + + public float timeStep; //!< Current time step of the simulation + public Vector3f[] linearVelocities = null; //!< Array with the bodies linear velocities + public Vector3f[] angularVelocities = null; //!< Array with the bodies angular velocities + public Vector3f[] positions = null; //!< Reference to the bodies positions + public Quaternion[] orientations = null; //!< Reference to the bodies orientations + public Map mapBodyToConstrainedVelocityIndex; //!< Reference to the map that associates rigid body to their index in the rained velocities array + public boolean isWarmStartingActive; //!< True if warm starting of the solver is active + + public ConstraintSolverData(final Map refMapBodyToConstrainedVelocityIndex) { + this.mapBodyToConstrainedVelocityIndex = refMapBodyToConstrainedVelocityIndex; + + } +} diff --git a/src/org/atriasoft/ephysics/engine/ContactManifoldSolver.java b/src/org/atriasoft/ephysics/engine/ContactManifoldSolver.java new file mode 100644 index 0000000..dae63b6 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/ContactManifoldSolver.java @@ -0,0 +1,53 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.ephysics.collision.ContactManifold; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * Contact solver internal data structure to store all the information relative to a contact manifold. + */ +public class ContactManifoldSolver { + int indexBody1; //!< Index of body 1 in the raint solver + int indexBody2; //!< Index of body 2 in the raint solver + float massInverseBody1; //!< Inverse of the mass of body 1 + float massInverseBody2; //!< Inverse of the mass of body 2 + Matrix3f inverseInertiaTensorBody1 = new Matrix3f(); //!< Inverse inertia tensor of body 1 + Matrix3f inverseInertiaTensorBody2 = new Matrix3f(); //!< Inverse inertia tensor of body 2 + ContactPointSolver[] contacts = new ContactPointSolver[ContactManifold.MAX_CONTACT_POINTS_IN_MANIFOLD]; //!< Contact point raints + int nbContacts; //!< Number of contact points + boolean isBody1DynamicType; //!< True if the body 1 is of type dynamic + boolean isBody2DynamicType; //!< True if the body 2 is of type dynamic + float restitutionFactor; //!< Mix of the restitution factor for two bodies + float frictionCoefficient; //!< Mix friction coefficient for the two bodies + float rollingResistanceFactor; //!< Rolling resistance factor between the two bodies + ContactManifold externalContactManifold; //!< Pointer to the external contact manifold + // - Variables used when friction raints are apply at the center of the manifold-// + Vector3f normal = new Vector3f(0, 0, 0); //!< Average normal vector of the contact manifold + Vector3f frictionPointBody1 = new Vector3f(0, 0, 0); //!< Point on body 1 where to apply the friction raints + Vector3f frictionPointBody2 = new Vector3f(0, 0, 0); //!< Point on body 2 where to apply the friction raints + Vector3f r1Friction = new Vector3f(0, 0, 0); //!< R1 vector for the friction raints + Vector3f r2Friction = new Vector3f(0, 0, 0); //!< R2 vector for the friction raints + Vector3f r1CrossT1 = new Vector3f(0, 0, 0); //!< Cross product of r1 with 1st friction vector + Vector3f r1CrossT2 = new Vector3f(0, 0, 0); //!< Cross product of r1 with 2nd friction vector + Vector3f r2CrossT1 = new Vector3f(0, 0, 0); //!< Cross product of r2 with 1st friction vector + Vector3f r2CrossT2 = new Vector3f(0, 0, 0); //!< Cross product of r2 with 2nd friction vector + float inverseFriction1Mass; //!< Matrix K for the first friction raint + float inverseFriction2Mass; //!< Matrix K for the second friction raint + float inverseTwistFrictionMass; //!< Matrix K for the twist friction raint + Matrix3f inverseRollingResistance = new Matrix3f(); //!< Matrix K for the rolling resistance raint + Vector3f frictionVector1 = new Vector3f(0, 0, 0); //!< First friction direction at contact manifold center + Vector3f frictionvec2 = new Vector3f(0, 0, 0); //!< Second friction direction at contact manifold center + Vector3f oldFrictionVector1 = new Vector3f(0, 0, 0); //!< Old 1st friction direction at contact manifold center + Vector3f oldFrictionvec2 = new Vector3f(0, 0, 0); //!< Old 2nd friction direction at contact manifold center + float friction1Impulse; //!< First friction direction impulse at manifold center + float friction2Impulse; //!< Second friction direction impulse at manifold center + float frictionTwistImpulse; //!< Twist friction impulse at contact manifold center + Vector3f rollingResistanceImpulse = new Vector3f(0, 0, 0); //!< Rolling resistance impulse + + public ContactManifoldSolver() { + for (int iii = 0; iii < ContactManifold.MAX_CONTACT_POINTS_IN_MANIFOLD; iii++) { + this.contacts[iii] = new ContactPointSolver(); + } + } +} diff --git a/src/org/atriasoft/ephysics/engine/ContactPointSolver.java b/src/org/atriasoft/ephysics/engine/ContactPointSolver.java new file mode 100644 index 0000000..379bb7e --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/ContactPointSolver.java @@ -0,0 +1,37 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.etk.math.Vector3f; + +import org.atriasoft.ephysics.constraint.ContactPoint; + +/** + * Contact solver internal data structure that to store all the + * information relative to a contact point + */ +public class ContactPointSolver { + float penetrationImpulse; //!< Accumulated normal impulse + float friction1Impulse; //!< Accumulated impulse in the 1st friction direction + float friction2Impulse; //!< Accumulated impulse in the 2nd friction direction + float penetrationSplitImpulse; //!< Accumulated split impulse for penetration correction + Vector3f rollingResistanceImpulse = new Vector3f(0, 0, 0); //!< Accumulated rolling resistance impulse + Vector3f normal = new Vector3f(0, 0, 0); //!< Normal vector of the contact + Vector3f frictionVector1 = new Vector3f(0, 0, 0); //!< First friction vector in the tangent plane + Vector3f frictionvec2 = new Vector3f(0, 0, 0); //!< Second friction vector in the tangent plane + Vector3f oldFrictionVector1 = new Vector3f(0, 0, 0); //!< Old first friction vector in the tangent plane + Vector3f oldFrictionvec2 = new Vector3f(0, 0, 0); //!< Old second friction vector in the tangent plane + Vector3f r1 = new Vector3f(0, 0, 0); //!< Vector from the body 1 center to the contact point + Vector3f r2 = new Vector3f(0, 0, 0); //!< Vector from the body 2 center to the contact point + Vector3f r1CrossT1 = new Vector3f(0, 0, 0); //!< Cross product of r1 with 1st friction vector + Vector3f r1CrossT2 = new Vector3f(0, 0, 0); //!< Cross product of r1 with 2nd friction vector + Vector3f r2CrossT1 = new Vector3f(0, 0, 0); //!< Cross product of r2 with 1st friction vector + Vector3f r2CrossT2 = new Vector3f(0, 0, 0); //!< Cross product of r2 with 2nd friction vector + Vector3f r1CrossN = new Vector3f(0, 0, 0); //!< Cross product of r1 with the contact normal + Vector3f r2CrossN = new Vector3f(0, 0, 0); //!< Cross product of r2 with the contact normal + float penetrationDepth; //!< Penetration depth + float restitutionBias; //!< Velocity restitution bias + float inversePenetrationMass; //!< Inverse of the matrix K for the penenetration + float inverseFriction1Mass; //!< Inverse of the matrix K for the 1st friction + float inverseFriction2Mass; //!< Inverse of the matrix K for the 2nd friction + boolean isRestingContact; //!< True if the contact was existing last time step + ContactPoint externalContact; //!< Pointer to the external contact +} diff --git a/src/org/atriasoft/ephysics/engine/ContactSolver.java b/src/org/atriasoft/ephysics/engine/ContactSolver.java new file mode 100644 index 0000000..677afea --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/ContactSolver.java @@ -0,0 +1,1109 @@ +package org.atriasoft.ephysics.engine; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.atriasoft.ephysics.Configuration; +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.body.RigidBody; +import org.atriasoft.ephysics.collision.ContactManifold; +import org.atriasoft.ephysics.constraint.ContactPoint; +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Matrix3f; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents the contact solver that is used to solve rigid bodies contacts. + * The raint solver is based on the "Sequential Impulse" technique described by + * Erin Catto in his GDC slides (http://code.google.com/p/box2d/downloads/list). + * + * A raint between two bodies is represented by a function C(x) which is equal to zero + * when the raint is satisfied. The condition C(x)=0 describes a valid position and the + * condition dC(x)/dt=0 describes a valid velocity. We have dC(x)/dt = Jv + b = 0 where J is + * the Jacobian matrix of the raint, v is a vector that contains the velocity of both + * bodies and b is the raint bias. We are looking for a force F_c that will act on the + * bodies to keep the raint satisfied. Note that from the work principle, we have + * F_c = J^t * lambda where J^t is the transpose of the Jacobian matrix and lambda is a + * Lagrange multiplier. Therefore, finding the force F_c is equivalent to finding the Lagrange + * multiplier lambda. + * + * An impulse P = F * dt where F is a force and dt is the timestep. We can apply impulses a + * body to change its velocity. The idea of the Sequential Impulse technique is to apply + * impulses to bodies of each raints in order to keep the raint satisfied. + * + * --- Step 1 --- + * + * First, we integrate the applied force F_a acting of each rigid body (like gravity, ...) and + * we obtain some new velocities v2' that tends to violate the raints. + * + * v2' = v1 + dt * M^-1 * F_a + * + * where M is a matrix that contains mass and inertia tensor information. + * + * --- Step 2 --- + * + * During the second step, we iterate over all the raints for a certain number of + * iterations and for each raint we compute the impulse to apply to the bodies needed + * so that the new velocity of the bodies satisfy Jv + b = 0. From the Newton law, we know that + * M * deltaV = P_c where M is the mass of the body, deltaV is the difference of velocity and + * P_c is the raint impulse to apply to the body. Therefore, we have + * v2 = v2' + M^-1 * P_c. For each raint, we can compute the Lagrange multiplier lambda + * using : lambda = -this.c (Jv2' + b) where this.c = 1 / (J * M^-1 * J^t). Now that we have the + * Lagrange multiplier lambda, we can compute the impulse P_c = J^t * lambda * dt to apply to + * the bodies to satisfy the raint. + * + * --- Step 3 --- + * + * In the third step, we integrate the new position x2 of the bodies using the new velocities + * v2 computed in the second step with : x2 = x1 + dt * v2. + * + * Note that in the following code (as it is also explained in the slides from Erin Catto), + * the value lambda is not only the lagrange multiplier but is the multiplication of the + * Lagrange multiplier with the timestep dt. Therefore, in the following code, when we use + * lambda, we mean (lambda * dt). + * + * We are using the accumulated impulse technique that is also described in the slides from + * Erin Catto. + * + * We are also using warm starting. The idea is to warm start the solver at the beginning of + * each step by applying the last impulstes for the raints that we already existing at the + * previous step. This allows the iterative solver to converge faster towards the solution. + * + * For contact raints, we are also using split impulses so that the position correction + * that uses Baumgarte stabilization does not change the momentum of the bodies. + * + * There are two ways to apply the friction raints. Either the friction raints are + * applied at each contact point or they are applied only at the center of the contact manifold + * between two bodies. If we solve the friction raints at each contact point, we need + * two raints (two tangential friction directions) and if we solve the friction + * raints at the center of the contact manifold, we need two raints for tangential + * friction but also another twist friction raint to prevent spin of the body around the + * contact manifold center. + */ +public class ContactSolver { + + private static float BETA = 0.2f; //!< Beta value for the penetration depth position correction without split impulses + private static float BETA_SPLIT_IMPULSE = 0.2f; //!< Beta value for the penetration depth position correction with split impulses + private static float SLOP = 0.01f; //!< Slop distance (allowed penetration distance between bodies) + private Vector3f[] splitLinearVelocities; //!< Split linear velocities for the position contact solver (split impulse) + private Vector3f[] splitAngularVelocities; //!< Split angular velocities for the position contact solver (split impulse) + private float timeStep; //!< Current time step + private List contactConstraints = new ArrayList<>(); //!< Contact raints + private Vector3f[] linearVelocities; //!< Array of linear velocities + private Vector3f[] angularVelocities; //!< Array of angular velocities + private final Map mapBodyToConstrainedVelocityIndex; //!< Reference to the map of rigid body to their index in the rained velocities array + private final boolean isWarmStartingActive; //!< True if the warm starting of the solver is active + private boolean isSplitImpulseActive; //!< True if the split impulse position correction is active + private boolean isSolveFrictionAtContactManifoldCenterActive; //!< True if we solve 3 friction raints at the contact manifold center only instead of 2 friction raints at each contact point + + /** + * Constructor + * @param _mapBodyToVelocityIndex + */ + public ContactSolver(final Map _mapBodyToVelocityIndex) { + this.splitLinearVelocities = null; + this.splitAngularVelocities = null; + this.linearVelocities = null; + this.angularVelocities = null; + this.mapBodyToConstrainedVelocityIndex = _mapBodyToVelocityIndex; + this.isWarmStartingActive = false; // TODO default true + this.isSplitImpulseActive = true; // TODO default true + this.isSolveFrictionAtContactManifoldCenterActive = true; // TODO default true + } + + /** + * Apply an impulse to the two bodies of a raint + * @param _impulse Impulse to apply + * @param _manifold Constraint to apply the impulse + */ + private void applyImpulse(final Impulse _impulse, final ContactManifoldSolver _manifold) { + //Log.info(" -- _manifold.massInverseBody1=" + _manifold.massInverseBody1); + //Log.info(" -- _manifold.inverseInertiaTensorBody1=" + _manifold.inverseInertiaTensorBody1); + //Log.info(" -- _manifold.massInverseBody2=" + _manifold.massInverseBody2); + //Log.info(" -- _manifold.inverseInertiaTensorBody2=" + _manifold.inverseInertiaTensorBody2); + //Log.info(" -- _impulse.angularImpulseBody2=" + _impulse.angularImpulseBody2); + // Update the velocities of the body 1 by applying the impulse P + this.linearVelocities[_manifold.indexBody1].add(_impulse.linearImpulseBody1.multiplyNew(_manifold.massInverseBody1)); + //Log.info(" -- this.angularVelocities[_manifold.indexBody1]=" + this.angularVelocities[_manifold.indexBody1]); + this.angularVelocities[_manifold.indexBody1].add(_manifold.inverseInertiaTensorBody1.multiplyNew(_impulse.angularImpulseBody1)); + //Log.info(" -- this.angularVelocities[_manifold.indexBody1]=" + this.angularVelocities[_manifold.indexBody1]); + // Update the velocities of the body 1 by applying the impulse P + this.linearVelocities[_manifold.indexBody2].add(_impulse.linearImpulseBody2.multiplyNew(_manifold.massInverseBody2)); + //Log.info(" -- this.angularVelocities[_manifold.indexBody2]=" + this.angularVelocities[_manifold.indexBody2]); + this.angularVelocities[_manifold.indexBody2].add(_manifold.inverseInertiaTensorBody2.multiplyNew(_impulse.angularImpulseBody2)); + //Log.info(" -- this.angularVelocities[_manifold.indexBody2]=" + this.angularVelocities[_manifold.indexBody2]); + } + + /** + * Apply an impulse to the two bodies of a raint + * @param _impulse Impulse to apply + * @param _manifold Constraint to apply the impulse + */ + private void applySplitImpulse(final Impulse _impulse, final ContactManifoldSolver _manifold) { + // Update the velocities of the body 1 by applying the impulse P + this.splitLinearVelocities[_manifold.indexBody1].add(_impulse.linearImpulseBody1.multiplyNew(_manifold.massInverseBody1)); + this.splitAngularVelocities[_manifold.indexBody1].add(_manifold.inverseInertiaTensorBody1.multiplyNew(_impulse.angularImpulseBody1)); + // Update the velocities of the body 1 by applying the impulse P + this.splitLinearVelocities[_manifold.indexBody2].add(_impulse.linearImpulseBody2.multiplyNew(_manifold.massInverseBody2)); + this.splitAngularVelocities[_manifold.indexBody2].add(_manifold.inverseInertiaTensorBody2.multiplyNew(_impulse.angularImpulseBody2)); + } + + /** + * Clean up the raint solver + */ + public void cleanup() { + this.contactConstraints.clear(); + } + + /** + * Compute the first friction raint impulse + * @param _deltaLambda Ratio to apply at the calculation. + * @param _contactPoint Contact point property + * @return Impulse of the friction result + */ + private Impulse computeFriction1Impulse(final float _deltaLambda, final ContactPointSolver _contactPoint) { + //Log.error("=== r1CrossT1=" + _contactPoint.r1CrossT1); + //Log.error("=== r2CrossT1=" + _contactPoint.r2CrossT1); + return new Impulse(_contactPoint.frictionVector1.multiplyNew(-1).multiply(_deltaLambda), _contactPoint.r1CrossT1.multiplyNew(-1).multiply(_deltaLambda), + _contactPoint.frictionVector1.multiplyNew(_deltaLambda), _contactPoint.r2CrossT1.multiplyNew(_deltaLambda)); + } + + /** + * Compute the second friction raint impulse + * @param _deltaLambda Ratio to apply at the calculation. + * @param _contactPoint Contact point property + * @return Impulse of the friction result + */ + private Impulse computeFriction2Impulse(final float _deltaLambda, final ContactPointSolver _contactPoint) { + //Log.error("=== r1CrossT2=" + _contactPoint.r1CrossT2); + //Log.error("=== r2CrossT2=" + _contactPoint.r2CrossT2); + return new Impulse(_contactPoint.frictionvec2.multiplyNew(-1).multiply(_deltaLambda), _contactPoint.r1CrossT2.multiplyNew(-1).multiplyNew(_deltaLambda), + _contactPoint.frictionvec2.multiplyNew(_deltaLambda), _contactPoint.r2CrossT2.multiplyNew(_deltaLambda)); + } + + /** + * Compute the two unit orthogonal vectors "t1" and "t2" that span the tangential friction + * plane for a contact manifold. The two vectors have to be such that : t1 x t2 = contactNormal. + * @param _deltaVelocity Velocity ratio (with the delta time step) + * @param[in,out] _contactPoint Contact point property + */ + private void computeFrictionVectors(final Vector3f _deltaVelocity, final ContactManifoldSolver _contactManifold) { + assert (_contactManifold.normal.length() > 0.0f); + // Compute the velocity difference vector in the tangential plane + final Vector3f normalVelocity = _contactManifold.normal.multiplyNew(_deltaVelocity.dot(_contactManifold.normal)); + final Vector3f tangentVelocity = _deltaVelocity.lessNew(normalVelocity); + // If the velocty difference in the tangential plane is not zero + final float lengthTangenVelocity = tangentVelocity.length(); + if (lengthTangenVelocity > Constant.FLOAT_EPSILON) { + // Compute the first friction vector in the direction of the tangent + // velocity difference + _contactManifold.frictionVector1 = tangentVelocity.divideNew(lengthTangenVelocity); + //Log.error("===1==>>>>>> _contactManifold.frictionVector1=" + _contactManifold.frictionVector1); + } else { + // Get any orthogonal vector to the normal as the first friction vector + _contactManifold.frictionVector1 = _contactManifold.normal.getOrthoVector(); + //Log.error("===2==>>>>>> _contactManifold.frictionVector1=" + _contactManifold.frictionVector1); + } + + // The second friction vector is computed by the cross product of the firs + // friction vector and the contact normal + _contactManifold.frictionvec2 = _contactManifold.normal.cross(_contactManifold.frictionVector1).safeNormalizeNew(); + } + + /** + * Compute the two unit orthogonal vectors "t1" and "t2" that span the tangential friction + * plane for a contact point. The two vectors have to be such that : t1 x t2 = contactNormal. + * @param _deltaVelocity Velocity ratio (with the delta time step) + * @param[in,out] _contactPoint Contact point property + */ + private void computeFrictionVectors(final Vector3f _deltaVelocity, final ContactPointSolver _contactPoint) { + assert (_contactPoint.normal.length() > 0.0f); + //Log.error("===3==>>>>>> _contactPoint.normal=" + _contactPoint.normal); + //Log.error("===3==>>>>>> _deltaVelocity=" + _deltaVelocity); + // Compute the velocity difference vector in the tangential plane + final Vector3f normalVelocity = _contactPoint.normal.multiplyNew(_deltaVelocity.dot(_contactPoint.normal)); + //Log.error("===3==>>>>>> normalVelocity=" + normalVelocity); + final Vector3f tangentVelocity = _deltaVelocity.lessNew(normalVelocity); + //Log.error("===3==>>>>>> tangentVelocity=" + tangentVelocity); + // If the velocty difference in the tangential plane is not zero + final float lengthTangenVelocity = tangentVelocity.length(); + //Log.error("===3==>>>>>> lengthTangenVelocity=" + FMath.floatToString(lengthTangenVelocity)); + if (lengthTangenVelocity > Constant.FLOAT_EPSILON) { + // Compute the first friction vector in the direction of the tangent + // velocity difference + _contactPoint.frictionVector1 = tangentVelocity.divideNew(lengthTangenVelocity); + //Log.error("===3==>>>>>> _contactPoint.frictionVector1=" + _contactPoint.frictionVector1); + } else { + // Get any orthogonal vector to the normal as the first friction vector + _contactPoint.frictionVector1 = _contactPoint.normal.getOrthoVector(); + //Log.error("===4==>>>>>> _contactPoint.frictionVector1=" + _contactPoint.frictionVector1); + } + // The second friction vector is computed by the cross product of the firs + // friction vector and the contact normal + _contactPoint.frictionvec2 = _contactPoint.normal.cross(_contactPoint.frictionVector1).safeNormalizeNew(); + } + + /** + * Compute the mixed friction coefficient from the friction coefficient of each body + * @param _body1 First body to compute + * @param _body2 Second body to compute + * @return Mixed friction coefficient + */ + private float computeMixedFrictionCoefficient(final RigidBody _body1, final RigidBody _body2) { + // Use the geometric mean to compute the mixed friction coefficient + return FMath.sqrt(_body1.getMaterial().getFrictionCoefficient() * _body2.getMaterial().getFrictionCoefficient()); + } + + /** + * Compute the collision restitution factor from the restitution factor of each body + * @param _body1 First body to compute + * @param _body2 Second body to compute + * @return Collision restitution factor + */ + private float computeMixedRestitutionFactor(final RigidBody _body1, final RigidBody _body2) { + final float restitution1 = _body1.getMaterial().getBounciness(); + final float restitution2 = _body2.getMaterial().getBounciness(); + //Log.error("######################### restitution1=" + FMath.floatToString(restitution1)); + //Log.error("######################### restitution2=" + FMath.floatToString(restitution2)); + // Return the largest restitution factor + return (restitution1 > restitution2) ? restitution1 : restitution2; + } + + /** + * Compute the mixed rolling resistance factor between two bodies + * @param _body1 First body to compute + * @param _body2 Second body to compute + * @return Mixed rolling resistance + */ + private float computeMixedRollingResistance(final RigidBody _body1, final RigidBody _body2) { + return 0.5f * (_body1.getMaterial().getRollingResistance() + _body2.getMaterial().getRollingResistance()); + } + + /** + * Compute a penetration raint impulse + * @param _deltaLambda Ratio to apply at the calculation. + * @param[in,out] _contactPoint Contact point property + * @return Impulse of the penetration result + */ + private Impulse computePenetrationImpulse(final float _deltaLambda, final ContactPointSolver _contactPoint) { + //Log.error("=== r1CrossN=" + _contactPoint.r1CrossN); + //Log.error("=== r2CrossN=" + _contactPoint.r2CrossN); + return new Impulse(_contactPoint.normal.multiplyNew(-1).multiply(_deltaLambda), _contactPoint.r1CrossN.multiplyNew(-1).multiply(_deltaLambda), _contactPoint.normal.multiplyNew(_deltaLambda), + _contactPoint.r2CrossN.multiplyNew(_deltaLambda)); + } + + /** + * Initialize the contact raints before solving the system + */ + private void initializeContactConstraints() { + // For each contact raint + for (int ccc = 0; ccc < this.contactConstraints.size(); ccc++) { + //Log.warning("}}}} ccc=" + ccc); + final ContactManifoldSolver manifold = this.contactConstraints.get(ccc); + // Get the inertia tensors of both bodies + final Matrix3f I1 = manifold.inverseInertiaTensorBody1; + //Log.warning("}}}} I1=" + I1); + final Matrix3f I2 = manifold.inverseInertiaTensorBody2; + //Log.warning("}}}} I2=" + I2); + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + manifold.normal = new Vector3f(0.0f, 0.0f, 0.0f); + //Log.error("]]] manifold.normal=" + manifold.normal); + } + //Log.warning("}}}} manifold.normal=" + manifold.normal); + // Get the velocities of the bodies + final Vector3f v1 = this.linearVelocities[manifold.indexBody1]; + final Vector3f w1 = this.angularVelocities[manifold.indexBody1]; + final Vector3f v2 = this.linearVelocities[manifold.indexBody2]; + final Vector3f w2 = this.angularVelocities[manifold.indexBody2]; + //Log.warning("}}}} v1=" + v1); + //Log.warning("}}}} w1=" + w1); + //Log.warning("}}}} v2=" + v2); + //Log.warning("}}}} w2=" + w2); + // For each contact point raint + for (int iii = 0; iii < manifold.nbContacts; iii++) { + //Log.warning("}}}} iii=" + iii); + final ContactPointSolver contactPoint = manifold.contacts[iii]; + //Log.warning("}}}} contactPoint.r1=" + contactPoint.r1); + //Log.warning("}}}} contactPoint.r2=" + contactPoint.r2); + //Log.warning("}}}} contactPoint.normal=" + contactPoint.normal); + final ContactPoint externalContact = contactPoint.externalContact; + // Compute the velocity difference + final Vector3f deltaV = v2.addNew(w2.cross(contactPoint.r2)).less(v1).less(w1.cross(contactPoint.r1)); + //Log.warning("}}}} deltaV=" + deltaV); + contactPoint.r1CrossN = contactPoint.r1.cross(contactPoint.normal); + contactPoint.r2CrossN = contactPoint.r2.cross(contactPoint.normal); + //Log.warning("}}}} contactPoint.r1CrossN=" + contactPoint.r1CrossN); + //Log.warning("}}}} contactPoint.r2CrossN=" + contactPoint.r2CrossN); + // Compute the inverse mass matrix K for the penetration raint + final float massPenetration = manifold.massInverseBody1 + manifold.massInverseBody2 + ((I1.multiplyNew(contactPoint.r1CrossN)).cross(contactPoint.r1)).dot(contactPoint.normal) + + ((I2.multiplyNew(contactPoint.r2CrossN)).cross(contactPoint.r2)).dot(contactPoint.normal); + //Log.warning("}}}} massPenetration=" + FMath.floatToString(massPenetration)); + if (massPenetration > 0.0f) { + contactPoint.inversePenetrationMass = 1.0f / massPenetration; + } + //Log.warning("}}}} contactPoint.inversePenetrationMass=" + FMath.floatToString(contactPoint.inversePenetrationMass)); + // If we do not solve the friction raints at the center of the contact manifold + //Log.warning("}}}} this.isSolveFrictionAtContactManifoldCenterActive=" + this.isSolveFrictionAtContactManifoldCenterActive); + if (!this.isSolveFrictionAtContactManifoldCenterActive) { + // Compute the friction vectors + computeFrictionVectors(deltaV, contactPoint); + //Log.warning("}}}} contactPoint.frictionVector1=" + contactPoint.frictionVector1); + contactPoint.r1CrossT1 = contactPoint.r1.cross(contactPoint.frictionVector1); + contactPoint.r1CrossT2 = contactPoint.r1.cross(contactPoint.frictionvec2); + contactPoint.r2CrossT1 = contactPoint.r2.cross(contactPoint.frictionVector1); + contactPoint.r2CrossT2 = contactPoint.r2.cross(contactPoint.frictionvec2); + //Log.warning("}}}} contactPoint.r1CrossT1=" + contactPoint.r1CrossT1); + //Log.warning("}}}} contactPoint.r1CrossT2=" + contactPoint.r1CrossT2); + //Log.warning("}}}} contactPoint.r2CrossT1=" + contactPoint.r2CrossT1); + //Log.warning("}}}} contactPoint.r2CrossT2=" + contactPoint.r2CrossT2); + // Compute the inverse mass matrix K for the friction + // raints at each contact point + final float friction1Mass = manifold.massInverseBody1 + manifold.massInverseBody2 + + ((I1.multiplyNew(contactPoint.r1CrossT1)).cross(contactPoint.r1)).dot(contactPoint.frictionVector1) + + ((I2.multiplyNew(contactPoint.r2CrossT1)).cross(contactPoint.r2)).dot(contactPoint.frictionVector1); + final float friction2Mass = manifold.massInverseBody1 + manifold.massInverseBody2 + ((I1.multiplyNew(contactPoint.r1CrossT2)).cross(contactPoint.r1)).dot(contactPoint.frictionvec2) + + ((I2.multiplyNew(contactPoint.r2CrossT2)).cross(contactPoint.r2)).dot(contactPoint.frictionvec2); + if (friction1Mass > 0.0f) { + contactPoint.inverseFriction1Mass = 1.0f / friction1Mass; + } + if (friction2Mass > 0.0f) { + contactPoint.inverseFriction2Mass = 1.0f / friction2Mass; + } + } + // Compute the restitution velocity bias "b". We compute this here instead + // of inside the solve() method because we need to use the velocity difference + // at the beginning of the contact. Note that if it is a resting contact (normal + // velocity bellow a given threshold), we do not add a restitution velocity bias + contactPoint.restitutionBias = 0.0f; + //Log.warning("}}}}+++++++++++++++++++++ contactPoint.restitutionBias=" + contactPoint.restitutionBias); + final float deltaVDotN = deltaV.dot(contactPoint.normal); + //Log.warning("}}}}+++++++++++++++++++++ deltaV=" + deltaV); + //Log.warning("}}}}+++++++++++++++++++++ contactPoint.normal=" + contactPoint.normal); + //Log.warning("}}}}+++++++++++++++++++++ deltaVDotN=" + FMath.floatToString(deltaVDotN)); + //Log.warning("}}}}+++++++++++++++++++++ Configuration.RESTITUTION_VELOCITY_THRESHOLD=" + FMath.floatToString(Configuration.RESTITUTION_VELOCITY_THRESHOLD)); + //Log.warning("}}}}+++++++++++++++++++++ manifold.restitutionFactor=" + FMath.floatToString(manifold.restitutionFactor)); + if (deltaVDotN < -Configuration.RESTITUTION_VELOCITY_THRESHOLD) { + contactPoint.restitutionBias = manifold.restitutionFactor * deltaVDotN; + //Log.warning("}}}}+++++++++++++++++++++ contactPoint.restitutionBias=" + FMath.floatToString(contactPoint.restitutionBias)); + } + // If the warm starting of the contact solver is active + if (this.isWarmStartingActive) { + // Get the cached accumulated impulses from the previous step + contactPoint.penetrationImpulse = externalContact.getPenetrationImpulse(); + contactPoint.friction1Impulse = externalContact.getFrictionImpulse1(); + contactPoint.friction2Impulse = externalContact.getFrictionImpulse2(); + contactPoint.rollingResistanceImpulse = externalContact.getRollingResistanceImpulse(); + } + // Initialize the split impulses to zero + contactPoint.penetrationSplitImpulse = 0.0f; + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + //Log.error("]]] contactPoint.normal=" + contactPoint.normal); + manifold.normal.add(contactPoint.normal); + //Log.error("]]] manifold.normal=" + manifold.normal); + } + } + // Compute the inverse K matrix for the rolling resistance raint + manifold.inverseRollingResistance.setZero(); + if (manifold.rollingResistanceFactor > 0 && (manifold.isBody1DynamicType || manifold.isBody2DynamicType)) { + manifold.inverseRollingResistance = manifold.inverseInertiaTensorBody1.addNew(manifold.inverseInertiaTensorBody2); + manifold.inverseRollingResistance = manifold.inverseRollingResistance.inverseNew(); + } + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + //Log.error("]]] manifold.normal=" + manifold.normal); + manifold.normal.normalize(); + //Log.error("]]] manifold.normal=" + manifold.normal); + final Vector3f deltaVFrictionPoint = v2.addNew(w2.cross(manifold.r2Friction)).less(v1).less(w1.cross(manifold.r1Friction)); + //Log.error("]]] deltaVFrictionPoint=" + deltaVFrictionPoint); + // Compute the friction vectors + computeFrictionVectors(deltaVFrictionPoint, manifold); + // Compute the inverse mass matrix K for the friction raints at the center of + // the contact manifold + manifold.r1CrossT1 = manifold.r1Friction.cross(manifold.frictionVector1); + manifold.r1CrossT2 = manifold.r1Friction.cross(manifold.frictionvec2); + manifold.r2CrossT1 = manifold.r2Friction.cross(manifold.frictionVector1); + manifold.r2CrossT2 = manifold.r2Friction.cross(manifold.frictionvec2); + //Log.warning("}}}} manifold.r1CrossT1=" + manifold.r1CrossT1); + //Log.warning("}}}} manifold.r1CrossT2=" + manifold.r1CrossT2); + //Log.warning("}}}} manifold.r2CrossT1=" + manifold.r2CrossT1); + //Log.warning("}}}} manifold.r2CrossT2=" + manifold.r2CrossT2); + final float friction1Mass = manifold.massInverseBody1 + manifold.massInverseBody2 + ((I1.multiplyNew(manifold.r1CrossT1)).cross(manifold.r1Friction)).dot(manifold.frictionVector1) + + ((I2.multiplyNew(manifold.r2CrossT1)).cross(manifold.r2Friction)).dot(manifold.frictionVector1); + //Log.error("]]] friction1Mass=" + FMath.floatToString(friction1Mass)); + final float friction2Mass = manifold.massInverseBody1 + manifold.massInverseBody2 + ((I1.multiplyNew(manifold.r1CrossT2)).cross(manifold.r1Friction)).dot(manifold.frictionvec2) + + ((I2.multiplyNew(manifold.r2CrossT2)).cross(manifold.r2Friction)).dot(manifold.frictionvec2); + //Log.error("]]] friction2Mass=" + FMath.floatToString(friction2Mass)); + final float frictionTwistMass = manifold.normal.dot(manifold.inverseInertiaTensorBody1.multiplyNew(manifold.normal)) + + manifold.normal.dot(manifold.inverseInertiaTensorBody2.multiplyNew(manifold.normal)); + //Log.error("]]] frictionTwistMass=" + FMath.floatToString(frictionTwistMass)); + if (friction1Mass > 0.0f) { + manifold.inverseFriction1Mass = 1.0f / friction1Mass; + //Log.error("]]] manifold.inverseFriction1Mass=" + FMath.floatToString(manifold.inverseFriction1Mass)); + } + if (friction2Mass > 0.0f) { + manifold.inverseFriction2Mass = 1.0f / friction2Mass; + //Log.error("]]] manifold.inverseFriction2Mass=" + FMath.floatToString(manifold.inverseFriction2Mass)); + } + if (frictionTwistMass > 0.0f) { + manifold.inverseTwistFrictionMass = 1.0f / frictionTwistMass; + //Log.error("]]] manifold.inverseTwistFrictionMass=" + FMath.floatToString(manifold.inverseTwistFrictionMass)); + } + } + } + } + + /** + * Initialize the raint solver for a given island + * @param _dt Delta step time + * @param _island Island list property + */ + public void initializeForIsland(final float _dt, final Island _island) { + assert (_island != null); + assert (_island.getNbBodies() > 0); + assert (_island.getNbContactManifolds() > 0); + assert (this.splitLinearVelocities != null); + assert (this.splitAngularVelocities != null); + // Set the current time step + // TODO: optimize this... this is so ugly ... + this.timeStep = _dt; + { + this.contactConstraints = new ArrayList<>(_island.getNbContactManifolds()); + } + // this.contactConstraints.resize(_island.getNbContactManifolds()); + // For each contact manifold of the island + final List contactManifolds = _island.getContactManifold(); + for (int iii = 0; iii < _island.getNbContactManifolds(); ++iii) { + final ContactManifold externalManifold = contactManifolds.get(iii); + final ContactManifoldSolver internalManifold = new ContactManifoldSolver(); // TODO Note no real need to reallocate a new one ==> maybe reuse (done in C++ not in java ...) + this.contactConstraints.add(internalManifold); + + assert (externalManifold.getNbContactPoints() > 0); + // Get the two bodies of the contact + final RigidBody body1 = (RigidBody) (externalManifold.getContactPoint(0).getBody1()); + final RigidBody body2 = (RigidBody) (externalManifold.getContactPoint(0).getBody2()); + assert (body1 != null); + assert (body2 != null); + // Get the position of the two bodies + final Vector3f x1 = body1.centerOfMassWorld; + final Vector3f x2 = body2.centerOfMassWorld; + // Initialize the internal contact manifold structure using the external + // contact manifold + internalManifold.indexBody1 = this.mapBodyToConstrainedVelocityIndex.get(body1); + internalManifold.indexBody2 = this.mapBodyToConstrainedVelocityIndex.get(body2); + internalManifold.inverseInertiaTensorBody1 = body1.getInertiaTensorInverseWorld(); + internalManifold.inverseInertiaTensorBody2 = body2.getInertiaTensorInverseWorld(); + internalManifold.massInverseBody1 = body1.massInverse; + internalManifold.massInverseBody2 = body2.massInverse; + internalManifold.nbContacts = externalManifold.getNbContactPoints(); + internalManifold.restitutionFactor = computeMixedRestitutionFactor(body1, body2); + internalManifold.frictionCoefficient = computeMixedFrictionCoefficient(body1, body2); + internalManifold.rollingResistanceFactor = computeMixedRollingResistance(body1, body2); + internalManifold.externalContactManifold = externalManifold; + internalManifold.isBody1DynamicType = body1.getType() == BodyType.DYNAMIC; + internalManifold.isBody2DynamicType = body2.getType() == BodyType.DYNAMIC; + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1 = new Vector3f(0.0f, 0.0f, 0.0f); + internalManifold.frictionPointBody2 = new Vector3f(0.0f, 0.0f, 0.0f); + //Log.error("]]] internalManifold.frictionPointBody1=" + internalManifold.frictionPointBody1); + //Log.error("]]] internalManifold.frictionPointBody2=" + internalManifold.frictionPointBody2); + } + // For each contact point of the contact manifold + for (int ccc = 0; ccc < externalManifold.getNbContactPoints(); ++ccc) { + final ContactPointSolver contactPoint = internalManifold.contacts[ccc]; + // Get a contact point + final ContactPoint externalContact = externalManifold.getContactPoint(ccc); + // Get the contact point on the two bodies + final Vector3f p1 = externalContact.getWorldPointOnBody1(); + final Vector3f p2 = externalContact.getWorldPointOnBody2(); + contactPoint.externalContact = externalContact; + contactPoint.normal = externalContact.getNormal(); + contactPoint.r1 = p1.lessNew(x1); + contactPoint.r2 = p2.lessNew(x2); + contactPoint.penetrationDepth = externalContact.getPenetrationDepth(); + contactPoint.isRestingContact = externalContact.getIsRestingContact(); + externalContact.setIsRestingContact(true); + contactPoint.oldFrictionVector1 = externalContact.getFrictionVector1().clone(); + contactPoint.oldFrictionvec2 = externalContact.getFrictionvec2().clone(); + contactPoint.penetrationImpulse = 0.0f; + contactPoint.friction1Impulse = 0.0f; + contactPoint.friction2Impulse = 0.0f; + contactPoint.rollingResistanceImpulse = new Vector3f(0.0f, 0.0f, 0.0f); + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1.add(p1); + internalManifold.frictionPointBody2.add(p2); + //Log.error("]]] internalManifold.frictionPointBody1=" + internalManifold.frictionPointBody1); + //Log.error("]]] internalManifold.frictionPointBody2=" + internalManifold.frictionPointBody2); + } + } + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1.divide(internalManifold.nbContacts); + internalManifold.frictionPointBody2.divide(internalManifold.nbContacts); + //Log.error("]]] internalManifold.frictionPointBody1=" + internalManifold.frictionPointBody1); + //Log.error("]]] internalManifold.frictionPointBody2=" + internalManifold.frictionPointBody2); + internalManifold.r1Friction = internalManifold.frictionPointBody1.lessNew(x1); + internalManifold.r2Friction = internalManifold.frictionPointBody2.lessNew(x2); + //Log.error("]]] internalManifold.r1Friction=" + internalManifold.r1Friction); + //Log.error("]]] internalManifold.r2Friction=" + internalManifold.r2Friction); + internalManifold.oldFrictionVector1 = externalManifold.getFrictionVector1().clone(); + internalManifold.oldFrictionvec2 = externalManifold.getFrictionvec2().clone(); + //Log.error("]]] internalManifold.oldFrictionVector1=" + internalManifold.oldFrictionVector1); + //Log.error("]]] internalManifold.oldFrictionvec2=" + internalManifold.oldFrictionvec2); + // If warm starting is active + if (this.isWarmStartingActive) { + // Initialize the accumulated impulses with the previous step accumulated impulses + internalManifold.friction1Impulse = externalManifold.getFrictionImpulse1(); + internalManifold.friction2Impulse = externalManifold.getFrictionImpulse2(); + internalManifold.frictionTwistImpulse = externalManifold.getFrictionTwistImpulse(); + //Log.error("]]] internalManifold.friction1Impulse=" + FMath.floatToString(internalManifold.friction1Impulse)); + //Log.error("]]] internalManifold.friction2Impulse=" + FMath.floatToString(internalManifold.friction2Impulse)); + //Log.error("]]] internalManifold.frictionTwistImpulse=" + FMath.floatToString(internalManifold.frictionTwistImpulse)); + } else { + // Initialize the accumulated impulses to zero + internalManifold.friction1Impulse = 0.0f; + internalManifold.friction2Impulse = 0.0f; + internalManifold.frictionTwistImpulse = 0.0f; + internalManifold.rollingResistanceImpulse = new Vector3f(0, 0, 0); + } + } + } + // Fill-in all the matrices needed to solve the LCP problem + initializeContactConstraints(); + } + + /** + * Get the split impulses position correction technique is used for contacts + * @return true the split status is Enable + * @return true the split status is Disable + */ + public boolean isSplitImpulseActive() { + return this.isSplitImpulseActive; + } + + /** + * Set the rained velocities arrays + * @param _rainedLinearVelocities Constrained Linear velocities Table pointer (not free) + * @param _rainedAngularVelocities Constrained angular velocities Table pointer (not free) + */ + public void setConstrainedVelocitiesArrays(final Vector3f[] _rainedLinearVelocities, final Vector3f[] _rainedAngularVelocities) { + assert (_rainedLinearVelocities != null); + assert (_rainedAngularVelocities != null); + this.linearVelocities = _rainedLinearVelocities; + this.angularVelocities = _rainedAngularVelocities; + } + + /** + * Activate or deactivate the solving of friction raints at the center of + * the contact manifold instead of solving them at each contact point + * @param _isActive Enable or not the center inertie + */ + public void setIsSolveFrictionAtContactManifoldCenterActive(final boolean _isActive) { + this.isSolveFrictionAtContactManifoldCenterActive = _isActive; + } + + /** + * Activate or Deactivate the split impulses for contacts + * @param _isActive status to set. + */ + public void setIsSplitImpulseActive(final boolean _isActive) { + this.isSplitImpulseActive = _isActive; + } + + /** + * Set the split velocities arrays + * @param _splitLinearVelocities Split linear velocities Table pointer (not free) + * @param _splitAngularVelocities Split angular velocities Table pointer (not free) + */ + public void setSplitVelocitiesArrays(final Vector3f[] _splitLinearVelocities, final Vector3f[] _splitAngularVelocities) { + assert (_splitLinearVelocities != null); + assert (_splitAngularVelocities != null); + this.splitLinearVelocities = _splitLinearVelocities; + this.splitAngularVelocities = _splitAngularVelocities; + } + + /** + * Solve the contacts + */ + public void solve() { + //Log.warning("========================================"); + //Log.warning("== Contact SOLVER ... " + this.contactConstraints.size()); + //Log.warning("========================================"); + // For each contact manifold + for (int ccc = 0; ccc < this.contactConstraints.size(); ++ccc) { + //Log.warning("ccc=" + ccc + " / " + this.contactConstraints.size()); + final ContactManifoldSolver contactManifold = this.contactConstraints.get(ccc); + float sumPenetrationImpulse = 0.0f; + // Get the rained velocities + final Vector3f v1 = this.linearVelocities[contactManifold.indexBody1]; + final Vector3f w1 = this.angularVelocities[contactManifold.indexBody1]; + final Vector3f v2 = this.linearVelocities[contactManifold.indexBody2]; + final Vector3f w2 = this.angularVelocities[contactManifold.indexBody2]; + //Log.warning(" v1=" + v1); + //Log.warning(" w1=" + w1); + //Log.warning(" v2=" + v2); + //Log.warning(" w2=" + w2); + for (int iii = 0; iii < contactManifold.nbContacts; ++iii) { + //Log.warning(" iii=" + iii); + final ContactPointSolver contactPoint = contactManifold.contacts[iii]; + // --------- Penetration --------- // + // Compute J*v + Vector3f deltaV = v2.addNew(w2.cross(contactPoint.r2)).less(v1).less(w1.cross(contactPoint.r1)); + final float deltaVDotN = deltaV.dot(contactPoint.normal); + //Log.warning(" contactPoint.R1=" + contactPoint.r1); + //Log.warning(" contactPoint.R2=" + contactPoint.r2); + //Log.warning(" contactPoint.r1CrossN=" + contactPoint.r1CrossN); + //Log.warning(" contactPoint.r2CrossN=" + contactPoint.r2CrossN); + //Log.warning(" contactPoint.r1CrossT1=" + contactPoint.r1CrossT1); + //Log.warning(" contactPoint.r2CrossT1=" + contactPoint.r2CrossT1); + //Log.warning(" contactPoint.r1CrossT2=" + contactPoint.r1CrossT2); + //Log.warning(" contactPoint.r2CrossT2=" + contactPoint.r2CrossT2); + //Log.warning(" contactPoint.penetrationDepth=" + FMath.floatToString(contactPoint.penetrationDepth)); + //Log.warning(" contactPoint.normal=" + contactPoint.normal); + //Log.warning(" deltaV=" + deltaV); + float Jv = deltaVDotN; + // Compute the bias "b" of the raint + final float beta = this.isSplitImpulseActive ? BETA_SPLIT_IMPULSE : BETA; + //Log.warning(" beta=" + FMath.floatToString(beta)); + //Log.warning(" BETA=" + FMath.floatToString(BETA)); + //Log.warning(" SLOP=" + FMath.floatToString(SLOP)); + //Log.warning(" this.timeStep=" + FMath.floatToString(this.timeStep)); + //Log.warning(" contactPoint.restitutionBias=" + FMath.floatToString(contactPoint.restitutionBias)); + float biasPenetrationDepth = 0.0f; + if (contactPoint.penetrationDepth > SLOP) { + //Log.warning(" (beta / this.timeStep)=" + FMath.floatToString((beta / this.timeStep))); + //Log.warning(" FMath.max(0.0f, contactPoint.penetrationDepth - SLOP)=" + FMath.floatToString(FMath.max(0.0f, contactPoint.penetrationDepth - SLOP))); + biasPenetrationDepth = -(beta / this.timeStep) * FMath.max(0.0f, contactPoint.penetrationDepth - SLOP); + //Log.warning(" biasPenetrationDepth=" + FMath.floatToString(biasPenetrationDepth)); + } + //Log.warning(" contactPoint.restitutionBias=" + FMath.floatToString(contactPoint.restitutionBias)); + //Log.warning(" biasPenetrationDepth=" + FMath.floatToString(biasPenetrationDepth)); + final float b = biasPenetrationDepth + contactPoint.restitutionBias; + //Log.warning(" b=" + FMath.floatToString(b)); + // Compute the Lagrange multiplier lambda + float deltaLambda; + if (this.isSplitImpulseActive) { + deltaLambda = -(Jv + contactPoint.restitutionBias) * contactPoint.inversePenetrationMass; + } else { + deltaLambda = -(Jv + b) * contactPoint.inversePenetrationMass; + } + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + float lambdaTemp = contactPoint.penetrationImpulse; + contactPoint.penetrationImpulse = FMath.max(contactPoint.penetrationImpulse + deltaLambda, 0.0f); + deltaLambda = contactPoint.penetrationImpulse - lambdaTemp; + //Log.warning(" contactPoint.penetrationImpulse=" + FMath.floatToString(contactPoint.penetrationImpulse)); + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + // Compute the impulse P=J^T * lambda + final Impulse impulsePenetration = computePenetrationImpulse(deltaLambda, contactPoint); + //Log.warning(" impulsePenetration.a1=" + impulsePenetration.angularImpulseBody1); + //Log.warning(" impulsePenetration.a2=" + impulsePenetration.angularImpulseBody2); + //Log.warning(" impulsePenetration.i1=" + impulsePenetration.linearImpulseBody1); + //Log.warning(" impulsePenetration.i2=" + impulsePenetration.linearImpulseBody2); + // Apply the impulse to the bodies of the raint + applyImpulse(impulsePenetration, contactManifold); + sumPenetrationImpulse += contactPoint.penetrationImpulse; + //Log.warning(" sumpenetrationImpulse=" + FMath.floatToString(sumPenetrationImpulse)); + //Log.warning(" isSplitImpulseActive=" + this.isSplitImpulseActive); + //Log.warning(" isSolveFrictionAtContactManifoldCenterActive=" + this.isSolveFrictionAtContactManifoldCenterActive); + // If the split impulse position correction is active + if (this.isSplitImpulseActive) { + // Split impulse (position correction) + final Vector3f v1Split = this.splitLinearVelocities[contactManifold.indexBody1]; + final Vector3f w1Split = this.splitAngularVelocities[contactManifold.indexBody1]; + final Vector3f v2Split = this.splitLinearVelocities[contactManifold.indexBody2]; + final Vector3f w2Split = this.splitAngularVelocities[contactManifold.indexBody2]; + final Vector3f deltaVSplit = v2Split.addNew(w2Split.cross(contactPoint.r2)).less(v1Split).less(w1Split.cross(contactPoint.r1)); + //Log.warning(" deltaVSplit=" + deltaVSplit); + final float JvSplit = deltaVSplit.dot(contactPoint.normal); + final float deltaLambdaSplit = -(JvSplit + biasPenetrationDepth) * contactPoint.inversePenetrationMass; + final float lambdaTempSplit = contactPoint.penetrationSplitImpulse; + contactPoint.penetrationSplitImpulse = FMath.max(contactPoint.penetrationSplitImpulse + deltaLambdaSplit, 0.0f); + deltaLambda = contactPoint.penetrationSplitImpulse - lambdaTempSplit; + // Compute the impulse P=J^T * lambda + final Impulse splitImpulsePenetration = computePenetrationImpulse(deltaLambdaSplit, contactPoint); + //Log.warning(" splitImpulsePenetration.a1=" + splitImpulsePenetration.angularImpulseBody1); + //Log.warning(" splitImpulsePenetration.a2=" + splitImpulsePenetration.angularImpulseBody2); + //Log.warning(" splitImpulsePenetration.i1=" + splitImpulsePenetration.linearImpulseBody1); + //Log.warning(" splitImpulsePenetration.i2=" + splitImpulsePenetration.linearImpulseBody2); + applySplitImpulse(splitImpulsePenetration, contactManifold); + } + // If we do not solve the friction raints at the center of the contact manifold + if (!this.isSolveFrictionAtContactManifoldCenterActive) { + // --------- Friction 1 --------- // + // Compute J*v + deltaV = v2.addNew(w2.cross(contactPoint.r2)).less(v1).less(w1.cross(contactPoint.r1)); + Jv = deltaV.dot(contactPoint.frictionVector1); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv; + deltaLambda *= contactPoint.inverseFriction1Mass; + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + float frictionLimit = contactManifold.frictionCoefficient * contactPoint.penetrationImpulse; + lambdaTemp = contactPoint.friction1Impulse; + contactPoint.friction1Impulse = FMath.max(-frictionLimit, FMath.min(contactPoint.friction1Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactPoint.friction1Impulse - lambdaTemp; + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + // Compute the impulse P=J^T * lambda + final Impulse impulseFriction1 = computeFriction1Impulse(deltaLambda, contactPoint); + + //Log.warning(" impulseFriction1.a1=" + impulseFriction1.angularImpulseBody1); + //Log.warning(" impulseFriction1.a2=" + impulseFriction1.angularImpulseBody2); + //Log.warning(" impulseFriction1.i1=" + impulseFriction1.linearImpulseBody1); + //Log.warning(" impulseFriction1.i2=" + impulseFriction1.linearImpulseBody2); + + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction1, contactManifold); + // --------- Friction 2 --------- // + // Compute J*v + deltaV = v2.addNew(w2.cross(contactPoint.r2)).less(v1).less(w1.cross(contactPoint.r1)); + Jv = deltaV.dot(contactPoint.frictionvec2); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv; + deltaLambda *= contactPoint.inverseFriction2Mass; + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + frictionLimit = contactManifold.frictionCoefficient * contactPoint.penetrationImpulse; + lambdaTemp = contactPoint.friction2Impulse; + contactPoint.friction2Impulse = FMath.max(-frictionLimit, FMath.min(contactPoint.friction2Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactPoint.friction2Impulse - lambdaTemp; + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + // Compute the impulse P=J^T * lambda + final Impulse impulseFriction2 = computeFriction2Impulse(deltaLambda, contactPoint); + //Log.warning(" impulseFriction2.a1=" + impulseFriction2.angularImpulseBody1); + //Log.warning(" impulseFriction2.a2=" + impulseFriction2.angularImpulseBody2); + //Log.warning(" impulseFriction2.i1=" + impulseFriction2.linearImpulseBody1); + //Log.warning(" impulseFriction2.i2=" + impulseFriction2.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction2, contactManifold); + // --------- Rolling resistance raint --------- // + if (contactManifold.rollingResistanceFactor > 0) { + // Compute J*v + final Vector3f JvRolling = w2.lessNew(w1); + // Compute the Lagrange multiplier lambda + Vector3f deltaLambdaRolling = contactManifold.inverseRollingResistance.multiplyNew((JvRolling.multiplyNew(-1))); + final float rollingLimit = contactManifold.rollingResistanceFactor * contactPoint.penetrationImpulse; + final Vector3f lambdaTempRolling = contactPoint.rollingResistanceImpulse; + contactPoint.rollingResistanceImpulse = contactPoint.rollingResistanceImpulse.addNew(deltaLambdaRolling).clampNew(rollingLimit); + deltaLambdaRolling = contactPoint.rollingResistanceImpulse.lessNew(lambdaTempRolling); + // Compute the impulse P=J^T * lambda + final Impulse impulseRolling = new Impulse(new Vector3f(0.0f, 0.0f, 0.0f), deltaLambdaRolling.multiplyNew(-1), new Vector3f(0.0f, 0.0f, 0.0f), deltaLambdaRolling); + //Log.warning(" impulseRolling.a1=" + impulseRolling.angularImpulseBody1); + //Log.warning(" impulseRolling.a2=" + impulseRolling.angularImpulseBody2); + //Log.warning(" impulseRolling.i1=" + impulseRolling.linearImpulseBody1); + //Log.warning(" impulseRolling.i2=" + impulseRolling.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRolling, contactManifold); + } + } + } + //Log.info(" w1=" + w1); + //Log.info(" w2=" + w2); + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + //Log.warning(" HHH isSolveFrictionAtContactManifoldCenterActive"); + //Log.info(" v2=" + v2); + + // ------ First friction raint at the center of the contact manifol ------ // + // Compute J*v + Vector3f deltaV = v2.addNew(w2.cross(contactManifold.r2Friction)).less(v1).less(w1.cross(contactManifold.r1Friction)); + float Jv = deltaV.dot(contactManifold.frictionVector1); + //Log.info(" v2=" + v2); + //Log.warning(" Jv=" + FMath.floatToString(Jv)); + //Log.warning(" contactManifold.frictionVector1=" + contactManifold.frictionVector1); + //Log.warning(" contactManifold.inverseFriction1Mass=" + FMath.floatToString(contactManifold.inverseFriction1Mass)); + // Compute the Lagrange multiplier lambda + float deltaLambda = -Jv * contactManifold.inverseFriction1Mass; + //Log.warning(" contactManifold.frictionCoefficient=" + FMath.floatToString(contactManifold.frictionCoefficient)); + //Log.warning(" sumPenetrationImpulse=" + FMath.floatToString(sumPenetrationImpulse)); + //Log.info(" v2=" + v2); + float frictionLimit = contactManifold.frictionCoefficient * sumPenetrationImpulse; + float lambdaTemp = contactManifold.friction1Impulse; + //Log.warning(" frictionLimit=" + FMath.floatToString(frictionLimit)); + //Log.warning(" lambdaTemp=" + FMath.floatToString(lambdaTemp)); + //Log.info(" v2=" + v2); + contactManifold.friction1Impulse = FMath.max(-frictionLimit, FMath.min(contactManifold.friction1Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactManifold.friction1Impulse - lambdaTemp; + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + // Compute the impulse P=J^T * lambda + Vector3f linearImpulseBody1 = contactManifold.frictionVector1.multiplyNew(-1).multiply(deltaLambda); + Vector3f angularImpulseBody1 = contactManifold.r1CrossT1.multiplyNew(-1).multiply(deltaLambda); + Vector3f linearImpulseBody2 = contactManifold.frictionVector1.multiplyNew(deltaLambda); + Vector3f angularImpulseBody2 = contactManifold.r2CrossT1.multiplyNew(deltaLambda); + + //Log.info(" v2=" + v2); + final Impulse impulseFriction1 = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + //Log.warning(" impulseFriction1.a1=" + impulseFriction1.angularImpulseBody1); + //Log.warning(" impulseFriction1.a2=" + impulseFriction1.angularImpulseBody2); + //Log.warning(" impulseFriction1.i1=" + impulseFriction1.linearImpulseBody1); + //Log.warning(" impulseFriction1.i2=" + impulseFriction1.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction1, contactManifold); + //Log.info(" v2=" + v2); + //Log.info(" w2=" + w2); + //Log.info(" v1=" + v1); + //Log.info(" w1=" + w1); + //Log.info(" contactManifold.r2Friction=" + contactManifold.r2Friction); + //Log.info(" contactManifold.r1Friction=" + contactManifold.r1Friction); + // ------ Second friction raint at the center of the contact manifol ----- // + // Compute J*v + deltaV = v2.addNew(w2.cross(contactManifold.r2Friction)).less(v1).less(w1.cross(contactManifold.r1Friction)); + //Log.warning(">>>> deltaV=" + deltaV); + Jv = deltaV.dot(contactManifold.frictionvec2); + //Log.warning(">>>> Jv=" + FMath.floatToString(Jv)); + //Log.warning(">>>> contactManifold.inverseFriction2Mass=" + FMath.floatToString(contactManifold.inverseFriction2Mass)); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv * contactManifold.inverseFriction2Mass; + //Log.warning(">>>> deltaLambda=" + FMath.floatToString(deltaLambda)); + frictionLimit = contactManifold.frictionCoefficient * sumPenetrationImpulse; + lambdaTemp = contactManifold.friction2Impulse; + //Log.warning(">>>> lambdaTemp=" + FMath.floatToString(lambdaTemp)); + //Log.warning(">>>> contactManifold.friction2Impulse=" + FMath.floatToString(contactManifold.friction2Impulse)); + //Log.warning(">>>>** frictionLimit=" + FMath.floatToString(frictionLimit)); + contactManifold.friction2Impulse = FMath.max(-frictionLimit, FMath.min(contactManifold.friction2Impulse + deltaLambda, frictionLimit)); + //Log.warning(">>>> contactManifold.friction2Impulse=" + FMath.floatToString(contactManifold.friction2Impulse)); + deltaLambda = contactManifold.friction2Impulse - lambdaTemp; + //Log.warning(">>>> deltaLambda=" + FMath.floatToString(deltaLambda)); + // Compute the impulse P=J^T * lambda + linearImpulseBody1 = contactManifold.frictionvec2.multiplyNew(-deltaLambda); + angularImpulseBody1 = contactManifold.r1CrossT2.multiplyNew(-deltaLambda); + linearImpulseBody2 = contactManifold.frictionvec2.multiplyNew(deltaLambda); + angularImpulseBody2 = contactManifold.r2CrossT2.multiplyNew(deltaLambda); + + final Impulse impulseFriction2 = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + //Log.warning(" impulseFriction2.a1=" + impulseFriction2.angularImpulseBody1); + //Log.warning(" impulseFriction2.a2=" + impulseFriction2.angularImpulseBody2); + //Log.warning(" impulseFriction2.i1=" + impulseFriction2.linearImpulseBody1); + //Log.warning(" impulseFriction2.i2=" + impulseFriction2.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction2, contactManifold); + //Log.info(" v2=" + v2); + //Log.info(" w2=" + w2); + //Log.info(" v1=" + v1); + //Log.info(" w1=" + w1); + // ------ Twist friction raint at the center of the contact manifol ------ // + // Compute J*v + deltaV = w2.lessNew(w1); + //Log.warning(" deltaV=" + deltaV); + //Log.warning(" contactManifold.normal=" + contactManifold.normal); + Jv = deltaV.dot(contactManifold.normal); + //Log.warning(" Jv=" + FMath.floatToString(Jv)); + //Log.warning(" contactManifold.inverseTwistFrictionMass=" + contactManifold.inverseTwistFrictionMass); + deltaLambda = -Jv * (contactManifold.inverseTwistFrictionMass); + //Log.warning(" deltaLambda=" + FMath.floatToString(deltaLambda)); + frictionLimit = contactManifold.frictionCoefficient * sumPenetrationImpulse; + //Log.warning(" contactManifold.frictionCoefficient=" + contactManifold.frictionCoefficient); + //Log.warning(" sumPenetrationImpulse=" + sumPenetrationImpulse); + //Log.warning(" frictionLimit=" + FMath.floatToString(frictionLimit)); + lambdaTemp = contactManifold.frictionTwistImpulse; + //Log.warning(" lambdaTemp=" + lambdaTemp); + contactManifold.frictionTwistImpulse = FMath.max(-frictionLimit, FMath.min(contactManifold.frictionTwistImpulse + deltaLambda, frictionLimit)); + //Log.warning(" contactManifold.frictionTwistImpulse=" + contactManifold.frictionTwistImpulse); + deltaLambda = contactManifold.frictionTwistImpulse - lambdaTemp; + //Log.warning(" deltaLambda=" + deltaLambda); + // Compute the impulse P=J^T * lambda + linearImpulseBody1 = new Vector3f(0.0f, 0.0f, 0.0f); + angularImpulseBody1 = contactManifold.normal.multiplyNew(-deltaLambda); + linearImpulseBody2 = new Vector3f(0.0f, 0.0f, 0.0f); + angularImpulseBody2 = contactManifold.normal.multiplyNew(deltaLambda); + + final Impulse impulseTwistFriction = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + //Log.warning(" impulseTwistFriction.a1=" + impulseTwistFriction.angularImpulseBody1); + //Log.warning(" impulseTwistFriction.a2=" + impulseTwistFriction.angularImpulseBody2); + //Log.warning(" impulseTwistFriction.i1=" + impulseTwistFriction.linearImpulseBody1); + //Log.warning(" impulseTwistFriction.i2=" + impulseTwistFriction.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseTwistFriction, contactManifold); + // --------- Rolling resistance raint at the center of the contact manifold --------- // + if (contactManifold.rollingResistanceFactor > 0) { + // Compute J*v + final Vector3f JvRolling = w2.lessNew(w1); + // Compute the Lagrange multiplier lambda + Vector3f deltaLambdaRolling = contactManifold.inverseRollingResistance.multiplyNew(JvRolling.multiplyNew(-1)); + final float rollingLimit = contactManifold.rollingResistanceFactor * sumPenetrationImpulse; + final Vector3f lambdaTempRolling = contactManifold.rollingResistanceImpulse; + contactManifold.rollingResistanceImpulse = (contactManifold.rollingResistanceImpulse.addNew(deltaLambdaRolling)).clampNew(rollingLimit); + deltaLambdaRolling = contactManifold.rollingResistanceImpulse.lessNew(lambdaTempRolling); + // Compute the impulse P=J^T * lambda + angularImpulseBody1 = deltaLambdaRolling.multiplyNew(-1); + angularImpulseBody2 = deltaLambdaRolling; + + final Impulse impulseRolling = new Impulse(new Vector3f(0.0f, 0.0f, 0.0f), angularImpulseBody1, new Vector3f(0.0f, 0.0f, 0.0f), angularImpulseBody2); + //Log.warning(" impulseRolling.a1=" + impulseRolling.angularImpulseBody1); + //Log.warning(" impulseRolling.a2=" + impulseRolling.angularImpulseBody2); + //Log.warning(" impulseRolling.i1=" + impulseRolling.linearImpulseBody1); + //Log.warning(" impulseRolling.i2=" + impulseRolling.linearImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRolling, contactManifold); + } + } + } + //System.exit(-1); + } + + /** + * Store the computed impulses to use them to warm start the solver at the next iteration + */ + public void storeImpulses() { + // For each contact manifold + for (int ccc = 0; ccc < this.contactConstraints.size(); ++ccc) { + final ContactManifoldSolver manifold = this.contactConstraints.get(ccc); + for (int iii = 0; iii < manifold.nbContacts; ++iii) { + final ContactPointSolver contactPoint = manifold.contacts[iii]; + contactPoint.externalContact.setPenetrationImpulse(contactPoint.penetrationImpulse); + contactPoint.externalContact.setFrictionImpulse1(contactPoint.friction1Impulse); + contactPoint.externalContact.setFrictionImpulse2(contactPoint.friction2Impulse); + contactPoint.externalContact.setRollingResistanceImpulse(contactPoint.rollingResistanceImpulse.clone()); + contactPoint.externalContact.setFrictionVector1(contactPoint.frictionVector1.clone()); + contactPoint.externalContact.setFrictionvec2(contactPoint.frictionvec2.clone()); + } + manifold.externalContactManifold.setFrictionImpulse1(manifold.friction1Impulse); + manifold.externalContactManifold.setFrictionImpulse2(manifold.friction2Impulse); + manifold.externalContactManifold.setFrictionTwistImpulse(manifold.frictionTwistImpulse); + manifold.externalContactManifold.setRollingResistanceImpulse(manifold.rollingResistanceImpulse.clone()); + manifold.externalContactManifold.setFrictionVector1(manifold.frictionVector1.clone()); + manifold.externalContactManifold.setFrictionvec2(manifold.frictionvec2.clone()); + } + } + + /** + * Warm start the solver. + * For each raint, we apply the previous impulse (from the previous step) + * at the beginning. With this technique, we will converge faster towards the solution of the linear system + */ + public void warmStart() { + // Check that warm starting is active + if (!this.isWarmStartingActive) { + return; + } + // For each raint + for (int ccc = 0; ccc < this.contactConstraints.size(); ++ccc) { + final ContactManifoldSolver contactManifold = this.contactConstraints.get(ccc); + boolean atLeastOneRestingContactPoint = false; + for (int iii = 0; iii < contactManifold.nbContacts; ++iii) { + final ContactPointSolver contactPoint = contactManifold.contacts[iii]; + // If it is not a new contact (this contact was already existing at last time step) + if (contactPoint.isRestingContact) { + atLeastOneRestingContactPoint = true; + // --------- Penetration --------- // + // Compute the impulse P = J^T * lambda + final Impulse impulsePenetration = computePenetrationImpulse(contactPoint.penetrationImpulse, contactPoint); + // Apply the impulse to the bodies of the raint + applyImpulse(impulsePenetration, contactManifold); + // If we do not solve the friction raints at the center of the contact manifold + if (!this.isSolveFrictionAtContactManifoldCenterActive) { + // Project the old friction impulses (with old friction vectors) into + // the new friction vectors to get the new friction impulses + final Vector3f oldFrictionImpulse = contactPoint.oldFrictionVector1.multiplyNew(contactPoint.friction1Impulse) + .add(contactPoint.oldFrictionvec2.multiplyNew(contactPoint.friction2Impulse)); + contactPoint.friction1Impulse = oldFrictionImpulse.dot(contactPoint.frictionVector1); + contactPoint.friction2Impulse = oldFrictionImpulse.dot(contactPoint.frictionvec2); + // --------- Friction 1 --------- // + // Compute the impulse P = J^T * lambda + final Impulse impulseFriction1 = computeFriction1Impulse(contactPoint.friction1Impulse, contactPoint); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction1, contactManifold); + // --------- Friction 2 --------- // + // Compute the impulse P=J^T * lambda + final Impulse impulseFriction2 = computeFriction2Impulse(contactPoint.friction2Impulse, contactPoint); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction2, contactManifold); + // ------ Rolling resistance------ // + if (contactManifold.rollingResistanceFactor > 0) { + // Compute the impulse P = J^T * lambda + final Impulse impulseRollingResistance = new Impulse(new Vector3f(0.0f, 0.0f, 0.0f), contactPoint.rollingResistanceImpulse.multiplyNew(-1), new Vector3f(0.0f, 0.0f, 0.0f), + contactPoint.rollingResistanceImpulse); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRollingResistance, contactManifold); + } + } + } else { + // If it is a new contact point + // Initialize the accumulated impulses to zero + contactPoint.penetrationImpulse = 0.0f; + contactPoint.friction1Impulse = 0.0f; + contactPoint.friction2Impulse = 0.0f; + contactPoint.rollingResistanceImpulse = new Vector3f(0.0f, 0.0f, 0.0f); + } + } + // If we solve the friction raints at the center of the contact manifold and there is + // at least one resting contact point in the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive && atLeastOneRestingContactPoint) { + + // Project the old friction impulses (with old friction vectors) into the new friction + // vectors to get the new friction impulses + final Vector3f oldFrictionImpulse = contactManifold.oldFrictionVector1.multiplyNew(contactManifold.friction1Impulse) + .add(contactManifold.oldFrictionvec2.multiplyNew(contactManifold.friction2Impulse)); + + //Log.error("]]] oldFrictionImpulse=" + oldFrictionImpulse); + contactManifold.friction1Impulse = oldFrictionImpulse.dot(contactManifold.frictionVector1); + contactManifold.friction2Impulse = oldFrictionImpulse.dot(contactManifold.frictionvec2); + //Log.error("]]] contactManifold.friction1Impulse=" + contactManifold.friction1Impulse); + //Log.error("]]] contactManifold.friction2Impulse=" + contactManifold.friction2Impulse); + // ------ First friction raint at the center of the contact manifold ------ // + // Compute the impulse P = J^T * lambda + Vector3f linearImpulseBody1 = contactManifold.frictionVector1.multiplyNew(-1).multiply(contactManifold.friction1Impulse); + Vector3f angularImpulseBody1 = contactManifold.r1CrossT1.multiplyNew(-1).multiply(contactManifold.friction1Impulse); + Vector3f linearImpulseBody2 = contactManifold.frictionVector1.multiplyNew(contactManifold.friction1Impulse); + Vector3f angularImpulseBody2 = contactManifold.r2CrossT1.multiplyNew(contactManifold.friction1Impulse); + + //Log.error("]]] linearImpulseBody1=" + linearImpulseBody1); + //Log.error("]]] angularImpulseBody1=" + angularImpulseBody1); + //Log.error("]]] linearImpulseBody2=" + linearImpulseBody2); + //Log.error("]]] angularImpulseBody2=" + angularImpulseBody2); + final Impulse impulseFriction1 = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction1, contactManifold); + // ------ Second friction raint at the center of the contact manifold ----- // + // Compute the impulse P = J^T * lambda + linearImpulseBody1 = contactManifold.frictionvec2.multiplyNew(-1).multiply(contactManifold.friction2Impulse); + angularImpulseBody1 = contactManifold.r1CrossT2.multiplyNew(-1).multiply(contactManifold.friction2Impulse); + linearImpulseBody2 = contactManifold.frictionvec2.multiplyNew(contactManifold.friction2Impulse); + angularImpulseBody2 = contactManifold.r2CrossT2.multiplyNew(contactManifold.friction2Impulse); + + //Log.error("]]] linearImpulseBody1=" + linearImpulseBody1); + //Log.error("]]] angularImpulseBody1=" + angularImpulseBody1); + //Log.error("]]] linearImpulseBody2=" + linearImpulseBody2); + //Log.error("]]] angularImpulseBody2=" + angularImpulseBody2); + final Impulse impulseFriction2 = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction2, contactManifold); + // ------ Twist friction raint at the center of the contact manifold ------ // + // Compute the impulse P = J^T * lambda + linearImpulseBody1 = new Vector3f(0.0f, 0.0f, 0.0f); + angularImpulseBody1 = contactManifold.normal.multiplyNew(contactManifold.frictionTwistImpulse); + linearImpulseBody2 = new Vector3f(0.0f, 0.0f, 0.0f); + angularImpulseBody2 = contactManifold.normal.multiplyNew(contactManifold.frictionTwistImpulse); + + //Log.error("]]] linearImpulseBody1=" + linearImpulseBody1); + //Log.error("]]] angularImpulseBody1=" + angularImpulseBody1); + //Log.error("]]] linearImpulseBody2=" + linearImpulseBody2); + //Log.error("]]] angularImpulseBody2=" + angularImpulseBody2); + final Impulse impulseTwistFriction = new Impulse(linearImpulseBody1, angularImpulseBody1, linearImpulseBody2, angularImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseTwistFriction, contactManifold); + // ------ Rolling resistance at the center of the contact manifold ------ // + // Compute the impulse P = J^T * lambda + angularImpulseBody1 = contactManifold.rollingResistanceImpulse.multiplyNew(-1); + angularImpulseBody2 = contactManifold.rollingResistanceImpulse; + + //Log.error("]]] angularImpulseBody1=" + angularImpulseBody1); + //Log.error("]]] angularImpulseBody2=" + angularImpulseBody2); + final Impulse impulseRollingResistance = new Impulse(new Vector3f(0.0f, 0.0f, 0.0f), angularImpulseBody1, new Vector3f(0.0f, 0.0f, 0.0f), angularImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRollingResistance, contactManifold); + } else + + { + // If it is a new contact manifold + // Initialize the accumulated impulses to zero + contactManifold.friction1Impulse = 0.0f; + contactManifold.friction2Impulse = 0.0f; + contactManifold.frictionTwistImpulse = 0.0f; + contactManifold.rollingResistanceImpulse = new Vector3f(0.0f, 0.0f, 0.0f); + } + } + } +} diff --git a/src/org/atriasoft/ephysics/engine/DynamicsWorld.java b/src/org/atriasoft/ephysics/engine/DynamicsWorld.java new file mode 100644 index 0000000..85937b8 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/DynamicsWorld.java @@ -0,0 +1,1042 @@ +package org.atriasoft.ephysics.engine; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.atriasoft.ephysics.Configuration; +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.body.RigidBody; +import org.atriasoft.ephysics.collision.ContactManifold; +import org.atriasoft.ephysics.collision.ContactManifoldListElement; +import org.atriasoft.ephysics.collision.ContactManifoldSet; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.broadphase.DTree; +import org.atriasoft.ephysics.constraint.BallAndSocketJoint; +import org.atriasoft.ephysics.constraint.BallAndSocketJointInfo; +import org.atriasoft.ephysics.constraint.ContactsPositionCorrectionTechnique; +import org.atriasoft.ephysics.constraint.FixedJoint; +import org.atriasoft.ephysics.constraint.FixedJointInfo; +import org.atriasoft.ephysics.constraint.HingeJoint; +import org.atriasoft.ephysics.constraint.HingeJointInfo; +import org.atriasoft.ephysics.constraint.Joint; +import org.atriasoft.ephysics.constraint.JointInfo; +import org.atriasoft.ephysics.constraint.JointListElement; +import org.atriasoft.ephysics.constraint.JointsPositionCorrectionTechnique; +import org.atriasoft.ephysics.constraint.SliderJoint; +import org.atriasoft.ephysics.constraint.SliderJointInfo; +import org.atriasoft.ephysics.internal.Log; +import org.atriasoft.ephysics.mathematics.Set; +import org.atriasoft.etk.math.FMath; +import org.atriasoft.etk.math.Quaternion; +import org.atriasoft.etk.math.Transform3D; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a dynamics world. This class inherits from + * the CollisionWorld class. In a dynamics world, bodies can collide + * and their movements are simulated using the laws of physics. + */ +public class DynamicsWorld extends CollisionWorld { + private static int kkk = 0; + protected ContactSolver contactSolver; //!< Contact solver + protected ConstraintSolver raintSolver; //!< Constraint solver + protected int nbVelocitySolverIterations; //!< Number of iterations for the velocity solver of the Sequential Impulses technique + protected int nbPositionSolverIterations; //!< Number of iterations for the position solver of the Sequential Impulses technique + protected boolean isSleepingEnabled; //!< True if the spleeping technique for inactive bodies is enabled + protected List rigidBodies = new ArrayList<>(); //!< All the rigid bodies of the physics world + protected List joints = new ArrayList<>(); //!< All the joints of the world + protected Vector3f gravity = new Vector3f(); //!< Gravity vector of the world + public float timeStep; //!< Current frame time step (in seconds) + protected boolean isGravityEnabled; //!< True if the gravity force is on + protected Vector3f[] rainedLinearVelocities; //!< Array of rained linear velocities (state of the linear velocities after solving the constraints) + protected Vector3f[] rainedAngularVelocities; //!< Array of rained angular velocities (state of the angular velocities after solving the constraints) + protected Vector3f[] splitLinearVelocities; //!< Split linear velocities for the position contact solver (split impulse) + protected Vector3f[] splitAngularVelocities; //!< Split angular velocities for the position contact solver (split impulse) + protected Vector3f[] rainedPositions; //!< Array of rained rigid bodies position (for position error correction) + protected Quaternion[] rainedOrientations; //!< Array of rained rigid bodies orientation (for position error correction) + protected Map mapBodyToConstrainedVelocityIndex = new HashMap(); //!< Map body to their index in the rained velocities array + protected List islands = new ArrayList<>(); //!< Array with all the islands of awaken bodies + protected int numberBodiesCapacity; //!< Current allocated capacity for the bodies + + protected float sleepLinearVelocity; //!< Sleep linear velocity threshold + protected float sleepAngularVelocity; //!< Sleep angular velocity threshold + protected float timeBeforeSleep; //!< Time (in seconds) before a body is put to sleep if its velocity becomes smaller than the sleep velocity. + + /** + * Constructor + * @param gravity Gravity vector in the world (in meters per second squared) + */ + public DynamicsWorld(final Vector3f _gravity) { + super(); + this.contactSolver = new ContactSolver(this.mapBodyToConstrainedVelocityIndex); + this.raintSolver = new ConstraintSolver(this.mapBodyToConstrainedVelocityIndex); + this.nbVelocitySolverIterations = Configuration.DEFAULT_VELOCITY_SOLVER_NB_ITERATIONS; + this.nbPositionSolverIterations = Configuration.DEFAULT_POSITION_SOLVER_NB_ITERATIONS; + this.isSleepingEnabled = Configuration.SPLEEPING_ENABLED; + this.gravity = _gravity; + this.isGravityEnabled = true; + this.numberBodiesCapacity = 0; + this.sleepLinearVelocity = Configuration.DEFAULT_SLEEP_LINEAR_VELOCITY; + this.sleepAngularVelocity = Configuration.DEFAULT_SLEEP_ANGULAR_VELOCITY; + this.timeBeforeSleep = Configuration.DEFAULT_TIME_BEFORE_SLEEP; + } + + /** + * Add the joint to the list of joints of the two bodies involved in the joint + * @param[in,out] _joint Joint to add at the body. + */ + protected void addJointToBody(final Joint _joint) { + if (_joint == null) { + //Log.warning("Request add null joint"); + return; + } + // Add the joint at the beginning of the linked list of joints of the first body + _joint.getBody1().jointsList = new JointListElement(_joint, _joint.getBody1().jointsList); + // Add the joint at the beginning of the linked list of joints of the second body + _joint.getBody2().jointsList = new JointListElement(_joint, _joint.getBody2().jointsList); + } + + /** + * Compute the islands of awake bodies. + * An island is an isolated group of rigid bodies that have raints (joints or contacts) + * between each other. This method computes the islands at each time step as follows: For each + * awake rigid body, we run a Depth First Search (DFS) through the raint graph of that body + * (graph where nodes are the bodies and where the edges are the raints between the bodies) to + * find all the bodies that are connected with it (the bodies that share joints or contacts with + * it). Then, we create an island with this group of connected bodies. + */ + protected void computeIslands() { + final int nbBodies = this.rigidBodies.size(); + // Clear all the islands + this.islands.clear(); + int nbContactManifolds = 0; + // Reset all the isAlreadyInIsland variables of bodies, joints and contact manifolds + for (final RigidBody it : this.rigidBodies) { + final int nbBodyManifolds = it.resetIsAlreadyInIslandAndCountManifolds(); + nbContactManifolds += nbBodyManifolds; + } + for (final Joint it : this.joints) { + it.isAlreadyInIsland = false; + } + // Create a stack (using an array) for the rigid bodies to visit during the Depth First Search + final List stackBodiesToVisit = new ArrayList<>(nbBodies); + for (int iii = 0; iii < nbBodies; iii++) { + stackBodiesToVisit.add(null); + } + + // For each rigid body of the world + for (final RigidBody body : this.rigidBodies) { + // If the body has already been added to an island, we go to the next body + if (body.isAlreadyInIsland) { + continue; + } + // If the body is static, we go to the next body + if (body.getType() == BodyType.STATIC) { + continue; + } + // If the body is sleeping or inactive, we go to the next body + if (body.isSleeping() || !body.isActive()) { + continue; + } + // Reset the stack of bodies to visit + int stackIndex = 0; + stackBodiesToVisit.set(stackIndex, body); + stackIndex++; + body.isAlreadyInIsland = true; + // Create the new island + this.islands.add(new Island(nbBodies, nbContactManifolds, this.joints.size())); + // While there are still some bodies to visit in the stack + while (stackIndex > 0) { + // Get the next body to visit from the stack + stackIndex--; + final RigidBody bodyToVisit = stackBodiesToVisit.get(stackIndex); + assert (bodyToVisit.isActive()); + // Awake the body if it is slepping + bodyToVisit.setIsSleeping(false); + // Add the body into the island + this.islands.get(this.islands.size() - 1).addBody(bodyToVisit); + // If the current body is static, we do not want to perform the DFS + // search across that body + if (bodyToVisit.getType() == BodyType.STATIC) { + continue; + } + // For each contact manifold in which the current body is involded + ContactManifoldListElement contactElement; + for (contactElement = bodyToVisit.contactManifoldsList; contactElement != null; contactElement = contactElement.next) { + final ContactManifold contactManifold = contactElement.contactManifold; + assert (contactManifold.getNbContactPoints() > 0); + // Check if the current contact manifold has already been added into an island + if (contactManifold.isAlreadyInIsland()) { + continue; + } + // Add the contact manifold into the island + this.islands.get(this.islands.size() - 1).addContactManifold(contactManifold); + contactManifold.isAlreadyInIsland = true; + // Get the other body of the contact manifold + final RigidBody body1 = (RigidBody) contactManifold.getBody1(); + final RigidBody body2 = (RigidBody) contactManifold.getBody2(); + final RigidBody otherBody = (body1.getID() == bodyToVisit.getID()) ? body2 : body1; + // Check if the other body has already been added to the island + if (otherBody.isAlreadyInIsland) { + continue; + } + // Insert the other body into the stack of bodies to visit + stackBodiesToVisit.set(stackIndex, otherBody); + stackIndex++; + otherBody.isAlreadyInIsland = true; + } + // For each joint in which the current body is involved + JointListElement jointElement; + for (jointElement = bodyToVisit.jointsList; jointElement != null; jointElement = jointElement.next) { + final Joint joint = jointElement.joint; + // Check if the current joint has already been added into an island + if (joint.isAlreadyInIsland()) { + continue; + } + // Add the joint into the island + this.islands.get(this.islands.size() - 1).addJoint(joint); + joint.isAlreadyInIsland = true; + // Get the other body of the contact manifold + final RigidBody body1 = joint.getBody1(); + final RigidBody body2 = joint.getBody2(); + final RigidBody otherBody = (body1.getID() == bodyToVisit.getID()) ? body2 : body1; + // Check if the other body has already been added to the island + if (otherBody.isAlreadyInIsland) { + continue; + } + // Insert the other body into the stack of bodies to visit + stackBodiesToVisit.set(stackIndex, otherBody); + stackIndex++; + otherBody.isAlreadyInIsland = true; + } + } + this.islands.get(this.islands.size() - 1).resetStaticBobyNotInIsland(); + } + } + + /** + * Create a joint between two bodies in the world and return a pointer to the new joint + * @param _jointInfo The information that is necessary to create the joint + * @return A pointer to the joint that has been created in the world + */ + public Joint createJoint(final JointInfo _jointInfo) { + Joint newJoint = null; + // Allocate memory to create the new joint + switch (_jointInfo.type) { + // Ball-and-Socket joint + case BALLSOCKETJOINT: + newJoint = new BallAndSocketJoint((BallAndSocketJointInfo) _jointInfo); + break; + // Slider joint + case SLIDERJOINT: + newJoint = new SliderJoint((SliderJointInfo) _jointInfo); + break; + // Hinge joint + case HINGEJOINT: + newJoint = new HingeJoint((HingeJointInfo) _jointInfo); + break; + // Fixed joint + case FIXEDJOINT: + newJoint = new FixedJoint((FixedJointInfo) _jointInfo); + break; + default: + assert (false); + return null; + } + // If the collision between the two bodies of the raint is disabled + if (!_jointInfo.isCollisionEnabled) { + // Add the pair of bodies in the set of body pairs that cannot collide with each other + this.collisionDetection.addNoCollisionPair(_jointInfo.body1, _jointInfo.body2); + } + // Add the joint into the world + this.joints.add(newJoint); + // Add the joint into the joint list of the bodies involved in the joint + addJointToBody(newJoint); + // Return the pointer to the created joint + return newJoint; + } + + /** + * Create a rigid body into the physics world + * @param _transform Transform3Dation from body local-space to world-space + * @return A pointer to the body that has been created in the world + */ + public RigidBody createRigidBody(final Transform3D _transform) { + // Compute the body ID + final int bodyID = computeNextAvailableBodyID(); + // Largest index cannot be used (it is used for invalid index) + assert (bodyID < Integer.MAX_VALUE); + // Create the rigid body + final RigidBody rigidBody = new RigidBody(_transform, this, bodyID); + assert (rigidBody != null); + // Add the rigid body to the physics world + this.bodies.add(rigidBody); + this.rigidBodies.add(rigidBody); + // Return the pointer to the rigid body + return rigidBody; + } + + /** + * Destroy a joint + * @param[in,out] _joint Pointer to the joint you want to destroy + */ + public void destroyJoint(Joint _joint) { + if (_joint == null) { + //Log.warning("Request destroy null joint"); + return; + } + // If the collision between the two bodies of the raint was disabled + if (!_joint.isCollisionEnabled()) { + // Remove the pair of bodies from the set of body pairs that cannot collide with each other + this.collisionDetection.removeNoCollisionPair(_joint.getBody1(), _joint.getBody2()); + } + // Wake up the two bodies of the joint + _joint.getBody1().setIsSleeping(false); + _joint.getBody2().setIsSleeping(false); + // Remove the joint from the world + this.joints.remove(_joint); + // Remove the joint from the joint list of the bodies involved in the joint + _joint.getBody1().removeJointFromjointsList(_joint); + _joint.getBody2().removeJointFromjointsList(_joint); + _joint = null; + } + + /** + * Destroy a rigid body and all the joints which it beints + * @param[in,out] _rigidBody Pointer to the body you want to destroy + */ + public void destroyRigidBody(RigidBody _rigidBody) { + // Remove all the collision shapes of the body + _rigidBody.removeAllCollisionShapes(); + // Add the body ID to the list of free IDs + this.freeBodiesIDs.add(_rigidBody.getID()); + // Destroy all the joints in which the rigid body to be destroyed is involved + for (JointListElement element = _rigidBody.jointsList; element != null; element = element.next) { + destroyJoint(element.joint); + } + // Reset the contact manifold list of the body + _rigidBody.resetContactManifoldsList(); + // Remove the rigid body from the list of rigid bodies + this.bodies.remove(_rigidBody); + this.rigidBodies.remove(_rigidBody); + _rigidBody = null; + } + + /** + * Enable/Disable the sleeping technique. + * The sleeping technique is used to put bodies that are not moving into sleep + * to speed up the simulation. + * @param _isSleepingEnabled True if you want to enable the sleeping technique and false otherwise + */ + public void enableSleeping(final boolean _isSleepingEnabled) { + this.isSleepingEnabled = _isSleepingEnabled; + if (!this.isSleepingEnabled) { + // For each body of the world + for (final RigidBody it : this.rigidBodies) { + // Wake up the rigid body + it.setIsSleeping(false); + } + } + } + + /** + * Get list of all contacts. + * @return The list of all contacts of the world + */ + public List getContactsList() { + final List contactManifolds = new ArrayList<>(); + // For each currently overlapping pair of bodies + for (final OverlappingPair pair : this.collisionDetection.overlappingPairs.values()) { + // For each contact manifold of the pair + final ContactManifoldSet manifoldSet = pair.getContactManifoldSet(); + for (int i = 0; i < manifoldSet.getNbContactManifolds(); i++) { + final ContactManifold manifold = manifoldSet.getContactManifold(i); + // Get the contact manifold + contactManifolds.add(manifold); + } + } + // Return all the contact manifold + return contactManifolds; + } + + /** + * Get the gravity vector of the world + * @return The current gravity vector (in meter per seconds squared) + */ + public Vector3f getGravity() { + return this.gravity; + } + + public List getIslands() { + return this.islands; + } + + /** + * Get the number of iterations for the position raint solver + */ + public int getNbIterationsPositionSolver() { + return this.nbPositionSolverIterations; + } + + /** + * Get the number of iterations for the velocity raint solver + * @return Number if iteration. + */ + public int getNbIterationsVelocitySolver() { + return this.nbVelocitySolverIterations; + } + + /** + * Get the number of all joints + * @return Number of joints in the world + */ + public int getNbJoints() { + return this.joints.size(); + } + + /** + * Get the number of rigid bodies in the world + * @return Number of rigid bodies in the world + */ + public int getNbRigidBodies() { + return this.rigidBodies.size(); + } + + /** + * Get an iterator to the beginning of the bodies of the physics world + * @return Starting iterator of the set of rigid bodies + */ + public List getRigidBodies() { + return this.rigidBodies; + } + + /** + * Return the current sleep angular velocity + * @return The sleep angular velocity (in radian per second) + */ + public float getSleepAngularVelocity() { + return this.sleepAngularVelocity; + } + + /** + * Get the sleep linear velocity + * @return The current sleep linear velocity (in meters per second) + */ + public float getSleepLinearVelocity() { + return this.sleepLinearVelocity; + } + + /** + * Get the time a body is required to stay still before sleeping + * @return Time a body is required to stay still before sleeping (in seconds) + */ + public float getTimeBeforeSleep() { + return this.timeBeforeSleep; + } + + /** + * Initialize the bodies velocities arrays for the next simulation step. + */ + protected void initVelocityArrays() { + // Allocate memory for the bodies velocity arrays + final int nbBodies = this.rigidBodies.size(); + // this.numberBodiesCapacity = 0; // TODO: Remove this, this is a test of portage + if (this.numberBodiesCapacity < nbBodies) { + this.numberBodiesCapacity = nbBodies; + this.splitLinearVelocities = new Vector3f[this.numberBodiesCapacity]; + this.splitAngularVelocities = new Vector3f[this.numberBodiesCapacity]; + this.rainedLinearVelocities = new Vector3f[this.numberBodiesCapacity]; + this.rainedAngularVelocities = new Vector3f[this.numberBodiesCapacity]; + this.rainedPositions = new Vector3f[this.numberBodiesCapacity]; + this.rainedOrientations = new Quaternion[this.numberBodiesCapacity]; + for (int iii = 0; iii < this.numberBodiesCapacity; iii++) { + this.splitLinearVelocities[iii] = Vector3f.zero(); + this.splitAngularVelocities[iii] = Vector3f.zero(); + this.rainedLinearVelocities[iii] = Vector3f.zero(); + this.rainedAngularVelocities[iii] = Vector3f.zero(); + this.rainedPositions[iii] = Vector3f.zero(); + this.rainedOrientations[iii] = Quaternion.identity(); + } + } else { + // Reset the velocities arrays + for (int iii = 0; iii < this.numberBodiesCapacity; iii++) { + this.splitLinearVelocities[iii].setZero(); + this.splitAngularVelocities[iii].setZero(); + this.rainedLinearVelocities[iii].setZero(); + this.rainedAngularVelocities[iii].setZero(); + this.rainedPositions[iii].setZero(); + this.rainedOrientations[iii].setIdentity(); + } + } + // Initialize the map of body indexes in the velocity arrays + this.mapBodyToConstrainedVelocityIndex.clear(); + int indexBody = 0; + for (final RigidBody it : this.rigidBodies) { + // Add the body into the map + this.mapBodyToConstrainedVelocityIndex.put(it, indexBody); + indexBody++; + } + } + + /** + * Integrate position and orientation of the rigid bodies. + * The positions and orientations of the bodies are integrated using + * the sympletic Euler time stepping scheme. + */ + protected void integrateRigidBodiesPositions() { + //Log.error("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++integrateRigidBodiesPositions"); + // For each island of the world + for (int i = 0; i < this.islands.size(); i++) { + final List bodies = this.islands.get(i).getBodies(); + // For each body of the island + for (int b = 0; b < bodies.size(); b++) { + //Log.error(" [" + b + "/" + bodies.size() + "]"); + // Get the rained velocity + final int indexArray = this.mapBodyToConstrainedVelocityIndex.get(bodies.get(b)); + final Vector3f newLinVelocity = this.rainedLinearVelocities[indexArray].clone(); + final Vector3f newAngVelocity = this.rainedAngularVelocities[indexArray].clone(); + //Log.error(" newAngVelocity = " + newAngVelocity); + // Add the split impulse velocity from Contact Solver (only used + // to update the position) + if (this.contactSolver.isSplitImpulseActive()) { + newLinVelocity.add(this.splitLinearVelocities[indexArray]); + newAngVelocity.add(this.splitAngularVelocities[indexArray]); + } + // Get current position and orientation of the body + final Vector3f currentPosition = bodies.get(b).centerOfMassWorld; + //Log.error(" bodies.get(b).getTransform() = " + bodies.get(b).getTransform()); + final Quaternion currentOrientation = bodies.get(b).getTransform().getOrientation(); + // Update the new rained position and orientation of the body + this.rainedPositions[indexArray] = newLinVelocity.multiplyNew(this.timeStep).add(currentPosition); + //Log.error(" currentOrientation = " + currentOrientation); + //Log.error(" newAngVelocity = " + newAngVelocity); + //Log.error(" this.timeStep = " + FMath.floatToString(this.timeStep)); + this.rainedOrientations[indexArray] = currentOrientation.addNew(new Quaternion(0, newAngVelocity).multiplyNew(currentOrientation).multiply(0.5f * this.timeStep)); + //Log.error(" this.rainedPositions[indexArray] = " + this.rainedPositions[indexArray]); + //Log.error(" this.rainedOrientations[indexArray] = " + this.rainedOrientations[indexArray]); + } + } + for (int iii = 1; iii < this.rainedPositions.length; iii++) { + Log.error(kkk + " this.rainedPositions[" + iii + "] = " + this.rainedPositions[iii]); + Log.error(kkk + " this.rainedOrientations[" + iii + "] = " + this.rainedOrientations[iii]); + } + //Log.error("------------------------------------------------------------------------------------------------"); + //Log.error("------------------------------------------------------------------------------------------------"); + //Log.error("------------------------------------------------------------------------------------------------"); + //Log.error("------------------------------------------------------------------------------------------------"); + if (kkk++ == 98) { + //System.exit(-1); + } + } + + /** + * Integrate the velocities of rigid bodies. + * This method only set the temporary velocities but does not update + * the actual velocitiy of the bodies. The velocities updated in this method + * might violate the raints and will be corrected in the raint and + * contact solver. + */ + protected void integrateRigidBodiesVelocities() { + // Initialize the bodies velocity arrays + initVelocityArrays(); + //Log.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + // For each island of the world + for (int iii = 0; iii < this.islands.size(); iii++) { + //Log.info("Manage island : " + iii + "/" + this.islands.size()); + final List bodies = this.islands.get(iii).getBodies(); + // For each body of the island + for (int bbb = 0; bbb < bodies.size(); bbb++) { + //Log.info(" body : " + bbb + "/" + bodies.size()); + // Insert the body into the map of rained velocities + final RigidBody tmpval = bodies.get(bbb); + int indexBody = this.mapBodyToConstrainedVelocityIndex.get(tmpval); + //Log.info(" indexBody=" + indexBody); + + assert (this.splitLinearVelocities[indexBody].isZero()); + assert (this.splitAngularVelocities[indexBody].isZero()); + // Integrate the external force to get the new velocity of the body + this.rainedLinearVelocities[indexBody] = bodies.get(bbb).getLinearVelocity().addNew(bodies.get(bbb).externalForce.multiplyNew(this.timeStep * bodies.get(bbb).massInverse)); + //Log.info(" this.rainedLinearVelocities[indexBody]=" + this.rainedLinearVelocities[indexBody]); + this.rainedAngularVelocities[indexBody] = bodies.get(bbb).getAngularVelocity() + .addNew(bodies.get(bbb).getInertiaTensorInverseWorld().multiplyNew(bodies.get(bbb).externalTorque.multiplyNew(this.timeStep))); + //Log.info(" this.rainedAngularVelocities[indexBody]=" + this.rainedAngularVelocities[indexBody]); + // If the gravity has to be applied to this rigid body + if (bodies.get(bbb).isGravityEnabled() && this.isGravityEnabled) { + // Integrate the gravity force + this.rainedLinearVelocities[indexBody].add(this.gravity.multiplyNew(this.timeStep * bodies.get(bbb).massInverse * bodies.get(bbb).getMass())); + } + //Log.info(" G this.rainedLinearVelocities[indexBody]=" + this.rainedLinearVelocities[indexBody]); + // Apply the velocity damping + // Damping force : F_c = -c' * v (c=damping factor) + // Equation : m * dv/dt = -c' * v + // => dv/dt = -c * v (with c=c'/m) + // => dv/dt + c * v = 0 + // Solution : v(t) = v0 * e^(-c * t) + // => v(t + dt) = v0 * e^(-c(t + dt)) + // = v0 * e^(-ct) * e^(-c * dt) + // = v(t) * e^(-c * dt) + // => v2 = v1 * e^(-c * dt) + // Using Taylor Serie for e^(-x) : e^x ~ 1 + x + x^2/2! + ... + // => e^(-x) ~ 1 - x + // => v2 = v1 * (1 - c * dt) + final float linDampingFactor = bodies.get(bbb).getLinearDamping(); + //Log.info(" linDampingFactor=" + FMath.floatToString(linDampingFactor)); + final float angDampingFactor = bodies.get(bbb).getAngularDamping(); + //Log.info(" angDampingFactor=" + FMath.floatToString(angDampingFactor)); + final float linearDamping = FMath.pow(1.0f - linDampingFactor, this.timeStep); + //Log.info(" linearDamping=" + FMath.floatToString(linearDamping)); + final float angularDamping = FMath.pow(1.0f - angDampingFactor, this.timeStep); + //Log.info(" angularDamping=" + FMath.floatToString(angularDamping)); + this.rainedLinearVelocities[indexBody].multiply(linearDamping); + //Log.info(" this.rainedLinearVelocities[indexBody]=" + this.rainedLinearVelocities[indexBody]); + this.rainedAngularVelocities[indexBody].multiply(angularDamping); + //Log.info(" this.rainedAngularVelocities[indexBody]=" + this.rainedAngularVelocities[indexBody]); + indexBody++; + } + } + } + + /** + * Get if the gravity is enaled + * @return True if the gravity is enabled in the world + */ + public boolean isGravityEnabled() { + return this.isGravityEnabled; + } + + /** + * Get if the sleeping technique is enabled + * @return True if the sleeping technique is enabled and false otherwise + */ + public boolean isSleepingEnabled() { + return this.isSleepingEnabled; + } + + /** + * Reset the external force and torque applied to the bodies + */ + protected void resetBodiesForceAndTorque() { + // For each body of the world + this.rigidBodies.forEach((obj) -> { + obj.externalForce.setZero(); + obj.externalTorque.setZero(); + }); + } + + /** + * Set the position correction technique used for contacts + * @param _technique Technique used for the position correction (Baumgarte or Split Impulses) + */ + public void setContactsPositionCorrectionTechnique(final ContactsPositionCorrectionTechnique _technique) { + if (_technique == ContactsPositionCorrectionTechnique.BAUMGARTE_CONTACTS) { + this.contactSolver.setIsSplitImpulseActive(false); + } else { + this.contactSolver.setIsSplitImpulseActive(true); + } + } + + /** + * Set an event listener object to receive events callbacks. + * @note If you use null as an argument, the events callbacks will be disabled. + * @param _eventListener Pointer to the event listener object that will receive + * event callbacks during the simulation + */ + public void setEventListener(final EventListener _eventListener) { + this.eventListener = _eventListener; + } + + /** + * Set the gravity vector of the world + * @param _gravity The gravity vector (in meter per seconds squared) + */ + public void setGravity(final Vector3f _gravity) { + this.gravity = _gravity; + } + + /** + * Enable/Disable the gravity + * @param _isGravityEnabled True if you want to enable the gravity in the world + * and false otherwise + */ + public void setIsGratityEnabled(final boolean _isGravityEnabled) { + this.isGravityEnabled = _isGravityEnabled; + } + + /** + * Activate or deactivate the solving of friction raints at the center of the contact + * manifold instead of solving them at each contact point + * @param _isActive True if you want the friction to be solved at the center of + * the contact manifold and false otherwise + */ + public void setIsSolveFrictionAtContactManifoldCenterActive(final boolean _isActive) { + this.contactSolver.setIsSolveFrictionAtContactManifoldCenterActive(_isActive); + } + + /** + * Set the position correction technique used for joints + * @param _technique Technique used for the joins position correction (Baumgarte or Non Linear Gauss Seidel) + */ + public void setJointsPositionCorrectionTechnique(final JointsPositionCorrectionTechnique _technique) { + if (_technique == JointsPositionCorrectionTechnique.BAUMGARTE_JOINTS) { + this.raintSolver.setIsNonLinearGaussSeidelPositionCorrectionActive(false); + } else { + this.raintSolver.setIsNonLinearGaussSeidelPositionCorrectionActive(true); + } + } + + /** + * Set the number of iterations for the position raint solver + * @param _nbIterations Number of iterations for the position solver + */ + public void setNbIterationsPositionSolver(final int _nbIterations) { + this.nbPositionSolverIterations = _nbIterations; + } + + /** + * Set the number of iterations for the velocity raint solver + * @param _nbIterations Number of iterations for the velocity solver + */ + public void setNbIterationsVelocitySolver(final int _nbIterations) { + this.nbVelocitySolverIterations = _nbIterations; + } + + /** + * Set the sleep angular velocity. + * When the velocity of a body becomes smaller than the sleep linear/angular + * velocity for a given amount of time, the body starts sleeping and does not need + * to be simulated anymore. + * @param _sleepAngularVelocity The sleep angular velocity (in radian per second) + */ + public void setSleepAngularVelocity(final float _sleepAngularVelocity) { + if (_sleepAngularVelocity < 0.0f) { + //Log.error("Can not set _sleepAngularVelocity=" + _sleepAngularVelocity + " < 0 "); + return; + } + this.sleepAngularVelocity = _sleepAngularVelocity; + } + + /** + * Set the sleep linear velocity + * @param _sleepLinearVelocity The new sleep linear velocity (in meters per second) + */ + public void setSleepLinearVelocity(final float _sleepLinearVelocity) { + if (_sleepLinearVelocity < 0.0f) { + //Log.error("Can not set _sleepLinearVelocity=" + _sleepLinearVelocity + " < 0 "); + return; + } + this.sleepLinearVelocity = _sleepLinearVelocity; + } + + /** + * Set the time a body is required to stay still before sleeping + * @param timeBeforeSleep Time a body is required to stay still before sleeping (in seconds) + */ + public void setTimeBeforeSleep(final float timeBeforeSleep) { + if (timeBeforeSleep < 0.0f) { + //Log.error("Can not set timeBeforeSleep=" + timeBeforeSleep + " < 0 "); + return; + } + this.timeBeforeSleep = timeBeforeSleep; + } + + /** + * Solve the contacts and raints + */ + protected void solveContactsAndConstraints() { + // Set the velocities arrays + this.contactSolver.setSplitVelocitiesArrays(this.splitLinearVelocities, this.splitAngularVelocities); + this.contactSolver.setConstrainedVelocitiesArrays(this.rainedLinearVelocities, this.rainedAngularVelocities); + this.raintSolver.setConstrainedVelocitiesArrays(this.rainedLinearVelocities, this.rainedAngularVelocities); + this.raintSolver.setConstrainedPositionsArrays(this.rainedPositions, this.rainedOrientations); + + //Log.info(")))))))))))))))"); + for (int iii = 0; iii < this.rainedAngularVelocities.length; iii++) { + //Log.info(" " + iii + " : " + this.rainedAngularVelocities[iii]); + } + // ---------- Solve velocity raints for joints and contacts ---------- // + final int idIsland = 0; + // For each island of the world + for (final Island island : this.islands) { + // Check if there are contacts and raints to solve + final boolean isConstraintsToSolve = island.getNbJoints() > 0; + final boolean isContactsToSolve = island.getNbContactManifolds() > 0; + //Log.info("solveContactsAndConstraints : " + idIsland + " " + isConstraintsToSolve + " " + isContactsToSolve); + if (!isConstraintsToSolve && !isContactsToSolve) { + continue; + } + // If there are contacts in the current island + if (isContactsToSolve) { + // Initialize the solver + this.contactSolver.initializeForIsland(this.timeStep, island); + // Warm start the contact solver + this.contactSolver.warmStart(); + } + // If there are raints + if (isConstraintsToSolve) { + // Initialize the raint solver + this.raintSolver.initializeForIsland(this.timeStep, island); + } + // For each iteration of the velocity solver + for (int i = 0; i < this.nbVelocitySolverIterations; i++) { + // Solve the raints + if (isConstraintsToSolve) { + this.raintSolver.solveVelocityConstraints(island); + } + // Solve the contacts + if (isContactsToSolve) { + this.contactSolver.solve(); + } + } + // Cache the lambda values in order to use them in the next + // step and cleanup the contact solver + if (isContactsToSolve) { + this.contactSolver.storeImpulses(); + this.contactSolver.cleanup(); + } + } + //Log.info("(((((((((((((("); + for (int iii = 0; iii < this.rainedAngularVelocities.length; iii++) { + //Log.info(" " + iii + " : " + this.rainedAngularVelocities[iii]); + } + } + + /** + * Solve the position error correction of the raints + */ + protected void solvePositionCorrection() { + // Do not continue if there is no raints + if (this.joints.isEmpty()) { + return; + } + // For each island of the world + for (int islandIndex = 0; islandIndex < this.islands.size(); islandIndex++) { + // ---------- Solve the position error correction for the raints ---------- // + // For each iteration of the position (error correction) solver + for (int i = 0; i < this.nbPositionSolverIterations; i++) { + // Solve the position raints + this.raintSolver.solvePositionConstraints(this.islands.get(islandIndex)); + } + } + } + + @Override + public void testCollision(final CollisionBody _body1, final CollisionBody _body2, final CollisionCallback _callback) { + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + for (ProxyShape shape = _body1.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes1.add(shape.broadPhaseID); + } + final Set shapes2 = new Set(Set.getDTreeCoparator()); + for (ProxyShape shape = _body2.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes2.add(shape.broadPhaseID); + } + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(_callback, shapes1, shapes2); + } + + @Override + public void testCollision(final CollisionBody _body, final CollisionCallback _callback) { + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + // For each shape of the body + for (ProxyShape shape = _body.getProxyShapesList(); shape != null; shape = shape.getNext()) { + shapes1.add(shape.broadPhaseID); + } + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(_callback, shapes1, emptySet); + } + + /// Test and report collisions between all shapes of the world + @Override + public void testCollision(final CollisionCallback _callback) { + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(_callback, emptySet, emptySet); + } + + @Override + public void testCollision(final ProxyShape _shape, final CollisionCallback _callback) { + // Create the sets of shapes + final Set shapes = new Set(Set.getDTreeCoparator()); + shapes.add(_shape.broadPhaseID); + final Set emptySet = new Set(Set.getDTreeCoparator()); + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(_callback, shapes, emptySet); + } + + @Override + public void testCollision(final ProxyShape _shape1, final ProxyShape _shape2, final CollisionCallback _callback) { + // Create the sets of shapes + final Set shapes1 = new Set(Set.getDTreeCoparator()); + shapes1.add(_shape1.broadPhaseID); + final Set shapes2 = new Set(Set.getDTreeCoparator()); + shapes2.add(_shape2.broadPhaseID); + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(_callback, shapes1, shapes2); + } + + /** + * Update the physics simulation + * @param timeStep The amount of time to step the simulation by (in seconds) + */ + public void update(final float timeStep) { + //Log.error("========> start compute " + this.rigidBodies.size()); + for (final CollisionBody elem : this.bodies) { + //Log.info(" " + elem.getID() + " / " + elem.getAABB()); + //Log.info(" " + elem.getID() + " / " + elem.getTransform()); + } + + this.timeStep = timeStep; + // Notify the event listener about the beginning of an internal tick + if (this.eventListener != null) { + this.eventListener.beginInternalTick(); + } + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + if (this.rigidBodies.size() == 0) { + // no rigid body ==> no process to do ... + return; + } + // Compute the collision detection + this.collisionDetection.computeCollisionDetection(); + // Compute the islands (separate groups of bodies with raints between each others) + computeIslands(); + for (final CollisionBody elem : this.bodies) { + //Log.info("111 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("111 " + elem.getID() + " / " + elem.getTransform()); + } + // Integrate the velocities + integrateRigidBodiesVelocities(); + for (final CollisionBody elem : this.bodies) { + //Log.info("222 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("222 " + elem.getID() + " / " + elem.getTransform()); + } + // Solve the contacts and raints + solveContactsAndConstraints(); + for (final CollisionBody elem : this.bodies) { + //Log.info("333 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("333 " + elem.getID() + " / " + elem.getTransform()); + } + // Integrate the position and orientation of each body + integrateRigidBodiesPositions(); + for (final CollisionBody elem : this.bodies) { + //Log.info("444 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("444 " + elem.getID() + " / " + elem.getTransform()); + } + // Solve the position correction for raints + solvePositionCorrection(); + for (final CollisionBody elem : this.bodies) { + //Log.info("555 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("555 " + elem.getID() + " / " + elem.getTransform()); + } + // Update the state (positions and velocities) of the bodies + //Log.info(" ==> update state"); + updateBodiesState(); // here to update each aaBB skeleton in the Broadphase ... not systematic but why ? + //Log.info(" ===> bug a partir d'ici sur le quaternion"); + for (final CollisionBody elem : this.bodies) { + //Log.info("--- " + elem.getID() + " / " + elem.getAABB()); + //Log.info("--- " + elem.getID() + " / " + elem.getTransform()); + } + if (this.isSleepingEnabled) { + updateSleepingBodies(); + } + for (final CollisionBody elem : this.bodies) { + //Log.info("666 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("666 " + elem.getID() + " / " + elem.getTransform()); + } + // Notify the event listener about the end of an internal tick + if (this.eventListener != null) { + this.eventListener.endInternalTick(); + } + for (final CollisionBody elem : this.bodies) { + //Log.info("777 " + elem.getID() + " / " + elem.getAABB()); + //Log.info("777 " + elem.getID() + " / " + elem.getTransform()); + } + // Reset the external force and torque applied to the bodies + resetBodiesForceAndTorque(); + //Log.error("<< ============= end compute "); + } + + /** + * Update the postion/orientation of the bodies + */ + protected void updateBodiesState() { + // For each island of the world + for (int islandIndex = 0; islandIndex < this.islands.size(); islandIndex++) { + // For each body of the island + final List bodies = this.islands.get(islandIndex).getBodies(); + for (int b = 0; b < this.islands.get(islandIndex).getNbBodies(); b++) { + final int index = this.mapBodyToConstrainedVelocityIndex.get(bodies.get(b)); + // Update the linear and angular velocity of the body + bodies.get(b).linearVelocity = this.rainedLinearVelocities[index].clone(); + if (bodies.get(b).isAngularReactionEnable() == true) { + bodies.get(b).angularVelocity = this.rainedAngularVelocities[index].clone(); + } + // Update the position of the center of mass of the body + bodies.get(b).centerOfMassWorld = this.rainedPositions[index].clone(); + // Update the orientation of the body + //Log.error("<< ============= this.rainedOrientations[index] " + this.rainedOrientations[index]); + //Log.error("<< ============= this.rainedOrientations[index].safeNormalizeNew() " + this.rainedOrientations[index].safeNormalizeNew()); + if (bodies.get(b).isAngularReactionEnable() == true) { + bodies.get(b).getTransform().setOrientation(this.rainedOrientations[index].safeNormalizeNew()); + } + // Update the transform of the body (using the new center of mass and new orientation) + bodies.get(b).updateTransformWithCenterOfMass(); + // Update the broad-phase state of the body + //Log.info(" " + b + " ==> updateBroadPhaseState"); + bodies.get(b).updateBroadPhaseState(); + } + } + } + + /** + * Put bodies to sleep if needed. + * For each island, if all the bodies have been almost still for a int enough period of + * time, we put all the bodies of the island to sleep. + */ + protected void updateSleepingBodies() { + final float sleepLinearVelocitySquare = this.sleepLinearVelocity * this.sleepLinearVelocity; + final float sleepAngularVelocitySquare = this.sleepAngularVelocity * this.sleepAngularVelocity; + // For each island of the world + for (int i = 0; i < this.islands.size(); i++) { + float minSleepTime = Float.MAX_VALUE; + // For each body of the island + final List bodies = this.islands.get(i).getBodies(); + for (int b = 0; b < bodies.size(); b++) { + // Skip static bodies + if (bodies.get(b).getType() == BodyType.STATIC) { + continue; + } + // If the body is velocity is large enough to stay awake + Log.error(" check if ready to sleep: linear = " + bodies.get(b).getLinearVelocity().length2() + " > " + sleepLinearVelocitySquare); + Log.error(" check if ready to sleep: angular = " + bodies.get(b).getAngularVelocity().length2() + " > " + sleepAngularVelocitySquare); + if (bodies.get(b).getLinearVelocity().length2() > sleepLinearVelocitySquare || bodies.get(b).getAngularVelocity().length2() > sleepAngularVelocitySquare + || !bodies.get(b).isAllowedToSleep()) { + // Reset the sleep time of the body + bodies.get(b).sleepTime = 0.0f; + minSleepTime = 0.0f; + } else { // If the body velocity is bellow the sleeping velocity threshold + // Increase the sleep time + bodies.get(b).sleepTime += this.timeStep; + if (bodies.get(b).sleepTime < minSleepTime) { + minSleepTime = bodies.get(b).sleepTime; + } + } + } + // If the velocity of all the bodies of the island is under the + // sleeping velocity threshold for a period of time larger than + // the time required to become a sleeping body + if (minSleepTime >= this.timeBeforeSleep) { + // Put all the bodies of the island to sleep + for (int b = 0; b < this.islands.get(i).getNbBodies(); b++) { + bodies.get(b).setIsSleeping(true); + } + } + } + } + +} diff --git a/src/org/atriasoft/ephysics/engine/EventListener.java b/src/org/atriasoft/ephysics/engine/EventListener.java new file mode 100644 index 0000000..08cca1c --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/EventListener.java @@ -0,0 +1,40 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.ephysics.constraint.ContactPointInfo; + +/** + * This class can be used to receive event callbacks from the physics engine. + * In order to receive callbacks, you need to create a new class that inherits from + * this one and you must the methods you need. Then, you need to register your + * new event listener class to the physics world using the DynamicsWorld::setEventListener() + * method. + */ +public interface EventListener { + /** + * Called when a new contact point is found between two bodies that were separated before + * @param contact Information about the contact + */ + void beginContact(ContactPointInfo contact); + + /** + * Called at the beginning of an internal tick of the simulation step. + * Each time the DynamicsWorld::update() method is called, the physics + * engine will do several internal simulation steps. This method is + * called at the beginning of each internal simulation step. + */ + void beginInternalTick(); + + /** + * Called at the end of an internal tick of the simulation step. + * Each time the DynamicsWorld::update() metho is called, the physics + * engine will do several internal simulation steps. This method is + * called at the end of each internal simulation step. + */ + void endInternalTick(); + + /** + * Called when a new contact point is found between two bodies + * @param contact Information about the contact + */ + void newContact(ContactPointInfo contact); +} diff --git a/src/org/atriasoft/ephysics/engine/Impulse.java b/src/org/atriasoft/ephysics/engine/Impulse.java new file mode 100644 index 0000000..ab4b3a4 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/Impulse.java @@ -0,0 +1,28 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.etk.math.Vector3f; + +/** + * Represents an impulse that we can apply to bodies in the contact or raint solver. + */ +public class Impulse { + public final Vector3f linearImpulseBody1; //!< Linear impulse applied to the first body + public final Vector3f angularImpulseBody1; //!< Angular impulse applied to the first body + public final Vector3f linearImpulseBody2; //!< Linear impulse applied to the second body + public final Vector3f angularImpulseBody2; //!< Angular impulse applied to the second body + + public Impulse(final Impulse impulse) { + this.linearImpulseBody1 = impulse.linearImpulseBody1.clone(); + this.angularImpulseBody1 = impulse.angularImpulseBody1.clone(); + this.linearImpulseBody2 = impulse.linearImpulseBody2.clone(); + this.angularImpulseBody2 = impulse.angularImpulseBody2.clone(); + + } + + public Impulse(final Vector3f _initLinearImpulseBody1, final Vector3f _initAngularImpulseBody1, final Vector3f _initLinearImpulseBody2, final Vector3f _initAngularImpulseBody2) { + this.linearImpulseBody1 = _initLinearImpulseBody1.clone(); + this.angularImpulseBody1 = _initAngularImpulseBody1.clone(); + this.linearImpulseBody2 = _initLinearImpulseBody2.clone(); + this.angularImpulseBody2 = _initAngularImpulseBody2.clone(); + } +} diff --git a/src/org/atriasoft/ephysics/engine/Island.java b/src/org/atriasoft/ephysics/engine/Island.java new file mode 100644 index 0000000..29e6f35 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/Island.java @@ -0,0 +1,111 @@ +package org.atriasoft.ephysics.engine; + +import java.util.ArrayList; +import java.util.List; + +import org.atriasoft.ephysics.body.BodyType; +import org.atriasoft.ephysics.body.RigidBody; +import org.atriasoft.ephysics.collision.ContactManifold; +import org.atriasoft.ephysics.constraint.Joint; +import org.atriasoft.ephysics.internal.Log; + +/** + * An island represent an isolated group of awake bodies that are connected with each other by + * some contraints (contacts or joints). + */ +public class Island { + private final List bodies = new ArrayList<>(); //!< Array with all the bodies of the island + private final List contactManifolds = new ArrayList<>(); //!< Array with all the contact manifolds between bodies of the island + private final List joints = new ArrayList<>(); //!< Array with all the joints between bodies of the island + + /** + * Constructor + */ + public Island(final int nbMaxBodies, final int nbMaxContactManifolds, final int nbMaxJoints) { + // Allocate memory for the arrays + //this.bodies.reserve(_nbMaxBodies); + //this.contactManifolds.reserve(_nbMaxContactManifolds); + //this.joints.reserve(_nbMaxJoints); + } + + /** + * Add a body. + */ + public void addBody(final RigidBody _body) { + if (_body.isSleeping() == true) { + Log.error("Try to add a body that is sleeping ..."); + return; + } + this.bodies.add(_body); + } + + /** + * Add a contact manifold. + */ + public void addContactManifold(final ContactManifold _contactManifold) { + this.contactManifolds.add(_contactManifold); + } + + /** + * Add a joint. + */ + public void addJoint(final Joint _joint) { + this.joints.add(_joint); + } + + /** + * Return a pointer to the array of bodies + */ + public List getBodies() { + return this.bodies; + } + + /** + * Return a pointer to the array of contact manifolds + */ + public List getContactManifold() { + return this.contactManifolds; + } + + /** + * Return a pointer to the array of joints + */ + public List getJoints() { + return this.joints; + } + + /** + * Get the number of body + * @return Number of bodies. + */ + public int getNbBodies() { + return this.bodies.size(); + } + + /** + * @ Get the number of contact manifolds + * Return the number of contact manifolds in the island + */ + public int getNbContactManifolds() { + return this.contactManifolds.size(); + } + + /** + * Return the number of joints in the island + */ + public int getNbJoints() { + return this.joints.size(); + } + + /** + * Reset the isAlreadyIsland variable of the static bodies so that they can also be included in the other islands + */ + public void resetStaticBobyNotInIsland() { + for (final RigidBody it : this.bodies) { + if (it.getType() == BodyType.STATIC) { + it.isAlreadyInIsland = false; + } + } + } + +} diff --git a/src/org/atriasoft/ephysics/engine/Material.java b/src/org/atriasoft/ephysics/engine/Material.java new file mode 100644 index 0000000..4d0ca34 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/Material.java @@ -0,0 +1,91 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.ephysics.Configuration; + +public class Material { + + private float frictionCoefficient; //!< Friction coefficient (positive value) + private float rollingResistance; //!< Rolling resistance factor (positive value) + private float bounciness; //!< Bounciness during collisions (between 0 and 1) where 1 is for a very bouncy body + + /// Constructor + public Material() { + this.frictionCoefficient = Configuration.DEFAULT_FRICTION_COEFFICIENT; + this.rollingResistance = Configuration.DEFAULT_ROLLING_RESISTANCE; + this.bounciness = Configuration.DEFAULT_BOUNCINESS; + } + + /// Copy-ructor + public Material(final Material material) { + this.frictionCoefficient = material.frictionCoefficient; + this.rollingResistance = material.rollingResistance; + this.bounciness = material.bounciness; + } + + /// Return the bounciness + /// @return Bounciness factor (between 0 and 1) where 1 is very bouncy + public float getBounciness() { + return this.bounciness; + } + + /**Return the friction coefficient + * @return Friction coefficient (positive value) + */ + public float getFrictionCoefficient() { + return this.frictionCoefficient; + } + + /** Return the rolling resistance factor. If this value is larger than zero, + * it will be used to slow down the body when it is rolling + * against another body. + * @return The rolling resistance factor (positive value) + */ + public float getRollingResistance() { + return this.rollingResistance; + } + + /// Overloaded assignment operator + public Material set(final Material material) { + + // Check for self-assignment + if (this != material) { + this.frictionCoefficient = material.frictionCoefficient; + this.bounciness = material.bounciness; + this.rollingResistance = material.rollingResistance; + } + + // Return this material + return this; + } + + /** Set the bounciness. + * The bounciness should be a value between 0 and 1. The value 1 is used for a + * very bouncy body and zero is used for a body that is not bouncy at all. + * @param bounciness Bounciness factor (between 0 and 1) where 1 is very bouncy + */ + public void setBounciness(final float bounciness) { + assert (bounciness >= 0.0f && bounciness <= 1.0f); + this.bounciness = bounciness; + } + + /** Set the friction coefficient. + * The friction coefficient has to be a positive value. The value zero is used for no + * friction at all. + * @param frictionCoefficient Friction coefficient (positive value) + */ + public void setFrictionCoefficient(final float frictionCoefficient) { + assert (frictionCoefficient >= 0.0f); + this.frictionCoefficient = frictionCoefficient; + } + + /** Set the rolling resistance factor. If this value is larger than zero, + * it will be used to slow down the body when it is rolling + * against another body. + * @param rollingResistance The rolling resistance factor + */ + public void setRollingResistance(final float rollingResistance) { + assert (rollingResistance >= 0); + this.rollingResistance = rollingResistance; + } + +} diff --git a/src/org/atriasoft/ephysics/engine/OverlappingPair.java b/src/org/atriasoft/ephysics/engine/OverlappingPair.java new file mode 100644 index 0000000..6b95d19 --- /dev/null +++ b/src/org/atriasoft/ephysics/engine/OverlappingPair.java @@ -0,0 +1,88 @@ +package org.atriasoft.ephysics.engine; + +import org.atriasoft.ephysics.body.CollisionBody; +import org.atriasoft.ephysics.collision.ContactManifoldSet; +import org.atriasoft.ephysics.collision.ProxyShape; +import org.atriasoft.ephysics.collision.broadphase.PairDTree; +import org.atriasoft.ephysics.constraint.ContactPoint; +import org.atriasoft.ephysics.mathematics.PairInt; +import org.atriasoft.etk.math.Vector3f; + +/** + * This class represents a pair of two proxy collision shapes that are overlapping + * during the broad-phase collision detection. It is created when + * the two proxy collision shapes start to overlap and is destroyed when they do not + * overlap anymore. This class contains a contact manifold that + * store all the contact points between the two bodies. + */ +public class OverlappingPair { + /// Return the pair of bodies index of the pair + public static PairInt computeBodiesIndexPair(final CollisionBody body1, final CollisionBody body2) { + // Construct the pair of body index + final PairInt indexPair = body1.getID() < body2.getID() ? new PairInt(body1.getID(), body2.getID()) : new PairInt(body2.getID(), body1.getID()); + assert (indexPair.first != indexPair.second); + return indexPair; + } + + /// Return the pair of bodies index + public static PairDTree computeID(final ProxyShape shape1, final ProxyShape shape2) { + assert (shape1.getBroadPhaseID() != null && shape2.getBroadPhaseID() != null); + // Construct the pair of body index + return new PairDTree(shape1.getBroadPhaseID(), shape2.getBroadPhaseID()); + } + + private final ContactManifoldSet contactManifoldSet; //!< Set of persistent contact manifolds + + private Vector3f cachedSeparatingAxis; //!< Cached previous separating axis + + /// Constructor + public OverlappingPair(final ProxyShape shape1, final ProxyShape shape2, final int nbMaxContactManifolds) { + this.contactManifoldSet = new ContactManifoldSet(shape1, shape2, nbMaxContactManifolds); + this.cachedSeparatingAxis = new Vector3f(1.0f, 1.0f, 1.0f); + } + + /// Add a contact to the contact cache + public void addContact(final ContactPoint contact) { + this.contactManifoldSet.addContactPoint(contact); + } + + /// Clear the contact points of the contact manifold + public void clearContactPoints() { + this.contactManifoldSet.clear(); + } + + /// Return the cached separating axis + public Vector3f getCachedSeparatingAxis() { + return this.cachedSeparatingAxis; + } + + /// Return the a reference to the contact manifold set + public ContactManifoldSet getContactManifoldSet() { + return this.contactManifoldSet; + } + + /// Return the number of contacts in the cache + public int getNbContactPoints() { + return this.contactManifoldSet.getTotalNbContactPoints(); + } + + /// Return the pointer to first proxy collision shape + public ProxyShape getShape1() { + return this.contactManifoldSet.getShape1(); + } + + /// Return the pointer to second body + public ProxyShape getShape2() { + return this.contactManifoldSet.getShape2(); + } + + /// Set the cached separating axis + public void setCachedSeparatingAxis(final Vector3f axis) { + this.cachedSeparatingAxis = axis; + } + + /// Update the contact cache + public void update() { + this.contactManifoldSet.update(); + } +} diff --git a/src/org/atriasoft/ephysics/internal/Log.java b/src/org/atriasoft/ephysics/internal/Log.java new file mode 100644 index 0000000..ad567c9 --- /dev/null +++ b/src/org/atriasoft/ephysics/internal/Log.java @@ -0,0 +1,68 @@ +package org.atriasoft.ephysics.internal; + +import io.scenarium.logger.LogLevel; +import io.scenarium.logger.Logger; + +public class Log { + private static final String LIB_NAME = "ephysics"; + private static final String LIB_NAME_DRAW = Logger.getDrawableName(LIB_NAME); + private static final boolean PRINT_CRITICAL = Logger.getNeedPrint(LIB_NAME, LogLevel.CRITICAL); + private static final boolean PRINT_ERROR = Logger.getNeedPrint(LIB_NAME, LogLevel.ERROR); + private static final boolean PRINT_WARNING = Logger.getNeedPrint(LIB_NAME, LogLevel.WARNING); + private static final boolean PRINT_INFO = Logger.getNeedPrint(LIB_NAME, LogLevel.INFO); + private static final boolean PRINT_DEBUG = Logger.getNeedPrint(LIB_NAME, LogLevel.DEBUG); + private static final boolean PRINT_VERBOSE = Logger.getNeedPrint(LIB_NAME, LogLevel.VERBOSE); + private static final boolean PRINT_TODO = Logger.getNeedPrint(LIB_NAME, LogLevel.TODO); + private static final boolean PRINT_PRINT = Logger.getNeedPrint(LIB_NAME, LogLevel.PRINT); + + public static void critical(final String data) { + if (PRINT_CRITICAL) { + Logger.critical(LIB_NAME_DRAW, data); + } + } + + public static void debug(final String data) { + if (PRINT_DEBUG) { + Logger.debug(LIB_NAME_DRAW, data); + } + } + + public static void error(final String data) { + if (PRINT_ERROR) { + Logger.error(LIB_NAME_DRAW, data); + } + } + + public static void info(final String data) { + if (PRINT_INFO) { + Logger.info(LIB_NAME_DRAW, data); + } + } + + public static void print(final String data) { + if (PRINT_PRINT) { + Logger.print(LIB_NAME_DRAW, data); + } + } + + public static void todo(final String data) { + if (PRINT_TODO) { + Logger.todo(LIB_NAME_DRAW, data); + } + } + + public static void verbose(final String data) { + if (PRINT_VERBOSE) { + Logger.verbose(LIB_NAME_DRAW, data); + } + } + + public static void warning(final String data) { + if (PRINT_WARNING) { + Logger.warning(LIB_NAME_DRAW, data); + } + } + + private Log() {} + +} diff --git a/src/org/atriasoft/ephysics/mathematics/Mathematics.java b/src/org/atriasoft/ephysics/mathematics/Mathematics.java new file mode 100644 index 0000000..cf6c69a --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/Mathematics.java @@ -0,0 +1,96 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.mathematics; + +import org.atriasoft.etk.math.Vector3f; + +/** + * + * @author Jason Sorensen + */ +public class Mathematics { + + // Function to test if two real numbers are (almost) equal + // We test if two numbers a and b are such that (a-b) are in [-EPSILON; EPSILON] + public static boolean ApproxEqual(final float a, final float b, final float epsilon) { + final float difference = a - b; + return (difference < epsilon && difference > -epsilon); + } + + public static float ArcCos(final float radians) { + return (float) StrictMath.acos(radians); + } + + public static float ArcSin(final float radians) { + return (float) StrictMath.asin(radians); + } + + public static float ArcTan2(final float a, final float b) { + return (float) StrictMath.atan2(a, b); + } + + // Function that returns the result of the "value" clamped by + // two others values "lowerLimit" and "upperLimit" + public static float Clamp(final float value, final float lowerLimit, final float upperLimit) { + assert (lowerLimit <= upperLimit); + return Math.min(Math.max(value, lowerLimit), upperLimit); + } + + /// Compute the barycentric coordinates u, v, w of a point p inside the triangle (a, b, c) + /// This method uses the technique described in the book Real-Time collision detection by + /// Christer Ericson. + public static void computeBarycentricCoordinatesInTriangle(final Vector3f a, final Vector3f b, final Vector3f c, final Vector3f p, Float u, Float v, Float w) { + final Vector3f v0 = b.lessNew(a); + final Vector3f v1 = c.lessNew(a); + final Vector3f v2 = p.lessNew(a); + + final float d00 = v0.dot(v0); + final float d01 = v0.dot(v1); + final float d11 = v1.dot(v1); + final float d20 = v2.dot(v0); + final float d21 = v2.dot(v1); + + final float denom = d00 * d11 - d01 * d01; + v = (d11 * d20 - d01 * d21) / denom; + w = (d00 * d21 - d01 * d20) / denom; + u = 1.0f - v - w; + } + + public static float Cos(final float radians) { + return (float) StrictMath.cos(radians); + } + + public static float Sin(final float radians) { + return (float) StrictMath.sin(radians); + } + + public static float Sqrt(final float a) { + return (float) StrictMath.sqrt(a); + } + + public static float Tan(final float radians) { + return (float) StrictMath.tan(radians); + } +} diff --git a/src/org/atriasoft/ephysics/mathematics/Matrix2f.java b/src/org/atriasoft/ephysics/mathematics/Matrix2f.java new file mode 100644 index 0000000..b336c1f --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/Matrix2f.java @@ -0,0 +1,249 @@ +/* + * ReactPhysics3D physics library, http://code.google.com/p/reactphysics3d/ + * Copyright (c) 2010-2013 Daniel Chappuis + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the + * use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim + * that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * This file has been modified during the port to Java and differ from the source versions. + */ +package org.atriasoft.ephysics.mathematics; + +import org.atriasoft.etk.math.Constant; +import org.atriasoft.etk.math.Vector2f; + +/** + * This class represents a 2x2 matrix. + * + * @author Jason Sorensen + */ +public class Matrix2f { + + // Rows of the matrix (m[row][column]) + float m00; + float m01; + float m10; + float m11; + + // Constructor + public Matrix2f() { + setZero(); + } + + // Constructor + public Matrix2f(final float value) { + set(value, value, value, value); + } + + // Constructor with arguments + public Matrix2f(final float a1, final float a2, final float b1, final float b2) { + set(a1, a2, b1, b2); + } + + // Copy-constructor + public Matrix2f(final Matrix2f matrix) { + set(matrix); + } + + // Return the matrix with absolute values + public Matrix2f abs() { + this.m00 = Math.abs(this.m00); + this.m01 = Math.abs(this.m01); + this.m10 = Math.abs(this.m10); + this.m11 = Math.abs(this.m11); + return this; + } + + // Overloaded operator for addition with assignment + public Matrix2f add(final Matrix2f matrix) { + this.m00 += matrix.m00; + this.m01 += matrix.m01; + this.m10 += matrix.m10; + this.m11 += matrix.m11; + return this; + } + + @Override + public boolean equals(final Object obj) { + + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Matrix2f other = (Matrix2f) obj; + if (Float.floatToIntBits(this.m00) != Float.floatToIntBits(other.m00)) { + return false; + } + if (Float.floatToIntBits(this.m10) != Float.floatToIntBits(other.m10)) { + return false; + } + if (Float.floatToIntBits(this.m01) != Float.floatToIntBits(other.m01)) { + return false; + } + return Float.floatToIntBits(this.m11) == Float.floatToIntBits(other.m11); + } + + // Return a column + public Vector2f getColumn(final int index) { + if (index == 0) { + return new Vector2f(this.m00, this.m10); + } else if (index == 1) { + return new Vector2f(this.m01, this.m11); + } + throw new IllegalArgumentException("Unknown column index: " + index); + } + + // Return the determinant of the matrix + public float getDeterminant() { + return this.m00 * this.m11 - this.m10 * this.m01; + } + + // Return a row + public Vector2f getRow(final int index) { + if (index == 0) { + return new Vector2f(this.m00, this.m01); + } else if (index == 1) { + return new Vector2f(this.m10, this.m11); + } + throw new IllegalArgumentException("Unknown row index: " + index); + } + + // Return the trace of the matrix + public float getTrace() { + return this.m00 + this.m11; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 23 * hash + Float.floatToIntBits(this.m00); + hash = 23 * hash + Float.floatToIntBits(this.m10); + hash = 23 * hash + Float.floatToIntBits(this.m01); + hash = 23 * hash + Float.floatToIntBits(this.m11); + return hash; + } + + // Set the matrix to the identity matrix + public Matrix2f identity() { + this.m00 = 1.0f; + this.m01 = 0.0f; + this.m10 = 0.0f; + this.m11 = 1.0f; + return this; + } + + // Return the inverse matrix + public Matrix2f inverse() { + + // Compute the determinant of the matrix + final float determinant = getDeterminant(); + + // Check if the determinant is equal to zero + assert (Math.abs(determinant) > Constant.FLOAT_EPSILON); + final float invDeterminant = 1.0f / determinant; + + set(this.m11, -this.m01, -this.m10, this.m00); + + // Return the inverse matrix + return multiply(invDeterminant); + } + + public Matrix2f inverseNew() { + final Matrix2f tmp = new Matrix2f(this); + tmp.inverse(); + return tmp; + } + + // Overloaded operator for the negative of the matrix + public Matrix2f invert() { + this.m00 = -this.m00; + this.m01 = -this.m01; + this.m10 = -this.m10; + this.m11 = -this.m11; + return this; + } + + // Overloaded operator for substraction with assignment + public Matrix2f less(final Matrix2f matrix) { + this.m00 -= matrix.m00; + this.m01 -= matrix.m01; + this.m10 -= matrix.m10; + this.m11 -= matrix.m11; + return this; + } + + // Overloaded operator for multiplication with a number with assignment + public Matrix2f multiply(final float number) { + this.m00 *= number; + this.m01 *= number; + this.m10 *= number; + this.m11 *= number; + return this; + } + + // Overloaded operator for matrix multiplication + public Matrix2f multiply(final Matrix2f matrix) { + set(this.m00 * matrix.m00 + this.m01 * matrix.m10, this.m00 * matrix.m01 + this.m01 * matrix.m11, this.m10 * matrix.m00 + this.m11 * matrix.m10, this.m10 * matrix.m01 + this.m11 * matrix.m11); + return this; + } + + // Overloaded operator for multiplication with a vector + public Vector2f multiplyNew(final Vector2f vector) { + return new Vector2f(this.m00 * vector.x + this.m01 * vector.y, this.m10 * vector.x + this.m11 * vector.y); + } + + // Method to set all the values in the matrix + public final Matrix2f set(final float a1, final float a2, final float b1, final float b2) { + this.m00 = a1; + this.m01 = a2; + this.m10 = b1; + this.m11 = b2; + return this; + } + + public final Matrix2f set(final Matrix2f matrix) { + this.m00 = matrix.m00; + this.m01 = matrix.m01; + this.m10 = matrix.m10; + this.m11 = matrix.m11; + return this; + } + + // Set the matrix to zero + public final Matrix2f setZero() { + this.m00 = 0.0f; + this.m01 = 0.0f; + this.m10 = 0.0f; + this.m11 = 0.0f; + return this; + } + + @Override + public String toString() { + return "(00= " + this.m00 + ", 01= " + this.m01 + ", 10= " + this.m10 + ", 11= " + this.m11 + ")"; + } + + // Return the transpose matrix + public Matrix2f transpose() { + set(this.m00, this.m10, this.m01, this.m11); + return this; + } + +} diff --git a/src/org/atriasoft/ephysics/mathematics/PairInt.java b/src/org/atriasoft/ephysics/mathematics/PairInt.java new file mode 100644 index 0000000..07a94a2 --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/PairInt.java @@ -0,0 +1,67 @@ +package org.atriasoft.ephysics.mathematics; + +import java.util.Set; + +import javafx.collections.transformation.SortedList; + +public class PairInt { + public static int countInSet(final Set values, final PairInt sample) { + int count = 0; + for (final PairInt elem : values) { + if (elem.first != sample.first) { + continue; + } + if (elem.second != sample.second) { + continue; + } + count++; + } + return count; + } + + public static int countInSet(final SortedList values, final PairInt sample) { + int count = 0; + for (final PairInt elem : values) { + if (elem.first != sample.first) { + continue; + } + if (elem.second != sample.second) { + continue; + } + count++; + } + return count; + } + + public final int first; + + public final int second; + + public PairInt(final int first, final int second) { + this.first = first; + this.second = second; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PairInt tmp = (PairInt) obj; + if (this.first != tmp.first) { + return false; + } + if (this.second != tmp.second) { + return false; + } + return true; + } + + @Override + public String toString() { + return "PairInt [first=" + this.first + ", second=" + this.second + "]"; + } +} diff --git a/src/org/atriasoft/ephysics/mathematics/PairIntVector3f.java b/src/org/atriasoft/ephysics/mathematics/PairIntVector3f.java new file mode 100644 index 0000000..8fb379a --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/PairIntVector3f.java @@ -0,0 +1,14 @@ +package org.atriasoft.ephysics.mathematics; + +import org.atriasoft.etk.math.Vector3f; + +public class PairIntVector3f { + public final int first; + public final Vector3f second; + + public PairIntVector3f(final int first, final Vector3f second) { + this.first = first; + this.second = second; + } + +} diff --git a/src/org/atriasoft/ephysics/mathematics/Ray.java b/src/org/atriasoft/ephysics/mathematics/Ray.java new file mode 100644 index 0000000..a097491 --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/Ray.java @@ -0,0 +1,52 @@ +package org.atriasoft.ephysics.mathematics; + +import org.atriasoft.etk.math.Vector3f; + +public class Ray { + public Vector3f point1; //! implements Iterable { + public static Comparator getCollisionBodyCoparator() { + return new Comparator() { + @Override + public int compare(final CollisionBody a, final CollisionBody b) { + if (a.getID() < b.getID()) { + return -1; + } else if (a.getID() == b.getID()) { + return 0; + } + return -1; + } + }; + } + + public static Comparator getDTreeCoparator() { + return new Comparator() { + @Override + public int compare(final DTree a, final DTree b) { + if (a.uid < b.uid) { + return -1; + } else if (a.uid == b.uid) { + return 0; + } + return -1; + } + }; + } + + public static Comparator getIntegerComparator() { + return new Comparator() { + @Override + public int compare(final Integer a, final Integer b) { + if (a < b) { + return -1; + } else if (a == b) { + return 0; + } + return -1; + } + }; + } + + public static Comparator getPairDTreeCoparator() { + return new Comparator() { + @Override + public int compare(final PairDTree _pair1, final PairDTree _pair2) { + if (_pair1.first.uid < _pair2.first.uid) { + return -1; + } + if (_pair1.first.uid == _pair2.first.uid) { + if (_pair1.second.uid < _pair2.second.uid) { + return -1; + } + if (_pair1.second.uid == _pair2.second.uid) { + return 0; + } + return +1; + } + return +1; + } + }; + } + + public static Comparator getPairIntCoparator() { + return new Comparator() { + @Override + public int compare(final PairInt _pair1, final PairInt _pair2) { + if (_pair1.first < _pair2.first) { + return -1; + } + if (_pair1.first == _pair2.first) { + if (_pair1.second < _pair2.second) { + return -1; + } + if (_pair1.second == _pair2.second) { + return 0; + } + return +1; + } + return +1; + } + }; + } + + protected final List data = new ArrayList<>(); //!< Data of the Set ==> the Set table is composed of pointer, this permit to have high speed when resize the vector ... + + protected Comparator comparator = null; + + /* + new Comparator() { + @Override + public int compare(final TYPE _pair1, final TYPE _pair2) { + if (_pair1.first < _pair2.first) { + return -1; + } + if (_pair1.first == _pair2.first) { + if (_pair1.second < _pair2.second) { + return -1; + } + if (_pair1.second == _pair2.second) { + return 0; + } + return +1; + } + return +1; + } + }; + */ + /** + * @brief Constructor of the Set table. + */ + public Set(final Comparator comparator) { + this.comparator = comparator; + } + + /** + * @brief Add an element OR set an element value + * @note add and set is the same function. + * @param[in] _key Name of the value to set in the Set table. + * @param[in] _value Value to set in the Set table. + */ + public void add(final TYPE _key) { + for (int iii = 0; iii < this.data.size(); ++iii) { + if (_key == this.data.get(iii)) { + return; + } + // For multiple element: + if (this.comparator.compare(_key, this.data.get(iii)) == 0) { + // Find a position + this.data.add(iii, _key); + return; + } + } + this.data.add(_key); + } + + /** + * @brief Remove all entry in the Set table. + * @note It does not delete pointer if your value is a pointer type... + */ + public void clear() { + this.data.clear(); + } + + /** + * @brief Count the number of occurence of a specific element. + * @param[in] _key Name of the element to count iterence + * @return 0 No element was found + * @return 1 One element was found + */ + public int count(final TYPE _key) { + // TODO: search in a dichotomic way. + for (int iii = 0; iii < this.data.size(); iii++) { + if (this.comparator.compare(this.data.get(iii), _key) == 0) { + return 1; + } + } + return 0; + } + + public void erase(final int id) { + this.data.remove(id); + } + + public void erase(final TYPE id) { + this.data.remove(id); + } + + /** + * @brief Check if an element exist or not + * @param[in] _key Name of the Set requested + * @return true if the element exist + */ + public boolean exist(final TYPE _key) { + final int it = find(_key); + if (it == -1) { + return false; + } + return true; + } + + /** + * @brief Find an element position the the Set table + * @param[in] _key Name of the element to find + * @return Iterator on the element find or end() + */ + public int find(final TYPE _key) { + // TODO: search in a dichotomic way. + for (int iii = 0; iii < this.data.size(); iii++) { + if (this.comparator.compare(this.data.get(iii), _key) == 0) { + return iii; + } + } + return -1; + } + + /** + * @brief Get Element an a special position + * @param[in] _position Position in the Set + * @return An reference on the selected element + */ + public TYPE get(final int _position) { + return this.data.get(_position); + } + + /** + * @brief Check if the container have some element + * @return true The container is empty + * @return false The container have some element + */ + public boolean isEmpty() { + return this.data.size() == 0; + } + + @Override + public Iterator iterator() { + return this.data.iterator(); + } + + /** + * @brief Remove the last element of the vector + */ + public void popBack() { + this.data.remove(this.data.size() - 1); + } + + /** + * @brief Remove the first element of the vector + */ + public void popFront() { + this.data.remove(0); + } + + /** + * @brief: QuickSort implementation of sorting vector (all elements. + * @param[in,out] _data Vector to sort. + * @param[in] _high Comparator function of this element. + */ + void quickSort(final List _data, final Comparator _comparator) { + quickSort2(_data, 0, _data.size() - 1, _comparator); + } + + /** + * @brief: QuickSort implementation of sorting vector. + * @param[in,out] _data Vector to sort. + * @param[in] _low Lowest element to sort. + * @param[in] _high Highest element to sort. + * @param[in] _high Comparator function of this element. + */ + void quickSort(final List _data, final int _low, int _high, final Comparator _comparator) { + if (_high >= _data.size()) { + _high = _data.size() - 1; + } + /*if (_low >= _data.size()) { + _low = _data.size()-1; + }*/ + quickSort2(_data, _low, _high, _comparator); + } + + private void quickSort2(final List _data, final int _low, final int _high, final Comparator _comparator) { + if (_low >= _high) { + return; + } + // pi is partitioning index, arr[p] is now at right place + final int pi = quickSortPartition(_data, _low, _high, _comparator); + // Separately sort elements before partition and after partition + //if (pi != 0) { + quickSort2(_data, _low, pi - 1, _comparator); + //} + quickSort2(_data, pi + 1, _high, _comparator); + } + + private int quickSortPartition(final List _data, final int _low, final int _high, final Comparator _comparator) { + int iii = (_low - 1); + for (int jjj = _low; jjj < _high; ++jjj) { + if (_comparator.compare(_data.get(jjj), _data.get(_high)) < 0) { + iii++; + Collections.swap(_data, iii, jjj); + } + } + Collections.swap(_data, iii + 1, _high); + return (iii + 1); + } + + public void remove(final TYPE obj) { + this.data.remove(obj); + } + + /** + * @brief Set the comparator of the set. + * @param[in] _comparator comparing function. + */ + public void setComparator(final Comparator _comparator) { + this.comparator = _comparator; + sort(); + } + + /** + * @brief Get the number of element in the Set table + * @return number of elements + */ + public int size() { + return this.data.size(); + } + + /** + * @brief Order the Set with the corect functor + */ + private void sort() { + quickSort(this.data, this.comparator); + } +} diff --git a/src/org/atriasoft/ephysics/mathematics/SetInteger.java b/src/org/atriasoft/ephysics/mathematics/SetInteger.java new file mode 100644 index 0000000..be1aca0 --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/SetInteger.java @@ -0,0 +1,212 @@ +package org.atriasoft.ephysics.mathematics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class SetInteger { + private static void quickSort2(final List _data, final int _low, final int _high, final Comparator _comparator) { + if (_low >= _high) { + return; + } + // pi is partitioning index, arr[p] is now at right place + final int pi = quickSortPartition(_data, _low, _high, _comparator); + // Separately sort elements before partition and after partition + //if (pi != 0) { + quickSort2(_data, _low, pi - 1, _comparator); + //} + quickSort2(_data, pi + 1, _high, _comparator); + } + + private static int quickSortPartition(final List _data, final int _low, final int _high, final Comparator _comparator) { + int iii = (_low - 1); + for (int jjj = _low; jjj < _high; ++jjj) { + if (_comparator.compare(_data.get(jjj), _data.get(_high)) < 0) { + iii++; + Collections.swap(_data, iii, jjj); + } + } + Collections.swap(_data, iii + 1, _high); + return (iii + 1); + } + + private final List data = new ArrayList<>(); //!< Data of the Set ==> the Set table is composed of pointer, this permit to have high speed when resize the vector ... + + private Comparator comparator = new Comparator() { + @Override + public int compare(final Integer a, final Integer b) { + if (a < b) { + return -1; + } else if (a == b) { + return 0; + } + return -1; + } + }; + + /** + * @brief Constructor of the Set table. + */ + public SetInteger() { + + } + + /** + * @brief Add an element OR set an element value + * @note add and set is the same function. + * @param[in] _key Name of the value to set in the Set table. + * @param[in] _value Value to set in the Set table. + */ + public void add(final Integer _key) { + for (int iii = 0; iii < this.data.size(); ++iii) { + if (_key == this.data.get(iii)) { + return; + } + if (this.comparator.compare(_key, this.data.get(iii)) == 0) { + // Find a position + this.data.add(iii, _key); + return; + } + } + this.data.add(_key); + } + + /** + * @brief Remove all entry in the Set table. + * @note It does not delete pointer if your value is a pointer type... + */ + public void clear() { + this.data.clear(); + } + + /** + * @brief Count the number of occurence of a specific element. + * @param[in] _key Name of the element to count iterence + * @return 0 No element was found + * @return 1 One element was found + */ + public int count(final Integer _key) { + // TODO: search in a dichotomic way. + for (int iii = 0; iii < this.data.size(); iii++) { + if (this.comparator.compare(this.data.get(iii), _key) == 0) { + return 1; + } + } + return 0; + } + + /** + * @brief Check if an element exist or not + * @param[in] _key Name of the Set requested + * @return true if the element exist + */ + public boolean exist(final Integer _key) { + final int it = find(_key); + if (it == -1) { + return false; + } + return true; + } + + /** + * @brief Find an element position the the Set table + * @param[in] _key Name of the element to find + * @return Iterator on the element find or end() + */ + public int find(final Integer _key) { + // TODO: search in a dichotomic way. + for (int iii = 0; iii < this.data.size(); iii++) { + if (this.comparator.compare(this.data.get(iii), _key) == 0) { + return iii; + } + } + return -1; + } + + /** + * @brief Get Element an a special position + * @param[in] _position Position in the Set + * @return An reference on the selected element + */ + public Integer get(final int _position) { + return this.data.get(_position); + } + + public List getRaw() { + return this.data; + } + + /** + * @brief Check if the container have some element + * @return true The container is empty + * @return false The container have some element + */ + public boolean isEmpty() { + return this.data.size() == 0; + } + + /** + * @brief Remove the last element of the vector + */ + public void popBack() { + this.data.remove(this.data.size() - 1); + } + + /** + * @brief Remove the first element of the vector + */ + public void popFront() { + this.data.remove(0); + } + + /** + * @brief: QuickSort implementation of sorting vector (all elements. + * @param[in,out] _data Vector to sort. + * @param[in] _high Comparator function of this element. + */ + void quickSort(final List _data, final Comparator _comparator) { + quickSort2(_data, 0, _data.size() - 1, _comparator); + } + + /** + * @brief: QuickSort implementation of sorting vector. + * @param[in,out] _data Vector to sort. + * @param[in] _low Lowest element to sort. + * @param[in] _high Highest element to sort. + * @param[in] _high Comparator function of this element. + */ + void quickSort(final List _data, final int _low, int _high, final Comparator _comparator) { + if (_high >= _data.size()) { + _high = _data.size() - 1; + } + /*if (_low >= _data.size()) { + _low = _data.size()-1; + }*/ + quickSort2(_data, _low, _high, _comparator); + } + + /** + * @brief Set the comparator of the set. + * @param[in] _comparator comparing function. + */ + public void setComparator(final Comparator _comparator) { + this.comparator = _comparator; + sort(); + } + + /** + * @brief Get the number of element in the Set table + * @return number of elements + */ + public int size() { + return this.data.size(); + } + + /** + * @brief Order the Set with the corect functor + */ + private void sort() { + quickSort(this.data, this.comparator); + } +} diff --git a/src/org/atriasoft/ephysics/mathematics/SetMultiple.java b/src/org/atriasoft/ephysics/mathematics/SetMultiple.java new file mode 100644 index 0000000..08f660f --- /dev/null +++ b/src/org/atriasoft/ephysics/mathematics/SetMultiple.java @@ -0,0 +1,41 @@ +package org.atriasoft.ephysics.mathematics; + +import java.util.Comparator; + +public class SetMultiple extends Set { + /** + * @brief Constructor of the Set table. + */ + public SetMultiple(final Comparator comparator) { + super(comparator); + } + + /** + * @brief Add an element OR set an element value + * @note add and set is the same function. + * @param[in] _key Name of the value to set in the Set table. + * @param[in] _value Value to set in the Set table. + */ + @Override + public void add(final TYPE _key) { + for (int iii = 0; iii < this.data.size(); ++iii) { + if (_key == this.data.get(iii)) { + return; + } + final int compareValue = this.comparator.compare(_key, this.data.get(iii)); + if (compareValue == 0) { + // Find a position + this.data.set(iii, _key); + return; + } + // for single Element + if (compareValue == 1) { + // Find a position + this.data.add(iii, _key); + return; + } + } + this.data.add(_key); + } + +}