From 7b6855ec0e2fc4a3af51961fb635c210a2854b70 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Thu, 18 Jun 2020 22:02:27 +0200 Subject: [PATCH] [DEV] add some elements --- .classpath | 6 + .../etk/math/testTransformation3D.class | Bin 0 -> 3860 bytes res/person.blend1 | Bin 0 -> 594848 bytes src/org/atriaSoft/ephysics/body/Body.java | 141 +++ .../atriaSoft/ephysics/body/CollisionBody.cpp | 238 +++++ .../atriaSoft/ephysics/body/CollisionBody.hpp | 209 +++++ src/org/atriaSoft/ephysics/body/RigidBody.cpp | 316 +++++++ src/org/atriaSoft/ephysics/body/RigidBody.hpp | 294 ++++++ .../ephysics/collision/CollisionDetection.cpp | 458 ++++++++++ .../ephysics/collision/CollisionDetection.hpp | 143 +++ .../ephysics/collision/CollisionShapeInfo.hpp | 42 + .../ephysics/collision/ContactManifold.cpp | 310 +++++++ .../ephysics/collision/ContactManifold.hpp | 165 ++++ .../ephysics/collision/ContactManifoldSet.cpp | 197 ++++ .../ephysics/collision/ContactManifoldSet.hpp | 65 ++ .../ephysics/collision/ProxyShape.cpp | 218 +++++ .../ephysics/collision/ProxyShape.hpp | 135 +++ .../ephysics/collision/RaycastInfo.cpp | 30 + .../ephysics/collision/RaycastInfo.hpp | 88 ++ .../ephysics/collision/TriangleMesh.cpp | 14 + .../ephysics/collision/TriangleMesh.hpp | 55 ++ .../collision/TriangleVertexArray.cpp | 40 + .../collision/TriangleVertexArray.hpp | 73 ++ .../broadphase/BroadPhaseAlgorithm.cpp | 203 +++++ .../broadphase/BroadPhaseAlgorithm.hpp | 94 ++ .../collision/broadphase/DynamicAABBTree.cpp | 688 ++++++++++++++ .../collision/broadphase/DynamicAABBTree.hpp | 116 +++ .../narrowphase/CollisionDispatch.hpp | 37 + .../narrowphase/ConcaveVsConvexAlgorithm.cpp | 241 +++++ .../narrowphase/ConcaveVsConvexAlgorithm.hpp | 148 +++ .../narrowphase/DefaultCollisionDispatch.cpp | 47 + .../narrowphase/DefaultCollisionDispatch.hpp | 37 + .../narrowphase/EPA/EPAAlgorithm.cpp | 353 ++++++++ .../narrowphase/EPA/EPAAlgorithm.hpp | 91 ++ .../collision/narrowphase/EPA/EdgeEPA.cpp | 97 ++ .../collision/narrowphase/EPA/EdgeEPA.hpp | 75 ++ .../collision/narrowphase/EPA/TriangleEPA.cpp | 102 +++ .../collision/narrowphase/EPA/TriangleEPA.hpp | 141 +++ .../narrowphase/EPA/TrianglesStore.cpp | 10 + .../narrowphase/EPA/TrianglesStore.hpp | 68 ++ .../narrowphase/GJK/GJKAlgorithm.cpp | 370 ++++++++ .../narrowphase/GJK/GJKAlgorithm.hpp | 83 ++ .../collision/narrowphase/GJK/Simplex.cpp | 368 ++++++++ .../collision/narrowphase/GJK/Simplex.hpp | 167 ++++ .../narrowphase/NarrowPhaseAlgorithm.cpp | 24 + .../narrowphase/NarrowPhaseAlgorithm.hpp | 62 ++ .../narrowphase/SphereVsSphereAlgorithm.cpp | 51 ++ .../narrowphase/SphereVsSphereAlgorithm.hpp | 42 + .../ephysics/collision/shapes/AABB.cpp | 210 +++++ .../ephysics/collision/shapes/AABB.hpp | 153 ++++ .../ephysics/collision/shapes/BoxShape.cpp | 133 +++ .../ephysics/collision/shapes/BoxShape.hpp | 58 ++ .../collision/shapes/CapsuleShape.cpp | 260 ++++++ .../collision/shapes/CapsuleShape.hpp | 66 ++ .../collision/shapes/CollisionShape.cpp | 53 ++ .../collision/shapes/CollisionShape.hpp | 114 +++ .../collision/shapes/ConcaveMeshShape.cpp | 147 +++ .../collision/shapes/ConcaveMeshShape.hpp | 85 ++ .../collision/shapes/ConcaveShape.cpp | 50 ++ .../collision/shapes/ConcaveShape.hpp | 70 ++ .../ephysics/collision/shapes/ConeShape.cpp | 202 +++++ .../ephysics/collision/shapes/ConeShape.hpp | 67 ++ .../collision/shapes/ConvexMeshShape.cpp | 248 +++++ .../collision/shapes/ConvexMeshShape.hpp | 114 +++ .../ephysics/collision/shapes/ConvexShape.cpp | 33 + .../ephysics/collision/shapes/ConvexShape.hpp | 51 ++ .../collision/shapes/CylinderShape.cpp | 238 +++++ .../collision/shapes/CylinderShape.hpp | 66 ++ .../collision/shapes/HeightFieldShape.cpp | 246 +++++ .../collision/shapes/HeightFieldShape.hpp | 135 +++ .../ephysics/collision/shapes/SphereShape.cpp | 89 ++ .../ephysics/collision/shapes/SphereShape.hpp | 57 ++ .../collision/shapes/TriangleShape.cpp | 163 ++++ .../collision/shapes/TriangleShape.hpp | 72 ++ src/org/atriaSoft/ephysics/configuration.hpp | 99 ++ .../constraint/BallAndSocketJoint.cpp | 212 +++++ .../constraint/BallAndSocketJoint.hpp | 68 ++ .../ephysics/constraint/ContactPoint.cpp | 171 ++++ .../ephysics/constraint/ContactPoint.hpp | 145 +++ .../ephysics/constraint/FixedJoint.cpp | 311 +++++++ .../ephysics/constraint/FixedJoint.hpp | 78 ++ .../ephysics/constraint/HingeJoint.cpp | 849 ++++++++++++++++++ .../ephysics/constraint/HingeJoint.hpp | 206 +++++ .../atriaSoft/ephysics/constraint/Joint.cpp | 71 ++ .../atriaSoft/ephysics/constraint/Joint.hpp | 124 +++ .../ephysics/constraint/SliderJoint.cpp | 840 +++++++++++++++++ .../ephysics/constraint/SliderJoint.hpp | 203 +++++ src/org/atriaSoft/ephysics/debug.cpp | 12 + src/org/atriaSoft/ephysics/debug.hpp | 37 + .../ephysics/engine/CollisionWorld.cpp | 154 ++++ .../ephysics/engine/CollisionWorld.hpp | 173 ++++ .../ephysics/engine/ConstraintSolver.cpp | 78 ++ .../ephysics/engine/ConstraintSolver.hpp | 141 +++ .../ephysics/engine/ContactSolver.cpp | 712 +++++++++++++++ .../ephysics/engine/ContactSolver.hpp | 312 +++++++ .../ephysics/engine/DynamicsWorld.cpp | 789 ++++++++++++++++ .../ephysics/engine/DynamicsWorld.hpp | 297 ++++++ .../ephysics/engine/EventListener.hpp | 56 ++ src/org/atriaSoft/ephysics/engine/Impulse.hpp | 46 + src/org/atriaSoft/ephysics/engine/Island.cpp | 70 ++ src/org/atriaSoft/ephysics/engine/Island.hpp | 83 ++ .../atriaSoft/ephysics/engine/Material.cpp | 100 +++ .../atriaSoft/ephysics/engine/Material.hpp | 47 + .../ephysics/engine/OverlappingPair.cpp | 75 ++ .../ephysics/engine/OverlappingPair.hpp | 63 ++ .../atriaSoft/ephysics/engine/Profiler.cpp | 353 ++++++++ .../atriaSoft/ephysics/engine/Profiler.hpp | 155 ++++ src/org/atriaSoft/ephysics/engine/Timer.cpp | 75 ++ src/org/atriaSoft/ephysics/engine/Timer.hpp | 58 ++ src/org/atriaSoft/ephysics/ephysics.hpp | 38 + .../atriaSoft/ephysics/mathematics/Ray.hpp | 48 + .../ephysics/mathematics/mathematics.hpp | 22 + .../mathematics/mathematics_functions.cpp | 41 + .../mathematics/mathematics_functions.hpp | 59 ++ src/org/atriaSoft/ephysics/memory/Stack.hpp | 85 ++ src/org/atriaSoft/etk/math/Matrix3f.java | 28 + src/org/atriaSoft/etk/math/Vector3f.java | 35 + .../components/ComponentPhysics.java | 105 ++- .../gameEngine/engines/EnginePhysics.java | 13 +- .../atriaSoft/gameEngine/geometry/AABB.java | 33 + .../gameEngine/geometry/Geometry3D.java | 125 +++ .../atriaSoft/gameEngine/geometry/Line.java | 29 + .../atriaSoft/gameEngine/geometry/OBB.java | 30 + .../atriaSoft/gameEngine/geometry/Plane.java | 21 + .../atriaSoft/gameEngine/geometry/Ray.java | 29 + .../atriaSoft/gameEngine/geometry/Sphere.java | 21 + .../gameEngine/geometry/Triangle.java | 24 + .../atriaSoft/gameEngine/map/MapVoxel.java | 1 + .../gameEngine/physics/PhysicBox.java | 14 + .../gameEngine/physics/PhysicMapVoxel.java | 10 + .../gameEngine/physics/PhysicShape.java | 1 + .../gameEngine/physics/PhysicSphere.java | 4 + .../physics/ToolCollisionOBBWithOBB.java | 127 +++ .../sample/LoxelEngine/LoxelApplication.java | 20 +- .../etk/math/testTransformation3D.java | 77 ++ 135 files changed, 18430 insertions(+), 5 deletions(-) create mode 100644 jege/bin/binTest/test/atriaSoft/etk/math/testTransformation3D.class create mode 100644 res/person.blend1 create mode 100644 src/org/atriaSoft/ephysics/body/Body.java create mode 100644 src/org/atriaSoft/ephysics/body/CollisionBody.cpp create mode 100644 src/org/atriaSoft/ephysics/body/CollisionBody.hpp create mode 100644 src/org/atriaSoft/ephysics/body/RigidBody.cpp create mode 100644 src/org/atriaSoft/ephysics/body/RigidBody.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/CollisionDetection.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/CollisionDetection.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/CollisionShapeInfo.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/ContactManifold.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/ContactManifold.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/ContactManifoldSet.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/ContactManifoldSet.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/ProxyShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/ProxyShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/RaycastInfo.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/RaycastInfo.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/TriangleMesh.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/TriangleMesh.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/TriangleVertexArray.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/TriangleVertexArray.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/CollisionDispatch.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/AABB.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/AABB.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/BoxShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/BoxShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConeShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConeShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/SphereShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/SphereShape.hpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.cpp create mode 100644 src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.hpp create mode 100644 src/org/atriaSoft/ephysics/configuration.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/ContactPoint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/ContactPoint.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/FixedJoint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/FixedJoint.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/HingeJoint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/HingeJoint.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/Joint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/Joint.hpp create mode 100644 src/org/atriaSoft/ephysics/constraint/SliderJoint.cpp create mode 100644 src/org/atriaSoft/ephysics/constraint/SliderJoint.hpp create mode 100644 src/org/atriaSoft/ephysics/debug.cpp create mode 100644 src/org/atriaSoft/ephysics/debug.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/CollisionWorld.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/CollisionWorld.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/ConstraintSolver.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/ConstraintSolver.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/ContactSolver.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/ContactSolver.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/DynamicsWorld.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/DynamicsWorld.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/EventListener.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/Impulse.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/Island.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/Island.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/Material.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/Material.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/OverlappingPair.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/OverlappingPair.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/Profiler.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/Profiler.hpp create mode 100644 src/org/atriaSoft/ephysics/engine/Timer.cpp create mode 100644 src/org/atriaSoft/ephysics/engine/Timer.hpp create mode 100644 src/org/atriaSoft/ephysics/ephysics.hpp create mode 100644 src/org/atriaSoft/ephysics/mathematics/Ray.hpp create mode 100644 src/org/atriaSoft/ephysics/mathematics/mathematics.hpp create mode 100644 src/org/atriaSoft/ephysics/mathematics/mathematics_functions.cpp create mode 100644 src/org/atriaSoft/ephysics/mathematics/mathematics_functions.hpp create mode 100644 src/org/atriaSoft/ephysics/memory/Stack.hpp create mode 100644 src/org/atriaSoft/gameEngine/geometry/AABB.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Geometry3D.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Line.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/OBB.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Plane.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Ray.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Sphere.java create mode 100644 src/org/atriaSoft/gameEngine/geometry/Triangle.java create mode 100644 src/org/atriaSoft/gameEngine/physics/ToolCollisionOBBWithOBB.java create mode 100644 srcTest/test/atriaSoft/etk/math/testTransformation3D.java diff --git a/.classpath b/.classpath index 9724b2f..b6b4ba5 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,11 @@ + + + + + @@ -71,5 +76,6 @@ + diff --git a/jege/bin/binTest/test/atriaSoft/etk/math/testTransformation3D.class b/jege/bin/binTest/test/atriaSoft/etk/math/testTransformation3D.class new file mode 100644 index 0000000000000000000000000000000000000000..65efadcc3c824c143b3d02f86427ea7472cab7e4 GIT binary patch literal 3860 zcmcJR+iw#`6vn>^CI)B2#n3>wg%A?#T1*2u9@^(t>Py30RS8>-RP~`!tBU?NeJ$#l+4b7q*fo);NAm8TJ#*&FZ@xJ@e?I^D z7XTNq=0zLBu&ETxuxuKtJf|<4Va2=?&dKIVnA?+vtQD4ZgAA&!jmEt2Fzi{C@5$k; ztYyN}i>pf7WN5#nYN|QLuq_ZwF?g=&ONtNMuv5Zzv@`5ZsG4%Km|Ikgq`a6_82kx6 zEoW0Q4NK3q(POTt1%^;!D>+3dJolf`Rn5Gv@sSw1XN#Jt=9H9LP$~Ynrs<}b1BE}5 z(2Y!ZwMZG$e_k~eBP{3DaFT*VyznywxI-05M$Rb{T809LGm4&5OygmAF2AA}iWmDC zdg{F$fGI+w8>7pf=o;X|J{;gQ4ic?EJRT3GeAtUa+;o_sKOmB-j=3pHMD#d{9xsm3 zs@0F#D8orR)zO%QG+4edzb%BnhO-cf3$g3{%1Y zPBC=I;wdg?3$#R@z(O$PiT(U^&nk_WVr+)?GUPPO#dx~rI!RWGttGLFnLtRp6$C(cq`T{}M zDmgeRog(egIBg30`7nv=Uc5mWRLcsvtw?wiHyC!B`mDU9QW@fki3MXkvndHTF-=7z zZ78y-SW##B4kwAcz-q-?C&-6+OTsMXXl}V;)>QLZnp@0`bo10&$M)iF+VQP*DApl~ zTU>^>8J3%lU8yvYX53pkI16LxbV0&9c$YS!ty9W`;l`H7Z=^R0MJlB&pc^VFMiga+ z-e#FQi7}jV`81181*k^MGxVXNI=x8!TN4+~yi_kqP_Rt$)A+|Pt3D!O{fDj{zU@(= zYD?Fv67FyfdkS|A(+3%ccn#1b=*TljHd`?!!_-TmD}o8WZW%VBWqC8~tKpTTFS3@& z5;FR(54J{D@%N#Cdt9ma|6ip#sOjTz`Ps9-IMPQFKElUT@NJ}(oETSi+7U>86cp zCd6yNj}E$*EC;%F(i5EFzetq}x4F6W(y8tQol^QRfs>d=KXP=;`kY98 zg8++tzV^WZnUhxduznBe$HWx* zp*}7lTQ9Gi8x+aLu~k4>wt7%*n=LQuTk&(`<=85q?7me{*0#HV9$X|m5yWr_w{aO+ zn)VtUi@rt_KVZB@Uh6iX9)abqkeAUA%U86ec@d&{7X%hblpG-!iOZ#`xDtwjj{gB~ w^okOFXajNz6*p8f_tWBn@*SYF<3-${mZ-@xVP`o&??d>6vSj#_{M+!^zlOi_g#Z8m literal 0 HcmV?d00001 diff --git a/res/person.blend1 b/res/person.blend1 new file mode 100644 index 0000000000000000000000000000000000000000..e5f4438fb550497767dd9dad6948b56ee2c904b7 GIT binary patch literal 594848 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 QVWq^?YnGCXLU;WA4|%KD8~^|S literal 0 HcmV?d00001 diff --git a/src/org/atriaSoft/ephysics/body/Body.java b/src/org/atriaSoft/ephysics/body/Body.java new file mode 100644 index 0000000..1173503 --- /dev/null +++ b/src/org/atriaSoft/ephysics/body/Body.java @@ -0,0 +1,141 @@ +package org.atriaSoft.ephysics.body; + +/** + * @brief Represent a body of the physics engine. You should not + * instantiante this class but instantiate the CollisionBody or RigidBody + * classes instead. + */ +class Body { + protected long id; //!< ID of the body + protected boolean isAlreadyInIsland; //!< True if the body has already been added in an island (for sleeping technique) + protected boolean isAllowedToSleep; //!< True if the body is allowed to go to sleep for better efficiency + /** + * @brief 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; + protected boolean isSleeping; //!< True if the body is sleeping (for sleeping technique) + protected float sleepTime; //!< Elapsed time since the body velocity was bellow the sleep velocity + protected Object userData; //!< Pointer that can be used to attach user data to the body + /** + * @brief Constructor + * @param[in] id ID of the new body + */ + public Body(long id) { + this.id = id; + this.isAlreadyInIsland = false; + this.isAllowedToSleep = true; + this.isActive = true; + this.isSleeping = false; + this.sleepTime = 0; + this.userData = null; + } + /** + * @brief Return the id of the body + * @return The ID of the body + */ + public long getID() { + return this.id; + } + /** + * @brief Return whether or not the body is allowed to sleep + * @return True if the body is allowed to sleep and false otherwise + */ + public boolean isAllowedToSleep() { + return this.isAllowedToSleep; + } + /** + * @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 void setIsAllowedToSleep(boolean isAllowedToSleep) { + this.isAllowedToSleep = isAllowedToSleep; + if (!this.isAllowedToSleep) { + setIsSleeping(false); + } + } + /** + * @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 Return true if the body is active + * @return True if the body currently active and false otherwise + */ + public boolean isActive() { + return this.isActive; + } + /** + * @brief Set whether or not the body is active + * @param[in] isActive True if you want to activate the body + */ + public void setIsActive(boolean isActive) { + this.isActive = isActive; + } + /** + * @brief Set the variable to know whether or not the body is sleeping + * @param[in] isSleeping Set the new status + */ + public void setIsSleeping(boolean isSleeping) { + if (isSleeping) { + this.sleepTime = 0.0f; + } else { + if (this.isSleeping) { + this.sleepTime = 0.0f; + } + } + this.isSleeping = isSleeping; + } + /** + * @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 + */ + public Object getUserData() { + return this.userData; + } + /** + * @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(Object userData) { + this.userData = userData; + } + /** + * @brief Smaller than operator + * @param[in] obj Other object to compare + * @return true if the current element is smaller + */ + public boolean isLess( Body obj) { + return (this.id < obj.this.id); + } + /** + * @brief Larger than operator + * @param[in] obj Other object to compare + * @return true if the current element is Bigger + */ + public boolean isUpper( Body obj) { + return (this.id > obj.this.id); + } + /** + * @brief Equal operator + * @param[in] obj Other object to compare + * @return true if the curretn element is equal + */ + public boolean isEqual( Body obj) { + return (this.id == obj.this.id); + } + /** + * @brief Not equal operator + * @param[in] obj Other object to compare + * @return true if the curretn element is NOT equal + */ + public boolean isDifferent( Body obj) { + return (this.id != obj.this.id); + } +} diff --git a/src/org/atriaSoft/ephysics/body/CollisionBody.cpp b/src/org/atriaSoft/ephysics/body/CollisionBody.cpp new file mode 100644 index 0000000..51e158e --- /dev/null +++ b/src/org/atriaSoft/ephysics/body/CollisionBody.cpp @@ -0,0 +1,238 @@ +/** @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) + */ +#include +#include +#include + +// We want to use the ReactPhysics3D namespace +using namespace ephysics; + + +CollisionBody::CollisionBody( etk::Transform3D transform, CollisionWorld world, long id): + Body(id), + this.type(DYNAMIC), + this.transform(transform), + this.proxyCollisionShapes(null), + this.numberCollisionShapes(0), + this.contactManifoldsList(null), + this.world(world) { + + Log.debug(" set transform: " << transform); + if (isnan(transform.getPosition().x()) == true) { // check NAN + Log.critical(" set transform: " << transform); + } + if (isinf(transform.getOrientation().z()) == true) { + Log.critical(" set transform: " << transform); + } +} + +CollisionBody::~CollisionBody() { + assert(this.contactManifoldsList == null); + + // Remove all the proxy collision shapes of the body + removeAllCollisionShapes(); +} + +inline void CollisionBody::setType(BodyType type) { + this.type = type; + if (this.type == STATIC) { + // Update the broad-phase state of the body + updateBroadPhaseState(); + } +} + +void CollisionBody::setTransform( etk::Transform3D transform) { + Log.debug(" set transform: " << this.transform << " ==> " << transform); + if (isnan(transform.getPosition().x()) == true) { // check NAN + Log.critical(" set transform: " << this.transform << " ==> " << transform); + } + if (isinf(transform.getOrientation().z()) == true) { + Log.critical(" set transform: " << this.transform << " ==> " << transform); + } + this.transform = transform; + updateBroadPhaseState(); +} + +ProxyShape* CollisionBody::addCollisionShape(CollisionShape* collisionShape, + etk::Transform3D transform) { + // Create a proxy collision shape to attach the collision shape to the body + ProxyShape* proxyShape = ETKNEW(ProxyShape, this, collisionShape,transform, float(1)); + // Add it to the list of proxy collision shapes of the body + if (this.proxyCollisionShapes == null) { + this.proxyCollisionShapes = proxyShape; + } else { + proxyShape->this.next = this.proxyCollisionShapes; + this.proxyCollisionShapes = proxyShape; + } + AABB aabb; + collisionShape->computeAABB(aabb, this.transform * transform); + this.world.this.collisionDetection.addProxyCollisionShape(proxyShape, aabb); + this.numberCollisionShapes++; + return proxyShape; +} + +void CollisionBody::removeCollisionShape( ProxyShape* proxyShape) { + ProxyShape* current = this.proxyCollisionShapes; + // If the the first proxy shape is the one to remove + if (current == proxyShape) { + this.proxyCollisionShapes = current->this.next; + if (this.isActive) { + this.world.this.collisionDetection.removeProxyCollisionShape(current); + } + ETKDELETE(ProxyShape, current); + current = null; + this.numberCollisionShapes--; + return; + } + // Look for the proxy shape that contains the collision shape in parameter + while(current->this.next != null) { + // If we have found the collision shape to remove + if (current->this.next == proxyShape) { + // Remove the proxy collision shape + ProxyShape* elementToRemove = current->this.next; + current->this.next = elementToRemove->this.next; + if (this.isActive) { + this.world.this.collisionDetection.removeProxyCollisionShape(elementToRemove); + } + ETKDELETE(ProxyShape, elementToRemove); + elementToRemove = null; + this.numberCollisionShapes--; + return; + } + // Get the next element in the list + current = current->this.next; + } +} + + +void CollisionBody::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 + ProxyShape* nextElement = current->this.next; + if (this.isActive) { + this.world.this.collisionDetection.removeProxyCollisionShape(current); + } + ETKDELETE(ProxyShape, current); + // Get the next element in the list + current = nextElement; + } + this.proxyCollisionShapes = null; +} + + +void CollisionBody::resetContactManifoldsList() { + // Delete the linked list of contact manifolds of that body + ContactManifoldListElement* currentElement = this.contactManifoldsList; + while (currentElement != null) { + ContactManifoldListElement* nextElement = currentElement->next; + // Delete the current element + ETKDELETE(ContactManifoldListElement, currentElement); + currentElement = nextElement; + } + this.contactManifoldsList = null; +} + + +void CollisionBody::updateBroadPhaseState() { + // For all the proxy collision shapes of the body + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + // Update the proxy + updateProxyShapeInBroadPhase(shape); + } +} + + +void CollisionBody::updateProxyShapeInBroadPhase(ProxyShape* proxyShape, boolean forceReinsert) { + AABB aabb; + proxyShape->getCollisionShape()->computeAABB(aabb, this.transform * proxyShape->getLocalToBodyTransform()); + this.world.this.collisionDetection.updateProxyCollisionShape(proxyShape, aabb, vec3(0, 0, 0), forceReinsert); +} + + +void CollisionBody::setIsActive(boolean isActive) { + // If the state does not change + if (this.isActive == isActive) { + return; + } + Body::setIsActive(isActive); + // If we have to activate the body + if (isActive == true) { + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + AABB aabb; + shape->getCollisionShape()->computeAABB(aabb, this.transform * shape->this.localToBodyTransform); + this.world.this.collisionDetection.addProxyCollisionShape(shape, aabb); + } + } else { + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + this.world.this.collisionDetection.removeProxyCollisionShape(shape); + } + resetContactManifoldsList(); + } +} + + +void CollisionBody::askForBroadPhaseCollisionCheck() { + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + this.world.this.collisionDetection.askForBroadPhaseCollisionCheck(shape); + } +} + + +int CollisionBody::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->this.isAlreadyInIsland = false; + currentElement = currentElement->next; + nbManifolds++; + } + return nbManifolds; +} + +boolean CollisionBody::testPointInside( vec3 worldPoint) { + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + if (shape->testPointInside(worldPoint)) return true; + } + return false; +} + +boolean CollisionBody::raycast( Ray ray, RaycastInfo raycastInfo) { + if (this.isActive == false) { + return false; + } + boolean isHit = false; + Ray rayTemp(ray); + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + // Test if the ray hits the collision shape + if (shape->raycast(rayTemp, raycastInfo)) { + rayTemp.maxFraction = raycastInfo.hitFraction; + isHit = true; + } + } + return isHit; +} + +AABB CollisionBody::getAABB() { + AABB bodyAABB; + if (this.proxyCollisionShapes == null) { + return bodyAABB; + } + this.proxyCollisionShapes->getCollisionShape()->computeAABB(bodyAABB, this.transform * this.proxyCollisionShapes->getLocalToBodyTransform()); + for (ProxyShape* shape = this.proxyCollisionShapes->this.next; shape != null; shape = shape->this.next) { + AABB aabb; + shape->getCollisionShape()->computeAABB(aabb, this.transform * shape->getLocalToBodyTransform()); + bodyAABB.mergeWithAABB(aabb); + } + return bodyAABB; +} + diff --git a/src/org/atriaSoft/ephysics/body/CollisionBody.hpp b/src/org/atriaSoft/ephysics/body/CollisionBody.hpp new file mode 100644 index 0000000..8da0d5b --- /dev/null +++ b/src/org/atriaSoft/ephysics/body/CollisionBody.hpp @@ -0,0 +1,209 @@ +/** @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) + */ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace ephysics { + struct ContactManifoldListElement; + class ProxyShape; + class CollisionWorld; + /** + * @brief Define the type of the body + */ + 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. + }; + /** + * @brief This class represents a body that is able to collide with others bodies. This class inherits from the Body class. + */ + class CollisionBody : public Body { + protected : + BodyType this.type; //!< Type of body (static, kinematic or dynamic) + etk::Transform3D this.transform; //!< Position and orientation of the body + ProxyShape* this.proxyCollisionShapes; //!< First element of the linked list of proxy collision shapes of this body + int this.numberCollisionShapes; //!< Number of collision shapes + ContactManifoldListElement* this.contactManifoldsList; //!< First element of the linked list of contact manifolds involving this body + CollisionWorld this.world; //!< Reference to the world the body belongs to + /// Private copy-ructor + CollisionBody( CollisionBody body) = delete; + /// Private assignment operator + CollisionBody operator=( CollisionBody body) = delete; + /** + * @brief Reset the contact manifold lists + */ + void resetContactManifoldsList(); + /** + * @brief Remove all the collision shapes + */ + void removeAllCollisionShapes(); + /** + * @brief Update the broad-phase state for this body (because it has moved for instance) + */ + virtual void updateBroadPhaseState() ; + /** + * @brief Update the broad-phase state of a proxy collision shape of the body + */ + void updateProxyShapeInBroadPhase(ProxyShape* proxyShape, boolean forceReinsert = false) ; + /** + * @brief Ask the broad-phase to test again the collision shapes of the body for collision (as if the body has moved). + */ + void askForBroadPhaseCollisionCheck() ; + /** + * @brief Reset the this.isAlreadyInIsland variable of the body and contact manifolds. + * This method also returns the number of contact manifolds of the body. + */ + int resetIsAlreadyInIslandAndCountManifolds(); + public : + /** + * @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 + */ + CollisionBody( etk::Transform3D transform, CollisionWorld world, long id); + /** + * @brief Destructor + */ + virtual ~CollisionBody(); + /** + * @brief Return the type of the body + * @return the type of the body (STATIC, KINEMATIC, DYNAMIC) + */ + BodyType getType() { + return this.type; + } + /** + * @brief Set the type of the body + * @param[in] type The type of the body (STATIC, KINEMATIC, DYNAMIC) + */ + virtual void setType(BodyType type); + /** + * @brief Set whether or not the body is active + * @param[in] isActive True if you want to activate the body + */ + virtual void setIsActive(boolean isActive); + /** + * @brief Return the current position and orientation + * @return The current transformation of the body that transforms the local-space of the body into world-space + */ + etk::Transform3D getTransform() { + return this.transform; + } + /** + * @brief Set the current position and orientation + * @param transform The transformation of the body that transforms the local-space of the body into world-space + */ + virtual void setTransform( etk::Transform3D 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 into 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. + */ + ProxyShape* addCollisionShape(CollisionShape* collisionShape, etk::Transform3D transform); + /** + * @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 + */ + virtual void removeCollisionShape( ProxyShape* proxyShape); + /** + * @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 + */ + ContactManifoldListElement* getContactManifoldsList() { + return this.contactManifoldsList; + } + /** + * @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 + */ + boolean testPointInside( vec3 worldPoint) ; + /** + * @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 + */ + boolean raycast( Ray ray, RaycastInfo raycastInfo); + /** + * @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 + */ + AABB getAABB() ; + /** + * @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 + */ + ProxyShape* getProxyShapesList() { + return this.proxyCollisionShapes; + } + /** + * @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 + */ + ProxyShape* getProxyShapesList() { + return this.proxyCollisionShapes; + } + /** + * @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 + */ + vec3 getWorldPoint( vec3 localPoint) { + return this.transform * 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 + */ + vec3 getWorldVector( vec3 localVector) { + return this.transform.getOrientation() * localVector; + } + /** + * @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 + */ + vec3 getLocalPoint( vec3 worldPoint) { + return this.transform.getInverse() * 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 + */ + vec3 getLocalVector( vec3 worldVector) { + return this.transform.getOrientation().getInverse() * worldVector; + } + friend class CollisionWorld; + friend class DynamicsWorld; + friend class CollisionDetection; + friend class BroadPhaseAlgorithm; + friend class ConvexMeshShape; + friend class ProxyShape; + }; +} + diff --git a/src/org/atriaSoft/ephysics/body/RigidBody.cpp b/src/org/atriaSoft/ephysics/body/RigidBody.cpp new file mode 100644 index 0000000..0a366d6 --- /dev/null +++ b/src/org/atriaSoft/ephysics/body/RigidBody.cpp @@ -0,0 +1,316 @@ +/** @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) + */ +#include +#include +#include +#include +#include + +using namespace ephysics; + + +RigidBody::RigidBody( etk::Transform3D transform, CollisionWorld world, long id): + CollisionBody(transform, world, id), + this.initMass(1.0f), + this.centerOfMassLocal(0, 0, 0), + this.centerOfMassWorld(transform.getPosition()), + this.isGravityEnabled(true), + this.linearDamping(0.0f), + this.angularDamping(float(0.0)), + this.jointsList(null) { + // Compute the inverse mass + this.massInverse = 1.0f / this.initMass; +} + +RigidBody::~RigidBody() { + assert(this.jointsList == null); +} + + +void RigidBody::setType(BodyType type) { + if (this.type == type) { + return; + } + CollisionBody::setType(type); + recomputeMassInformation(); + if (this.type == STATIC) { + // Reset the velocity to zero + this.linearVelocity.setZero(); + this.angularVelocity.setZero(); + } + if ( this.type == STATIC + || this.type == 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.getInverse(); + } + 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(); +} + + +void RigidBody::setInertiaTensorLocal( etk::Matrix3x3 inertiaTensorLocal) { + if (this.type != DYNAMIC) { + return; + } + this.inertiaTensorLocal = inertiaTensorLocal; + this.inertiaTensorLocalInverse = this.inertiaTensorLocal.getInverse(); +} + + +void RigidBody::setCenterOfMassLocal( vec3 centerOfMassLocal) { + if (this.type != DYNAMIC) { + return; + } + vec3 oldCenterOfMass = this.centerOfMassWorld; + this.centerOfMassLocal = centerOfMassLocal; + this.centerOfMassWorld = this.transform * this.centerOfMassLocal; + this.linearVelocity += this.angularVelocity.cross(this.centerOfMassWorld - oldCenterOfMass); +} + + +void RigidBody::setMass(float mass) { + if (this.type != 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; + } +} + +void RigidBody::removeJointFrothis.jointsList( 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; + ETKDELETE(JointListElement, elementToRemove); + 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; + ETKDELETE(JointListElement, elementToRemove); + elementToRemove = null; + break; + } + currentElement = currentElement->next; + } + } +} + + +ProxyShape* RigidBody::addCollisionShape(CollisionShape* collisionShape, + etk::Transform3D transform, + float mass) { + assert(mass > 0.0f); + // Create a new proxy collision shape to attach the collision shape to the body + ProxyShape* proxyShape = ETKNEW(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->this.next = this.proxyCollisionShapes; + this.proxyCollisionShapes = proxyShape; + } + // Compute the world-space AABB of the new collision shape + AABB aabb; + collisionShape->computeAABB(aabb, this.transform * transform); + // Notify the collision detection about this new collision shape + this.world.this.collisionDetection.addProxyCollisionShape(proxyShape, aabb); + this.numberCollisionShapes++; + recomputeMassInformation(); + return proxyShape; +} + +void RigidBody::removeCollisionShape( ProxyShape* proxyShape) { + CollisionBody::removeCollisionShape(proxyShape); + recomputeMassInformation(); +} + + +void RigidBody::setLinearVelocity( vec3 linearVelocity) { + if (this.type == STATIC) { + return; + } + this.linearVelocity = linearVelocity; + if (this.linearVelocity.length2() > 0.0f) { + setIsSleeping(false); + } +} + + +void RigidBody::setAngularVelocity( vec3 angularVelocity) { + if (this.type == STATIC) { + return; + } + this.angularVelocity = angularVelocity; + if (this.angularVelocity.length2() > 0.0f) { + setIsSleeping(false); + } +} + +void RigidBody::setIsSleeping(boolean isSleeping) { + if (isSleeping) { + this.linearVelocity.setZero(); + this.angularVelocity.setZero(); + this.externalForce.setZero(); + this.externalTorque.setZero(); + } + Body::setIsSleeping(isSleeping); +} + +void RigidBody::updateTransformWithCenterOfMass() { + // Translate the body according to the translation of the center of mass position + this.transform.setPosition(this.centerOfMassWorld - this.transform.getOrientation() * this.centerOfMassLocal); + if (isnan(this.transform.getPosition().x()) == true) { + Log.critical("updateTransformWithCenterOfMass: " << this.transform); + } + if (isinf(this.transform.getOrientation().z()) == true) { + Log.critical(" set transform: " << this.transform); + } +} + +void RigidBody::setTransform( etk::Transform3D transform) { + Log.debug(" set transform: " << this.transform << " ==> " << transform); + if (isnan(transform.getPosition().x()) == true) { + Log.critical(" set transform: " << this.transform << " ==> " << transform); + } + if (isinf(transform.getOrientation().z()) == true) { + Log.critical(" set transform: " << this.transform << " ==> " << transform); + } + this.transform = transform; + vec3 oldCenterOfMass = this.centerOfMassWorld; + // Compute the new center of mass in world-space coordinates + this.centerOfMassWorld = this.transform * this.centerOfMassLocal; + // Update the linear velocity of the center of mass + this.linearVelocity += this.angularVelocity.cross(this.centerOfMassWorld - oldCenterOfMass); + updateBroadPhaseState(); +} + +void RigidBody::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 == STATIC || this.type == KINEMATIC) { + this.centerOfMassWorld = this.transform.getPosition(); + return; + } + assert(this.type == DYNAMIC); + // Compute the total mass of the body + for (ProxyShape* shape = this.proxyCollisionShapes; shape != NULL; shape = shape->this.next) { + this.initMass += shape->getMass(); + this.centerOfMassLocal += shape->getLocalToBodyTransform().getPosition() * 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 + vec3 oldCenterOfMass = this.centerOfMassWorld; + this.centerOfMassLocal *= this.massInverse; + this.centerOfMassWorld = this.transform * this.centerOfMassLocal; + // Compute the total mass and inertia tensor using all the collision shapes + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + // Get the inertia tensor of the collision shape in its local-space + etk::Matrix3x3 inertiaTensor; + shape->getCollisionShape()->computeLocalInertiaTensor(inertiaTensor, shape->getMass()); + // Convert the collision shape inertia tensor into the local-space of the body + etk::Transform3D shapeTransform = shape->getLocalToBodyTransform(); + etk::Matrix3x3 rotationMatrix = shapeTransform.getOrientation().getMatrix(); + inertiaTensor = rotationMatrix * inertiaTensor * rotationMatrix.getTranspose(); + // 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. + vec3 offset = shapeTransform.getPosition() - this.centerOfMassLocal; + float offsetSquare = offset.length2(); + vec3 off1 = offset * (-offset.x()); + vec3 off2 = offset * (-offset.y()); + vec3 off3 = offset * (-offset.z()); + etk::Matrix3x3 offsetMatrix(off1.x()+offsetSquare, off1.y(), off1.z(), + off2.x(), off2.y()+offsetSquare, off2.z(), + off3.x(), off3.y(), off3.z()+offsetSquare); + offsetMatrix *= shape->getMass(); + this.inertiaTensorLocal += inertiaTensor + offsetMatrix; + } + // Compute the local inverse inertia tensor + this.inertiaTensorLocalInverse = this.inertiaTensorLocal.getInverse(); + // Update the linear velocity of the center of mass + this.linearVelocity += this.angularVelocity.cross(this.centerOfMassWorld - oldCenterOfMass); +} + + +void RigidBody::updateBroadPhaseState() { + PROFILE("RigidBody::updateBroadPhaseState()"); + DynamicsWorld world = staticcast(this.world); + vec3 displacement = world.this.timeStep * this.linearVelocity; + // For all the proxy collision shapes of the body + for (ProxyShape* shape = this.proxyCollisionShapes; shape != null; shape = shape->this.next) { + // Recompute the world-space AABB of the collision shape + AABB aabb; + Log.verbose(" : " << aabb.getMin() << " " << aabb.getMax()); + Log.verbose(" this.transform: " << this.transform); + shape->getCollisionShape()->computeAABB(aabb, this.transform *shape->getLocalToBodyTransform()); + Log.verbose(" : " << aabb.getMin() << " " << aabb.getMax()); + // Update the broad-phase state for the proxy collision shape + this.world.this.collisionDetection.updateProxyCollisionShape(shape, aabb, displacement); + } +} + + +void RigidBody::applyForceToCenterOfMass( vec3 force) { + if (this.type != DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalForce += force; +} + +void RigidBody::applyForce( vec3 force, vec3 point) { + if (this.type != DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalForce += force; + this.externalTorque += (point - this.centerOfMassWorld).cross(force); +} + +void RigidBody::applyTorque( vec3 torque) { + if (this.type != DYNAMIC) { + return; + } + if (this.isSleeping) { + setIsSleeping(false); + } + this.externalTorque += torque; +} \ No newline at end of file diff --git a/src/org/atriaSoft/ephysics/body/RigidBody.hpp b/src/org/atriaSoft/ephysics/body/RigidBody.hpp new file mode 100644 index 0000000..4a7c85d --- /dev/null +++ b/src/org/atriaSoft/ephysics/body/RigidBody.hpp @@ -0,0 +1,294 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + + // Class declarations + struct JointListElement; + class Joint; + class DynamicsWorld; + + /** + * @brief 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. + */ + class RigidBody : public CollisionBody { + protected : + float this.initMass; //!< Intial mass of the body + vec3 this.centerOfMassLocal; //!< Center of mass of the body in local-space coordinates. The center of mass can therefore be different from the body origin + vec3 this.centerOfMassWorld; //!< Center of mass of the body in world-space coordinates + vec3 this.linearVelocity; //!< Linear velocity of the body + vec3 this.angularVelocity; //!< Angular velocity of the body + vec3 this.externalForce; //!< Current external force on the body + vec3 this.externalTorque; //!< Current external torque on the body + etk::Matrix3x3 this.inertiaTensorLocal; //!< Local inertia tensor of the body (in local-space) with respect to the center of mass of the body + etk::Matrix3x3 this.inertiaTensorLocalInverse; //!< Inverse of the inertia tensor of the body + float this.massInverse; //!< Inverse of the mass of the body + boolean this.isGravityEnabled; //!< True if the gravity needs to be applied to this rigid body + Material this.material; //!< Material properties of the rigid body + float this.linearDamping; //!< Linear velocity damping factor + float this.angularDamping; //!< Angular velocity damping factor + JointListElement* this.jointsList; //!< First element of the linked list of joints involving this body + /// Private copy-ructor + RigidBody( RigidBody body); + /// Private assignment operator + RigidBody operator=( RigidBody body); + /** + * @brief Remove a joint from the joints list + */ + void removeJointFrothis.jointsList( Joint* joint); + /** + * @brief Update the transform of the body after a change of the center of mass + */ + void updateTransformWithCenterOfMass(); + void updateBroadPhaseState() override; + public : + /** + * @brief 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 + */ + RigidBody( etk::Transform3D transform, CollisionWorld world, long id); + /** + * @brief Virtual Destructor + */ + virtual ~RigidBody(); + void setType(BodyType type) override; + /** + * @brief Set the current position and orientation + * @param[in] transform The transformation of the body that transforms the local-space of the body into world-space + */ + void setTransform( etk::Transform3D transform) override; + /** + * @brief Get the mass of the body + * @return The mass (in kilograms) of the body + */ + float getMass() { + return this.initMass; + } + /** + * @brief Get the linear velocity + * @return The linear velocity vector of the body + */ + vec3 getLinearVelocity() { + return this.linearVelocity; + } + /** + * @brief Set the linear velocity of the rigid body. + * @param[in] linearVelocity Linear velocity vector of the body + */ + void setLinearVelocity( vec3 linearVelocity); + /** + * @brief Get the angular velocity of the body + * @return The angular velocity vector of the body + */ + vec3 getAngularVelocity() { + return this.angularVelocity; + } + /** + * @brief Set the angular velocity. + * @param[in] angularVelocity The angular velocity vector of the body + */ + void setAngularVelocity( vec3 angularVelocity); + /** + * @brief Set the variable to know whether or not the body is sleeping + * @param[in] isSleeping New sleeping state of the body + */ + virtual void setIsSleeping(boolean isSleeping); + /** + * @brief Get the local inertia tensor of the body (in local-space coordinates) + * @return The 3x3 inertia tensor matrix of the body + */ + etk::Matrix3x3 getInertiaTensorLocal() { + return this.inertiaTensorLocal; + } + /** + * @brief Set the local inertia tensor of the body (in local-space coordinates) + * @param[in] inertiaTensorLocal The 3x3 inertia tensor matrix of the body in local-space coordinates + */ + void setInertiaTensorLocal( etk::Matrix3x3 inertiaTensorLocal); + /** + * @brief Set the local center of mass of the body (in local-space coordinates) + * @param[in] centerOfMassLocal The center of mass of the body in local-space coordinates + */ + void setCenterOfMassLocal( vec3 centerOfMassLocal); + /** + * @brief Set the mass of the rigid body + * @param[in] mass The mass (in kilograms) of the body + */ + void setMass(float mass); + /** + * @brief Get the inertia tensor in world coordinates. + * The inertia tensor Iw in world coordinates is computed + * with the local inertia tensor Ib in body coordinates + * by Iw = R * Ib * 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 + */ + etk::Matrix3x3 getInertiaTensorWorld() { + // Compute and return the inertia tensor in world coordinates + return this.transform.getOrientation().getMatrix() * this.inertiaTensorLocal * + this.transform.getOrientation().getMatrix().getTranspose(); + } + /** + * @brief Get the inverse of the inertia tensor in world coordinates. + * The inertia tensor Iw in world coordinates is computed with the + * local inverse inertia tensor Ib^-1 in body coordinates + * by Iw = R * Ib^-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 + */ + etk::Matrix3x3 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 + return this.transform.getOrientation().getMatrix() * this.inertiaTensorLocalInverse * + this.transform.getOrientation().getMatrix().getTranspose(); + } + /** + * @brief get the need of gravity appling to this rigid body + * @return True if the gravity is applied to the body + */ + boolean isGravityEnabled() { + return this.isGravityEnabled; + } + /** + * @brief Set the variable to know if the gravity is applied to this rigid body + * @param[in] isEnabled True if you want the gravity to be applied to this body + */ + void enableGravity(boolean isEnabled) { + this.isGravityEnabled = isEnabled; + } + /** + * @brief get a reference to the material properties of the rigid body + * @return A reference to the material of the body + */ + Material getMaterial() { + return this.material; + } + /** + * @brief Set a new material for this rigid body + * @param[in] material The material you want to set to the body + */ + void setMaterial( Material material) { + this.material = material; + } + /** + * @brief Get the linear velocity damping factor + * @return The linear damping factor of this body + */ + float getLinearDamping() { + return this.linearDamping; + } + /** + * @brief Set the linear damping factor. This is the ratio of the linear velocity that the body will lose every at seconds of simulation. + * @param[in] linearDamping The linear damping factor of this body + */ + void setLinearDamping(float linearDamping) { + assert(linearDamping >= 0.0f); + this.linearDamping = linearDamping; + } + /** + * @brief Get the angular velocity damping factor + * @return The angular damping factor of this body + */ + float getAngularDamping() { + return this.angularDamping; + } + /** + * @brief Set the angular damping factor. This is the ratio of the angular velocity that the body will lose at every seconds of simulation. + * @param[in] angularDamping The angular damping factor of this body + */ + void setAngularDamping(float angularDamping) { + assert(angularDamping >= 0.0f); + this.angularDamping = angularDamping; + } + /** + * @brief 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 + */ + JointListElement* getJointsList() { + return this.jointsList; + } + /** + * @brief 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 + */ + JointListElement* getJointsList() { + return this.jointsList; + } + /** + * @brief 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[in] force The external force to apply on the center of mass of the body + */ + void applyForceToCenterOfMass( vec3 force); + /** + * @brief 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[in] force The force to apply on the body + * @param[in] point The point where the force is applied (in world-space coordinates) + */ + void applyForce( vec3 force, vec3 point); + /** + * @brief 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[in] torque The external torque to apply on the body + */ + void applyTorque( vec3 torque); + /** + * @brief 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[in] collisionShape 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 into the local-space of the body + * @param[in] 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. + */ + ProxyShape* addCollisionShape(CollisionShape* collisionShape, + etk::Transform3D transform, + float mass); + virtual void removeCollisionShape( ProxyShape* proxyShape) override; + /** + * @brief Recompute the center of mass, total mass and inertia tensor of the body using all the collision shapes attached to the body. + */ + void recomputeMassInformation(); + friend class DynamicsWorld; + friend class ContactSolver; + friend class BallAndSocketJoint; + friend class SliderJoint; + friend class HingeJoint; + friend class FixedJoint; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/CollisionDetection.cpp b/src/org/atriaSoft/ephysics/collision/CollisionDetection.cpp new file mode 100644 index 0000000..a861049 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/CollisionDetection.cpp @@ -0,0 +1,458 @@ +/** @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) + */ +#include +#include +#include +#include +#include +#include + +// We want to use the ReactPhysics3D namespace +using namespace ephysics; +using namespace std; + +// Constructor +CollisionDetection::CollisionDetection(CollisionWorld* world): + this.world(world), + this.broadPhaseAlgorithm(*this), + this.isCollisionShapesAdded(false) { + // Set the default collision dispatch configuration + setCollisionDispatch(this.defaultCollisionDispatch); + // Fill-in the collision detection matrix with algorithms + fillInCollisionMatrix(); +} + +CollisionDetection::~CollisionDetection() { + +} + +void CollisionDetection::computeCollisionDetection() { + PROFILE("CollisionDetection::computeCollisionDetection()"); + // Compute the broad-phase collision detection + computeBroadPhase(); + // Compute the narrow-phase collision detection + computeNarrowPhase(); +} + +void CollisionDetection::testCollisionBetweenShapes(CollisionCallback* callback, etk::Set shapes1, etk::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); +} + +void CollisionDetection::reportCollisionBetweenShapes(CollisionCallback* callback, etk::Set shapes1, etk::Set shapes2) { + // For each possible collision pair of bodies + etk::Map::Iterator it; + for (it = this.overlappingPairs.begin(); it != this.overlappingPairs.end(); ++it) { + OverlappingPair* pair = it->second; + ProxyShape* shape1 = pair->getShape1(); + ProxyShape* shape2 = pair->getShape2(); + assert(shape1->this.broadPhaseID != shape2->this.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.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj !shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj ( shapes1.count(shape1->this.broadPhaseID) == 0 + || shapes2.count(shape2->this.broadPhaseID) == 0 ) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj ( shapes1.count(shape2->this.broadPhaseID) == 0 + || shapes2.count(shape1->this.broadPhaseID) == 0 ) ) { + continue; + } + if ( !shapes1.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.count(shape1->this.broadPhaseID) == 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.count(shape2->this.broadPhaseID) == 0) { + continue; + } + if ( !shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.count(shape1->this.broadPhaseID) == 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.count(shape2->this.broadPhaseID) == 0) { + continue; + } + // For each contact manifold set of the overlapping pair + ContactManifoldSet manifoldSet = pair->getContactManifoldSet(); + for (int j=0; jgetNbContactPoints(); i++) { + ContactPoint* contactPoint = manifold->getContactPoint(i); + // Create the contact info object for the contact + ContactPointInfo contactInfo(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); + } + } + } + } +} + +void CollisionDetection::computeBroadPhase() { + PROFILE("CollisionDetection::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(); + } +} + +void CollisionDetection::computeNarrowPhase() { + PROFILE("CollisionDetection::computeNarrowPhase()"); + // Clear the set of overlapping pairs in narrow-phase contact + this.contactOverlappingPairs.clear(); + // For each possible collision pair of bodies + etk::Map::Iterator it; + for (it = this.overlappingPairs.begin(); it != this.overlappingPairs.end(); ) { + OverlappingPair* pair = it->second; + ProxyShape* shape1 = pair->getShape1(); + ProxyShape* shape2 = pair->getShape2(); + assert(shape1->this.broadPhaseID != shape2->this.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 + ETKDELETE(OverlappingPair, it->second); + it->second = null; + it = this.overlappingPairs.erase(it); + continue; + } else { + ++it; + } + CollisionBody* body1 = shape1->getBody(); + 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 + boolean isBody1Active = !body1->isSleeping() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj body1->getType() != STATIC; + boolean isBody2Active = !body2->isSleeping() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj body2->getType() != STATIC; + if (!isBody1Active hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj !isBody2Active) { + continue; + } + // Check if the bodies are in the set of bodies that cannot collide between each other + longpair 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 + CollisionShapeType shape1Type = shape1->getCollisionShape()->getType(); + CollisionShapeType shape2Type = shape2->getCollisionShape()->getType(); + NarrowPhaseAlgorithm* narrowPhaseAlgorithm = this.collisionMatrix[shape1Type][shape2Type]; + // 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 + CollisionShapeInfo shape1Info(shape1, shape1->getCollisionShape(), shape1->getLocalToWorldTransform(), + pair, shape1->getCachedCollisionData()); + CollisionShapeInfo shape2Info(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(); +} + +void CollisionDetection::computeNarrowPhaseBetweenShapes(CollisionCallback* callback, etk::Set shapes1, etk::Set shapes2) { + this.contactOverlappingPairs.clear(); + // For each possible collision pair of bodies + etk::Map::Iterator it; + for (it = this.overlappingPairs.begin(); it != this.overlappingPairs.end(); ) { + OverlappingPair* pair = it->second; + ProxyShape* shape1 = pair->getShape1(); + ProxyShape* shape2 = pair->getShape2(); + assert(shape1->this.broadPhaseID != shape2->this.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.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj !shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj ( shapes1.count(shape1->this.broadPhaseID) == 0 + || shapes2.count(shape2->this.broadPhaseID) == 0 ) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj ( shapes1.count(shape2->this.broadPhaseID) == 0 + || shapes2.count(shape1->this.broadPhaseID) == 0 ) ) { + ++it; + continue; + } + if ( !shapes1.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.count(shape1->this.broadPhaseID) == 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.count(shape2->this.broadPhaseID) == 0) { + ++it; + continue; + } + if ( !shapes2.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes1.empty() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.count(shape1->this.broadPhaseID) == 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapes2.count(shape2->this.broadPhaseID) == 0) { + ++it; + 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 + ETKDELETE(OverlappingPair, it->second); + it->second = null; + it = this.overlappingPairs.erase(it); + continue; + } else { + ++it; + } + CollisionBody* body1 = shape1->getBody(); + 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() != DYNAMIC hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj body2->getType() != DYNAMIC) { + continue; + } + longpair 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() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj body2->isSleeping()) { + continue; + } + // Select the narrow phase algorithm to use according to the two collision shapes + CollisionShapeType shape1Type = shape1->getCollisionShape()->getType(); + CollisionShapeType shape2Type = shape2->getCollisionShape()->getType(); + NarrowPhaseAlgorithm* narrowPhaseAlgorithm = this.collisionMatrix[shape1Type][shape2Type]; + // 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 + CollisionShapeInfo shape1Info(shape1, + shape1->getCollisionShape(), + shape1->getLocalToWorldTransform(), + pair, + shape1->getCachedCollisionData()); + CollisionShapeInfo shape2Info(shape2, + shape2->getCollisionShape(), + shape2->getLocalToWorldTransform(), + pair, + shape2->getCachedCollisionData()); + TestCollisionBetweenShapesCallback narrowPhaseCallback(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 CollisionDetection::broadPhaseNotifyOverlappingPair(ProxyShape* shape1, ProxyShape* shape2) { + assert(shape1->this.broadPhaseID != shape2->this.broadPhaseID); + // If the two proxy collision shapes are from the same body, skip it + if (shape1->getBody()->getID() == shape2->getBody()->getID()) { + return; + } + // Check if the collision filtering allows collision between the two shapes + if ( (shape1->getCollideWithMaskBits() shape2->getCollisionCategoryBits()) == 0 + || (shape1->getCollisionCategoryBits() shape2->getCollideWithMaskBits()) == 0) { + return; + } + // Compute the overlapping pair ID + overlappingpairid pairID = OverlappingPair::computeID(shape1, shape2); + // Check if the overlapping pair already exists + if (this.overlappingPairs.find(pairID) != this.overlappingPairs.end()) return; + // Compute the maximum number of contact manifolds for this pair + int nbMaxManifolds = CollisionShape::computeNbMaxContactManifolds(shape1->getCollisionShape()->getType(), + shape2->getCollisionShape()->getType()); + // Create the overlapping pair and add it into the set of overlapping pairs + OverlappingPair* newPair = ETKNEW(OverlappingPair, shape1, shape2, nbMaxManifolds); + assert(newPair != null); + this.overlappingPairs.set(pairID, newPair); + // Wake up the two bodies + shape1->getBody()->setIsSleeping(false); + shape2->getBody()->setIsSleeping(false); +} + +void CollisionDetection::removeProxyCollisionShape(ProxyShape* proxyShape) { + // Remove all the overlapping pairs involving this proxy shape + etk::Map::Iterator it; + for (it = this.overlappingPairs.begin(); it != this.overlappingPairs.end(); ) { + if (it->second->getShape1()->this.broadPhaseID == proxyShape->this.broadPhaseID|| + it->second->getShape2()->this.broadPhaseID == proxyShape->this.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 + ETKDELETE(OverlappingPair, it->second); + it->second = null; + it = this.overlappingPairs.erase(it); + } else { + ++it; + } + } + // Remove the body from the broad-phase + this.broadPhaseAlgorithm.removeProxyCollisionShape(proxyShape); +} + +void CollisionDetection::notifyContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo) { + // If it is the first contact since the pairs are overlapping + if (overlappingPair->getNbContactPoints() == 0) { + // Trigger a callback event + if (this.world->this.eventListener != NULL) { + this.world->this.eventListener->beginContact(contactInfo); + } + } + // Create a new contact + createContact(overlappingPair, contactInfo); + // Trigger a callback event for the new contact + if (this.world->this.eventListener != NULL) { + this.world->this.eventListener->newContact(contactInfo); + } +} + +void CollisionDetection::createContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo) { + // Create a new contact + ContactPoint* contact = ETKNEW(ContactPoint, contactInfo); + // Add the contact to the contact manifold set of the corresponding overlapping pair + overlappingPair->addContact(contact); + // Add the overlapping pair into the set of pairs in contact during narrow-phase + overlappingpairid pairId = OverlappingPair::computeID(overlappingPair->getShape1(), + overlappingPair->getShape2()); + this.contactOverlappingPairs.set(pairId, overlappingPair); +} + +void CollisionDetection::addAllContactManifoldsToBodies() { + // For each overlapping pairs in contact during the narrow-phase + etk::Map::Iterator it; + for (it = this.contactOverlappingPairs.begin(); it != this.contactOverlappingPairs.end(); ++it) { + // Add all the contact manifolds of the pair into the list of contact manifolds + // of the two bodies involved in the contact + addContactManifoldToBody(it->second); + } +} + +void CollisionDetection::addContactManifoldToBody(OverlappingPair* pair) { + assert(pair != null); + CollisionBody* body1 = pair->getShape1()->getBody(); + CollisionBody* body2 = pair->getShape2()->getBody(); + ContactManifoldSet manifoldSet = pair->getContactManifoldSet(); + // For each contact manifold in the set of manifolds in the pair + for (int i=0; igetNbContactPoints() > 0); + // Add the contact manifold at the beginning of the linked + // list of contact manifolds of the first body + body1->this.contactManifoldsList = ETKNEW(ContactManifoldListElement, contactManifold, body1->this.contactManifoldsList);; + // Add the contact manifold at the beginning of the linked + // list of the contact manifolds of the second body + body2->this.contactManifoldsList = ETKNEW(ContactManifoldListElement, contactManifold, body2->this.contactManifoldsList);; + } +} + +void CollisionDetection::clearContactPoints() { + // For each overlapping pair + etk::Map::Iterator it; + for (it = this.overlappingPairs.begin(); it != this.overlappingPairs.end(); ++it) { + it->second->clearContactPoints(); + } +} + +void CollisionDetection::fillInCollisionMatrix() { + // For each possible type of collision shape + for (int i=0; iselectAlgorithm(i, j); + } + } +} + +EventListener* CollisionDetection::getWorldEventListener() { + return this.world->this.eventListener; +} + +void TestCollisionBetweenShapesCallback::notifyContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo) { + this.collisionCallback->notifyContact(contactInfo); +} + +NarrowPhaseAlgorithm* CollisionDetection::getCollisionAlgorithm(CollisionShapeType shape1Type, CollisionShapeType shape2Type) { + return this.collisionMatrix[shape1Type][shape2Type]; +} + +void CollisionDetection::setCollisionDispatch(CollisionDispatch* collisionDispatch) { + this.collisionDispatch = collisionDispatch; + this.collisionDispatch->init(this); + // Fill-in the collision matrix with the new algorithms to use + fillInCollisionMatrix(); +} + +void CollisionDetection::addProxyCollisionShape(ProxyShape* proxyShape, AABB aabb) { + // Add the body to the broad-phase + this.broadPhaseAlgorithm.addProxyCollisionShape(proxyShape, aabb); + this.isCollisionShapesAdded = true; +} + +void CollisionDetection::addNoCollisionPair(CollisionBody* body1, CollisionBody* body2) { + this.noCollisionPairs.set(OverlappingPair::computeBodiesIndexPair(body1, body2)); +} + +void CollisionDetection::removeNoCollisionPair(CollisionBody* body1, CollisionBody* body2) { + this.noCollisionPairs.erase(this.noCollisionPairs.find(OverlappingPair::computeBodiesIndexPair(body1, body2))); +} + +void CollisionDetection::askForBroadPhaseCollisionCheck(ProxyShape* shape) { + this.broadPhaseAlgorithm.addMovedCollisionShape(shape->this.broadPhaseID); +} + +void CollisionDetection::updateProxyCollisionShape(ProxyShape* shape, AABB aabb, vec3 displacement, boolean forceReinsert) { + this.broadPhaseAlgorithm.updateProxyCollisionShape(shape, aabb, displacement); +} + +void CollisionDetection::raycast(RaycastCallback* raycastCallback, Ray ray, unsigned short raycastWithCategoryMaskBits) { + PROFILE("CollisionDetection::raycast()"); + RaycastTest 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); +} + +boolean CollisionDetection::testAABBOverlap( ProxyShape* shape1, 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); +} + +CollisionWorld* CollisionDetection::getWorld() { + return this.world; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/CollisionDetection.hpp b/src/org/atriaSoft/ephysics/collision/CollisionDetection.hpp new file mode 100644 index 0000000..7771a4e --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/CollisionDetection.hpp @@ -0,0 +1,143 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + + class BroadPhaseAlgorithm; + class CollisionWorld; + class CollisionCallback; + + class TestCollisionBetweenShapesCallback : public NarrowPhaseCallback { + private: + CollisionCallback* this.collisionCallback; //!< + public: + // Constructor + TestCollisionBetweenShapesCallback(CollisionCallback* callback): + this.collisionCallback(callback) { + + } + // Called by a narrow-phase collision algorithm when a new contact has been found + virtual void notifyContact(OverlappingPair* overlappingPair, + ContactPointInfo contactInfo); + }; + + /** + * @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. + */ + class CollisionDetection : public NarrowPhaseCallback { + private : + CollisionDispatch* this.collisionDispatch; //!< Collision Detection Dispatch configuration + DefaultCollisionDispatch this.defaultCollisionDispatch; //!< Default collision dispatch configuration + NarrowPhaseAlgorithm* this.collisionMatrix[NBCOLLISIONSHAPETYPES][NBCOLLISIONSHAPETYPES]; //!< Collision detection matrix (algorithms to use) + CollisionWorld* this.world; //!< Pointer to the physics world + etk::Map this.overlappingPairs; //!< Broad-phase overlapping pairs + etk::Map this.contactOverlappingPairs; //!< Overlapping pairs in contact (during the current Narrow-phase collision detection) + BroadPhaseAlgorithm this.broadPhaseAlgorithm; //!< Broad-phase algorithm + // TODO : Delete this + GJKAlgorithm this.narrowPhaseGJKAlgorithm; //!< Narrow-phase GJK algorithm + etk::Set this.noCollisionPairs; //!< Set of pair of bodies that cannot collide between each other + boolean this.isCollisionShapesAdded; //!< True if some collision shapes have been added previously + /// Private copy-ructor + CollisionDetection( CollisionDetection collisionDetection); + /// Private assignment operator + CollisionDetection operator=( CollisionDetection collisionDetection); + /// Compute the broad-phase collision detection + void computeBroadPhase(); + /// Compute the narrow-phase collision detection + void computeNarrowPhase(); + /// Add a contact manifold to the linked list of contact manifolds of the two bodies + /// involed in the corresponding contact. + void addContactManifoldToBody(OverlappingPair* pair); + /// Delete all the contact points in the currently overlapping pairs + void clearContactPoints(); + /// Fill-in the collision detection matrix + void fillInCollisionMatrix(); + /// Add all the contact manifold of colliding pairs to their bodies + void addAllContactManifoldsToBodies(); + public : + /// Constructor + CollisionDetection(CollisionWorld* world); + /// Destructor + ~CollisionDetection(); + /// Set the collision dispatch configuration + void setCollisionDispatch(CollisionDispatch* collisionDispatch); + /// Return the Narrow-phase collision detection algorithm to use between two types of shapes + NarrowPhaseAlgorithm* getCollisionAlgorithm(CollisionShapeType shape1Type, + CollisionShapeType shape2Type) ; + /// Add a proxy collision shape to the collision detection + void addProxyCollisionShape(ProxyShape* proxyShape, AABB aabb); + /// Remove a proxy collision shape from the collision detection + void removeProxyCollisionShape(ProxyShape* proxyShape); + /// Update a proxy collision shape (that has moved for instance) + void updateProxyCollisionShape(ProxyShape* shape, + AABB aabb, + vec3 displacement = vec3(0, 0, 0), + boolean forceReinsert = false); + /// Add a pair of bodies that cannot collide with each other + void addNoCollisionPair(CollisionBody* body1, CollisionBody* body2); + /// Remove a pair of bodies that cannot collide with each other + void removeNoCollisionPair(CollisionBody* body1, CollisionBody* body2); + // 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. + void askForBroadPhaseCollisionCheck(ProxyShape* shape); + /// Compute the collision detection + void computeCollisionDetection(); + /// Compute the collision detection + void testCollisionBetweenShapes(CollisionCallback* callback, + etk::Set shapes1, + etk::Set shapes2); + /// Report collision between two sets of shapes + void reportCollisionBetweenShapes(CollisionCallback* callback, + etk::Set shapes1, + etk::Set shapes2) ; + /// Ray casting method + void raycast(RaycastCallback* raycastCallback, + Ray ray, + unsigned short raycastWithCategoryMaskBits) ; + /// Test if the AABBs of two bodies overlap + boolean testAABBOverlap( CollisionBody* body1, + CollisionBody* body2) ; + /// Test if the AABBs of two proxy shapes overlap + boolean testAABBOverlap( ProxyShape* shape1, + ProxyShape* shape2) ; + /// Allow the broadphase to notify the collision detection about an overlapping pair. + /// This method is called by the broad-phase collision detection algorithm + void broadPhaseNotifyOverlappingPair(ProxyShape* shape1, ProxyShape* shape2); + /// Compute the narrow-phase collision detection + void computeNarrowPhaseBetweenShapes(CollisionCallback* callback, + etk::Set shapes1, + etk::Set shapes2); + /// Return a pointer to the world + CollisionWorld* getWorld(); + /// Return the world event listener + EventListener* getWorldEventListener(); + /// Called by a narrow-phase collision algorithm when a new contact has been found + virtual void notifyContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo) override; + /// Create a new contact + void createContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo); + friend class DynamicsWorld; + friend class ConvexMeshShape; + }; + +} diff --git a/src/org/atriaSoft/ephysics/collision/CollisionShapeInfo.hpp b/src/org/atriaSoft/ephysics/collision/CollisionShapeInfo.hpp new file mode 100644 index 0000000..91b1890 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/CollisionShapeInfo.hpp @@ -0,0 +1,42 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + class OverlappingPair; + /** + * @brief It regroups different things about a collision shape. This is + * used to pass information about a collision shape to a collision algorithm. + */ + struct CollisionShapeInfo { + public: + OverlappingPair* overlappingPair; //!< Broadphase overlapping pair + ProxyShape* proxyShape; //!< Proxy shape + CollisionShape* collisionShape; //!< Pointer to the collision shape + etk::Transform3D shapeToWorldTransform; //!< etk::Transform3D that maps from collision shape local-space to world-space + void** cachedCollisionData; //!< Cached collision data of the proxy shape + /// Constructor + CollisionShapeInfo(ProxyShape* proxyCollisionShape, + CollisionShape* shape, + etk::Transform3D shapeLocalToWorldTransform, + OverlappingPair* pair, + void** cachedData): + overlappingPair(pair), + proxyShape(proxyCollisionShape), + collisionShape(shape), + shapeToWorldTransform(shapeLocalToWorldTransform), + cachedCollisionData(cachedData) { + + } + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/ContactManifold.cpp b/src/org/atriaSoft/ephysics/collision/ContactManifold.cpp new file mode 100644 index 0000000..8486d66 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ContactManifold.cpp @@ -0,0 +1,310 @@ +/** @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) + */ +#include + +using namespace ephysics; + +ContactManifold::ContactManifold(ProxyShape* shape1, + ProxyShape* shape2, + short normalDirectionId): + this.shape1(shape1), + this.shape2(shape2), + this.normalDirectionId(normalDirectionId), + this.nbContactPoints(0), + this.frictionImpulse1(0.0), + this.frictionImpulse2(0.0), + this.frictionTwistImpulse(0.0), + this.isAlreadyInIsland(false) { + +} + +ContactManifold::~ContactManifold() { + clear(); +} + +void ContactManifold::addContactPoint(ContactPoint* contact) { + // For contact already in the manifold + for (int i=0; igetWorldPointOnBody1() - contact->getWorldPointOnBody1()).length2(); + if (distance <= PERSISTENTCONTACTDISTTHRESHOLD*PERSISTENTCONTACTDISTTHRESHOLD) { + // Delete the new contact + ETKDELETE(ContactPoint, contact); + assert(this.nbContactPoints > 0); + return; + } + } + // If the contact manifold is full + if (this.nbContactPoints == MAXCONTACTPOINTSINMANIFOLD) { + int indexMaxPenetration = getIndexOfDeepestPenetration(contact); + 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); +} + +void ContactManifold::removeContactPoint(int index) { + assert(index < this.nbContactPoints); + assert(this.nbContactPoints > 0); + // Call the destructor explicitly and tell the memory allocator that + // the corresponding memory block is now free + ETKDELETE(ContactPoint, this.contactPoints[index]); + 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--; +} + +void ContactManifold::update( etk::Transform3D transform1, etk::Transform3D transform2) { + if (this.nbContactPoints == 0) { + return; + } + // Update the world coordinates and penetration depth of the contact points in the manifold + for (int i=0; isetWorldPointOnBody1(transform1 * this.contactPoints[i]->getLocalPointOnBody1()); + this.contactPoints[i]->setWorldPointOnBody2(transform2 * this.contactPoints[i]->getLocalPointOnBody2()); + this.contactPoints[i]->setPenetrationDepth((this.contactPoints[i]->getWorldPointOnBody1() - this.contactPoints[i]->getWorldPointOnBody2()).dot(this.contactPoints[i]->getNormal())); + } + float squarePersistentContactThreshold = PERSISTENTCONTACTDISTTHRESHOLD * PERSISTENTCONTACTDISTTHRESHOLD; + // Remove the contact points that don't represent very well the contact manifold + for (int i=staticcast(this.nbContactPoints)-1; i>=0; i--) { + assert(i < staticcast(this.nbContactPoints)); + // Compute the distance between contact points in the normal direction + float distanceNormal = -this.contactPoints[i]->getPenetrationDepth(); + // If the contacts points are too far from each other in the normal direction + if (distanceNormal > squarePersistentContactThreshold) { + removeContactPoint(i); + } else { + // Compute the distance of the two contact points in the plane + // orthogonal to the contact normal + vec3 projOfPoint1 = this.contactPoints[i]->getWorldPointOnBody1() + this.contactPoints[i]->getNormal() * distanceNormal; + vec3 projDifference = this.contactPoints[i]->getWorldPointOnBody2() - projOfPoint1; + // If the orthogonal distance is larger than the valid distance + // threshold, we remove the contact + if (projDifference.length2() > squarePersistentContactThreshold) { + removeContactPoint(i); + } + } + } +} + +int ContactManifold::getIndexOfDeepestPenetration(ContactPoint* newContact) { + assert(this.nbContactPoints == MAXCONTACTPOINTSINMANIFOLD); + int indexMaxPenetrationDepth = -1; + float maxPenetrationDepth = newContact->getPenetrationDepth(); + // For each contact in the cache + for (int i=0; igetPenetrationDepth() > maxPenetrationDepth) { + maxPenetrationDepth = this.contactPoints[i]->getPenetrationDepth(); + indexMaxPenetrationDepth = i; + } + } + // Return the index of largest penetration depth + return indexMaxPenetrationDepth; +} + +int ContactManifold::getIndexToRemove(int indexMaxPenetration, vec3 newPoint) { + assert(this.nbContactPoints == MAXCONTACTPOINTSINMANIFOLD); + 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 + vec3 vector1 = newPoint - this.contactPoints[1]->getLocalPointOnBody1(); + vec3 vector2 = this.contactPoints[3]->getLocalPointOnBody1() - this.contactPoints[2]->getLocalPointOnBody1(); + vec3 crossProduct = vector1.cross(vector2); + area0 = crossProduct.length2(); + } + if (indexMaxPenetration != 1) { + // Compute the area + vec3 vector1 = newPoint - this.contactPoints[0]->getLocalPointOnBody1(); + vec3 vector2 = this.contactPoints[3]->getLocalPointOnBody1() - this.contactPoints[2]->getLocalPointOnBody1(); + vec3 crossProduct = vector1.cross(vector2); + area1 = crossProduct.length2(); + } + if (indexMaxPenetration != 2) { + // Compute the area + vec3 vector1 = newPoint - this.contactPoints[0]->getLocalPointOnBody1(); + vec3 vector2 = this.contactPoints[3]->getLocalPointOnBody1() - this.contactPoints[1]->getLocalPointOnBody1(); + vec3 crossProduct = vector1.cross(vector2); + area2 = crossProduct.length2(); + } + if (indexMaxPenetration != 3) { + // Compute the area + vec3 vector1 = newPoint - this.contactPoints[0]->getLocalPointOnBody1(); + vec3 vector2 = this.contactPoints[2]->getLocalPointOnBody1() - this.contactPoints[1]->getLocalPointOnBody1(); + vec3 crossProduct = vector1.cross(vector2); + area3 = crossProduct.length2(); + } + // Return the index of the contact to remove + return getMaxArea(area0, area1, area2, area3); +} + +int ContactManifold::getMaxArea(float area0, float area1, float area2, 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; + } + } +} + +// Clear the contact manifold +void ContactManifold::clear() { + for (int iii=0; iiigetBody(); +} + +// Return a pointer to the second body of the contact manifold +CollisionBody* ContactManifold::getBody2() { + return this.shape2->getBody(); +} + +// Return the normal direction Id +int16t ContactManifold::getNormalDirectionId() { + return this.normalDirectionId; +} + +// Return the number of contact points in the manifold +int ContactManifold::getNbContactPoints() { + return this.nbContactPoints; +} + +// Return the first friction vector at the center of the contact manifold + vec3 ContactManifold::getFrictionVector1() { + return this.frictionVector1; +} + +// set the first friction vector at the center of the contact manifold +void ContactManifold::setFrictionVector1( vec3 frictionVector1) { + this.frictionVector1 = frictionVector1; +} + +// Return the second friction vector at the center of the contact manifold + vec3 ContactManifold::getFrictionvec2() { + return this.frictionvec2; +} + +// set the second friction vector at the center of the contact manifold +void ContactManifold::setFrictionvec2( vec3 frictionvec2) { + this.frictionvec2 = frictionvec2; +} + +// Return the first friction accumulated impulse +float ContactManifold::getFrictionImpulse1() { + return this.frictionImpulse1; +} + +// Set the first friction accumulated impulse +void ContactManifold::setFrictionImpulse1(float frictionImpulse1) { + this.frictionImpulse1 = frictionImpulse1; +} + +// Return the second friction accumulated impulse +float ContactManifold::getFrictionImpulse2() { + return this.frictionImpulse2; +} + +// Set the second friction accumulated impulse +void ContactManifold::setFrictionImpulse2(float frictionImpulse2) { + this.frictionImpulse2 = frictionImpulse2; +} + +// Return the friction twist accumulated impulse +float ContactManifold::getFrictionTwistImpulse() { + return this.frictionTwistImpulse; +} + +// Set the friction twist accumulated impulse +void ContactManifold::setFrictionTwistImpulse(float frictionTwistImpulse) { + this.frictionTwistImpulse = frictionTwistImpulse; +} + +// Set the accumulated rolling resistance impulse +void ContactManifold::setRollingResistanceImpulse( vec3 rollingResistanceImpulse) { + this.rollingResistanceImpulse = rollingResistanceImpulse; +} + +// Return a contact point of the manifold +ContactPoint* ContactManifold::getContactPoint(int index) { + assert(index < this.nbContactPoints); + return this.contactPoints[index]; +} + +// Return true if the contact manifold has already been added into an island +boolean ContactManifold::isAlreadyInIsland() { + return this.isAlreadyInIsland; +} + +// Return the normalized averaged normal vector +vec3 ContactManifold::getAverageContactNormal() { + vec3 averageNormal; + for (int i=0; igetNormal(); + } + return averageNormal.safeNormalized(); +} + +// Return the largest depth of all the contact points +float ContactManifold::getLargestContactDepth() { + float largestDepth = 0.0f; + for (int i=0; igetPenetrationDepth(); + if (depth > largestDepth) { + largestDepth = depth; + } + } + return largestDepth; +} diff --git a/src/org/atriaSoft/ephysics/collision/ContactManifold.hpp b/src/org/atriaSoft/ephysics/collision/ContactManifold.hpp new file mode 100644 index 0000000..02a9808 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ContactManifold.hpp @@ -0,0 +1,165 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include + +namespace ephysics { + + int MAXCONTACTPOINTSINMANIFOLD = 4; //!< Maximum number of contacts in the manifold + + class ContactManifold; + + /** + * @brief This structure represents a single element of a linked list of contact manifolds + */ + struct ContactManifoldListElement { + public: + ContactManifold* contactManifold; //!< Pointer to the actual contact manifold + ContactManifoldListElement* next; //!< Next element of the list + ContactManifoldListElement(ContactManifold* initContactManifold, + ContactManifoldListElement* initNext): + contactManifold(initContactManifold), + next(initNext) { + + } + }; + + /** + * @brief 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/GDC10CoumansErwinContact.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. + */ + class ContactManifold { + public: + /// Constructor + ContactManifold(ProxyShape* shape1, + ProxyShape* shape2, + int16t normalDirectionId); + /// Destructor + ~ContactManifold(); + /// DELETE copy-ructor + ContactManifold( ContactManifold contactManifold) = delete; + /// DELETE assignment operator + ContactManifold operator=( ContactManifold contactManifold) = delete; + private: + ProxyShape* this.shape1; //!< Pointer to the first proxy shape of the contact + ProxyShape* this.shape2; //!< Pointer to the second proxy shape of the contact + ContactPoint* this.contactPoints[MAXCONTACTPOINTSINMANIFOLD]; //!< Contact points in the manifold + int16t this.normalDirectionId; //!< Normal direction Id (Unique Id representing the normal direction) + int this.nbContactPoints; //!< Number of contacts in the cache + vec3 this.frictionVector1; //!< First friction vector of the contact manifold + vec3 this.frictionvec2; //!< Second friction vector of the contact manifold + float this.frictionImpulse1; //!< First friction raint accumulated impulse + float this.frictionImpulse2; //!< Second friction raint accumulated impulse + float this.frictionTwistImpulse; //!< Twist friction raint accumulated impulse + vec3 this.rollingResistanceImpulse; //!< Accumulated rolling resistance impulse + boolean this.isAlreadyInIsland; //!< True if the contact manifold has already been added into an island + /// Return the index of maximum area + int getMaxArea(float area0, float area1, float area2, float area3) ; + /** + * @brief 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(ContactPoint* newContact) ; + /** + * @brief 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(int indexMaxPenetration, vec3 newPoint) ; + /// Remove a contact point from the manifold + void removeContactPoint(int index); + /// Return true if the contact manifold has already been added into an island + boolean isAlreadyInIsland() ; + public: + /// Return a pointer to the first proxy shape of the contact + ProxyShape* getShape1() ; + /// Return a pointer to the second proxy shape of the contact + ProxyShape* getShape2() ; + /// Return a pointer to the first body of the contact manifold + CollisionBody* getBody1() ; + /// Return a pointer to the second body of the contact manifold + CollisionBody* getBody2() ; + /// Return the normal direction Id + int16t getNormalDirectionId() ; + /// Add a contact point to the manifold + void addContactPoint(ContactPoint* contact); + /** + * @brief 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. + */ + void update( etk::Transform3D transform1, + etk::Transform3D transform2); + /// Clear the contact manifold + void clear(); + /// Return the number of contact points in the manifold + int getNbContactPoints() ; + /// Return the first friction vector at the center of the contact manifold + vec3 getFrictionVector1() ; + /// set the first friction vector at the center of the contact manifold + void setFrictionVector1( vec3 frictionVector1); + /// Return the second friction vector at the center of the contact manifold + vec3 getFrictionvec2() ; + /// set the second friction vector at the center of the contact manifold + void setFrictionvec2( vec3 frictionvec2); + /// Return the first friction accumulated impulse + float getFrictionImpulse1() ; + /// Set the first friction accumulated impulse + void setFrictionImpulse1(float frictionImpulse1); + /// Return the second friction accumulated impulse + float getFrictionImpulse2() ; + /// Set the second friction accumulated impulse + void setFrictionImpulse2(float frictionImpulse2); + /// Return the friction twist accumulated impulse + float getFrictionTwistImpulse() ; + /// Set the friction twist accumulated impulse + void setFrictionTwistImpulse(float frictionTwistImpulse); + /// Set the accumulated rolling resistance impulse + void setRollingResistanceImpulse( vec3 rollingResistanceImpulse); + /// Return a contact point of the manifold + ContactPoint* getContactPoint(int index) ; + /// Return the normalized averaged normal vector + vec3 getAverageContactNormal() ; + /// Return the largest depth of all the contact points + float getLargestContactDepth() ; + friend class DynamicsWorld; + friend class Island; + friend class CollisionBody; + }; + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.cpp b/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.cpp new file mode 100644 index 0000000..59b7770 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.cpp @@ -0,0 +1,197 @@ +/** @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) + */ +#include + +using namespace ephysics; + +ContactManifoldSet::ContactManifoldSet(ProxyShape* shape1, + ProxyShape* shape2, + int nbMaxManifolds): + this.nbMaxManifolds(nbMaxManifolds), + this.nbManifolds(0), + this.shape1(shape1), + this.shape2(shape2) { + assert(nbMaxManifolds >= 1); +} + +ContactManifoldSet::~ContactManifoldSet() { + clear(); +} + +void ContactManifoldSet::addContactPoint(ContactPoint* contact) { + // Compute an Id corresponding to the normal direction (using a cubemap) + int16t 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 i=0; igetNbContactPoints() > 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 i=0; igetNbContactPoints() > 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 i=0; igetLargestContactDepth(); + if (depth < minDepth) { + minDepth = depth; + smallestDepthIndex = i; + } + } + // If we do not want to keep to new manifold (not created yet) with the + // new contact point + if (smallestDepthIndex == -1) { + // Delete the new contact + ETKDELETE(ContactPoint, contact); + contact = null; + return; + } + assert(smallestDepthIndex >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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 i=0; igetNbContactPoints() > 0); + } + return; +} + +int ContactManifoldSet::selectManifoldWithSimilarNormal(int16t normalDirectionId) { + // Return the Id of the manifold with the same normal direction id (if exists) + for (int i=0; igetNormalDirectionId()) { + return i; + } + } + return -1; +} + +int16t ContactManifoldSet::computeCubemapNormalId( vec3 normal) { + assert(normal.length2() > FLTEPSILON); + int faceNo; + float u, v; + float max = max3(fabs(normal.x()), fabs(normal.y()), fabs(normal.z())); + vec3 normalScaled = normal / max; + if (normalScaled.x() >= normalScaled.y() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj normalScaled.x() >= normalScaled.z()) { + faceNo = normalScaled.x() > 0 ? 0 : 1; + u = normalScaled.y(); + v = normalScaled.z(); + } else if (normalScaled.y() >= normalScaled.x() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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 = floor(((u + 1)/2) * CONTACTCUBEMAPFACENBSUBDIVISIONS); + int indexV = floor(((v + 1)/2) * CONTACTCUBEMAPFACENBSUBDIVISIONS); + if (indexU == CONTACTCUBEMAPFACENBSUBDIVISIONS) { + indexU--; + } + if (indexV == CONTACTCUBEMAPFACENBSUBDIVISIONS) { + indexV--; + } + int nbSubDivInFace = CONTACTCUBEMAPFACENBSUBDIVISIONS * CONTACTCUBEMAPFACENB_SUBDIVISIONS; + return faceNo * 200 + indexU * nbSubDivInFace + indexV; +} + +void ContactManifoldSet::update() { + for (int i=this.nbManifolds-1; i>=0; i--) { + // Update the contact manifold + this.manifolds[i]->update(this.shape1->getBody()->getTransform() * this.shape1->getLocalToBodyTransform(), + this.shape2->getBody()->getTransform() * this.shape2->getLocalToBodyTransform()); + // Remove the contact manifold if has no contact points anymore + if (this.manifolds[i]->getNbContactPoints() == 0) { + removeManifold(i); + } + } +} + +void ContactManifoldSet::clear() { + for (int i=this.nbManifolds-1; i>=0; i--) { + removeManifold(i); + } + assert(this.nbManifolds == 0); +} + +void ContactManifoldSet::createManifold(int16t normalDirectionId) { + assert(this.nbManifolds < this.nbMaxManifolds); + this.manifolds[this.nbManifolds] = ETKNEW(ContactManifold, this.shape1, this.shape2, normalDirectionId); + this.nbManifolds++; +} + +void ContactManifoldSet::removeManifold(int index) { + assert(this.nbManifolds > 0); + assert(index >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < this.nbManifolds); + // Delete the new contact + ETKDELETE(ContactManifold, this.manifolds[index]); + this.manifolds[index] = null; + for (int i=index; (i+1) < this.nbManifolds; i++) { + this.manifolds[i] = this.manifolds[i+1]; + } + this.nbManifolds--; +} + +ProxyShape* ContactManifoldSet::getShape1() { + return this.shape1; +} + +ProxyShape* ContactManifoldSet::getShape2() { + return this.shape2; +} + +int ContactManifoldSet::getNbContactManifolds() { + return this.nbManifolds; +} + +ContactManifold* ContactManifoldSet::getContactManifold(int index) { + assert(index >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < this.nbManifolds); + return this.manifolds[index]; +} + +int ContactManifoldSet::getTotalNbContactPoints() { + int nbPoints = 0; + for (int i=0; igetNbContactPoints(); + } + return nbPoints; +} diff --git a/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.hpp b/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.hpp new file mode 100644 index 0000000..9bf3d1c --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ContactManifoldSet.hpp @@ -0,0 +1,65 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + int MAXMANIFOLDSINCONTACTMANIFOLDSET = 3; // Maximum number of contact manifolds in the set + int CONTACTCUBEMAPFACENBSUBDIVISIONS = 3; // N Number for the N x N subdivisions of the cubemap + /** + * @brief 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. + */ + class ContactManifoldSet { + private: + int this.nbMaxManifolds; //!< Maximum number of contact manifolds in the set + int this.nbManifolds; //!< Current number of contact manifolds in the set + ProxyShape* this.shape1; //!< Pointer to the first proxy shape of the contact + ProxyShape* this.shape2; //!< Pointer to the second proxy shape of the contact + ContactManifold* this.manifolds[MAXMANIFOLDSINCONTACTMANIFOLDSET]; //!< Contact manifolds of the set + /// Create a new contact manifold and add it to the set + void createManifold(short normalDirectionId); + /// Remove a contact manifold from the set + void removeManifold(int index); + // Return the index of the contact manifold with a similar average normal. + int selectManifoldWithSimilarNormal(int16t normalDirectionId) ; + // Map the normal vector into a cubemap face bucket (a face contains 4x4 buckets) + // Each face of the cube is divided into 4x4 buckets. This method maps the + // normal vector into of the of the bucket and returns a unique Id for the bucket + int16t computeCubemapNormalId( vec3 normal) ; + public: + /// Constructor + ContactManifoldSet(ProxyShape* shape1, + ProxyShape* shape2, + int nbMaxManifolds); + /// Destructor + ~ContactManifoldSet(); + /// Return the first proxy shape + ProxyShape* getShape1() ; + /// Return the second proxy shape + ProxyShape* getShape2() ; + /// Add a contact point to the manifold set + void addContactPoint(ContactPoint* contact); + /// Update the contact manifolds + void update(); + /// Clear the contact manifold set + void clear(); + /// Return the number of manifolds in the set + int getNbContactManifolds() ; + /// Return a given contact manifold + ContactManifold* getContactManifold(int index) ; + /// Return the total number of contact points in the set of manifolds + int getTotalNbContactPoints() ; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/ProxyShape.cpp b/src/org/atriaSoft/ephysics/collision/ProxyShape.cpp new file mode 100644 index 0000000..e38abf4 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ProxyShape.cpp @@ -0,0 +1,218 @@ +/** @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) + */ +#include + +using namespace ephysics; + +// Constructor +/** + * @param body Pointer to the parent body + * @param shape Pointer to the collision shape + * @param transform etk::Transform3Dation from collision shape local-space to body local-space + * @param mass Mass of the collision shape (in kilograms) + */ +ProxyShape::ProxyShape(CollisionBody* body, CollisionShape* shape, etk::Transform3D transform, float mass) + :this.body(body), this.collisionShape(shape), this.localToBodyTransform(transform), this.mass(mass), + this.next(NULL), this.broadPhaseID(-1), this.cachedCollisionData(NULL), this.userData(NULL), + this.collisionCategoryBits(0x0001), this.collideWithMaskBits(0xFFFF) { + +} + +// Destructor +ProxyShape::~ProxyShape() { + // Release the cached collision data memory + if (this.cachedCollisionData != NULL) { + free(this.cachedCollisionData); + } +} + +// Return true if a point is inside the collision shape +/** + * @param worldPoint Point to test in world-space coordinates + * @return True if the point is inside the collision shape + */ +boolean ProxyShape::testPointInside( vec3 worldPoint) { + etk::Transform3D localToWorld = this.body->getTransform() * this.localToBodyTransform; + vec3 localPoint = localToWorld.getInverse() * worldPoint; + return this.collisionShape->testPointInside(localPoint, this); +} + +// Raycast method with feedback information +/** + * @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 + */ +boolean ProxyShape::raycast( Ray ray, RaycastInfo raycastInfo) { + + // If the corresponding body is not active, it cannot be hit by rays + if (!this.body->isActive()) return false; + + // Convert the ray into the local-space of the collision shape + etk::Transform3D localToWorldTransform = getLocalToWorldTransform(); + etk::Transform3D worldToLocalTransform = localToWorldTransform.getInverse(); + Ray rayLocal(worldToLocalTransform * ray.point1, + worldToLocalTransform * ray.point2, + ray.maxFraction); + + boolean isHit = this.collisionShape->raycast(rayLocal, raycastInfo, this); + if (isHit == true) { + // Convert the raycast info into world-space + raycastInfo.worldPoint = localToWorldTransform * raycastInfo.worldPoint; + raycastInfo.worldNormal = localToWorldTransform.getOrientation() * raycastInfo.worldNormal; + raycastInfo.worldNormal.normalize(); + } + return isHit; +} + +// Return the pointer to the cached collision data +void** ProxyShape::getCachedCollisionData() { + return this.cachedCollisionData; +} + +// Return the collision shape +/** + * @return Pointer to the internal collision shape + */ + CollisionShape* ProxyShape::getCollisionShape() { + return this.collisionShape; +} + +// Return the parent body +/** + * @return Pointer to the parent body + */ +CollisionBody* ProxyShape::getBody() { + return this.body; +} + +// Return the mass of the collision shape +/** + * @return Mass of the collision shape (in kilograms) + */ +float ProxyShape::getMass() { + return this.mass; +} + +// Return a pointer to the user data attached to this body +/** + * @return A pointer to the user data stored into the proxy shape + */ +void* ProxyShape::getUserData() { + return this.userData; +} + +// Attach user data to this body +/** + * @param userData Pointer to the user data you want to store within the proxy shape + */ +void ProxyShape::setUserData(void* userData) { + this.userData = userData; +} + +// 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 + */ + etk::Transform3D ProxyShape::getLocalToBodyTransform() { + return this.localToBodyTransform; +} + +// Set the local to parent body transform +void ProxyShape::setLocalToBodyTransform( etk::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); +} + +// Return the local to world transform +/** + * @return The transformation that transforms the local-space of the collision + * shape to the world-space + */ + etk::Transform3D ProxyShape::getLocalToWorldTransform() { + return this.body->this.transform * this.localToBodyTransform; +} + +// Return the next proxy shape in the linked list of proxy shapes +/** + * @return Pointer to the next proxy shape in the linked list of proxy shapes + */ +ProxyShape* ProxyShape::getNext() { + return this.next; +} + +// Return the next proxy shape in the linked list of proxy shapes +/** + * @return Pointer to the next proxy shape in the linked list of proxy shapes + */ + ProxyShape* ProxyShape::getNext() { + return this.next; +} + +// Return the collision category bits +/** + * @return The collision category bits mask of the proxy shape + */ +unsigned short ProxyShape::getCollisionCategoryBits() { + return this.collisionCategoryBits; +} + +// Set the collision category bits +/** + * @param collisionCategoryBits The collision category bits mask of the proxy shape + */ +void ProxyShape::setCollisionCategoryBits(unsigned short collisionCategoryBits) { + this.collisionCategoryBits = collisionCategoryBits; +} + +// Return the collision bits mask +/** + * @return The bits mask that specifies with which collision category this shape will collide + */ +unsigned short ProxyShape::getCollideWithMaskBits() { + return this.collideWithMaskBits; +} + +// Set the collision bits mask +/** + * @param collideWithMaskBits The bits mask that specifies with which collision category this shape will collide + */ +void ProxyShape::setCollideWithMaskBits(unsigned short collideWithMaskBits) { + this.collideWithMaskBits = collideWithMaskBits; +} + +// Return the local scaling vector of the collision shape +/** + * @return The local scaling vector + */ +vec3 ProxyShape::getLocalScaling() { + return this.collisionShape->getScaling(); +} + +// Set the local scaling vector of the collision shape +/** + * @param scaling The new local scaling vector + */ +void ProxyShape::setLocalScaling( vec3 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); +} diff --git a/src/org/atriaSoft/ephysics/collision/ProxyShape.hpp b/src/org/atriaSoft/ephysics/collision/ProxyShape.hpp new file mode 100644 index 0000000..4dc5a64 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/ProxyShape.hpp @@ -0,0 +1,135 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * @breif The CollisionShape instances are supposed to be unique for memory optimization. For instance, + * consider two rigid bodies with the same sphere collision shape. In this situation, we will have + * a unique instance of SphereShape but we need to differentiate between the two instances during + * the collision detection. They do not have the same position in the world and they do not + * belong to the same rigid body. The ProxyShape class is used for that purpose by attaching a + * rigid body with one of its collision shape. A body can have multiple proxy shapes (one for + * each collision shape attached to the body). + */ + class ProxyShape { + protected: + CollisionBody* this.body; //!< Pointer to the parent body + CollisionShape* this.collisionShape; //!< Internal collision shape + etk::Transform3D this.localToBodyTransform; //!< Local-space to parent body-space transform (does not change over time) + float this.mass; //!< Mass (in kilogramms) of the corresponding collision shape + ProxyShape* this.next; //!< Pointer to the next proxy shape of the body (linked list) + int this.broadPhaseID; //!< Broad-phase ID (node ID in the dynamic AABB tree) + void* this.cachedCollisionData; //!< Cached collision data + void* this.userData; //!< 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. + */ + unsigned short this.collisionCategoryBits; + /** + * @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. + */ + unsigned short this.collideWithMaskBits; + /// Private copy-ructor + ProxyShape( ProxyShape) = delete; + /// Private assignment operator + ProxyShape operator=( ProxyShape) = delete; + public: + /// Constructor + ProxyShape(CollisionBody* body, + CollisionShape* shape, + etk::Transform3D transform, + float mass); + + /// Destructor + virtual ~ProxyShape(); + + /// Return the collision shape + CollisionShape* getCollisionShape() ; + + /// Return the parent body + CollisionBody* getBody() ; + + /// Return the mass of the collision shape + float getMass() ; + + /// Return a pointer to the user data attached to this body + void* getUserData() ; + + /// Attach user data to this body + void setUserData(void* userData); + + /// Return the local to parent body transform + etk::Transform3D getLocalToBodyTransform() ; + + /// Set the local to parent body transform + void setLocalToBodyTransform( etk::Transform3D transform); + + /// Return the local to world transform + etk::Transform3D getLocalToWorldTransform() ; + + /// Return true if a point is inside the collision shape + boolean testPointInside( vec3 worldPoint); + + /// Raycast method with feedback information + boolean raycast( Ray ray, RaycastInfo raycastInfo); + + /// Return the collision bits mask + unsigned short getCollideWithMaskBits() ; + + /// Set the collision bits mask + void setCollideWithMaskBits(unsigned short collideWithMaskBits); + + /// Return the collision category bits + unsigned short getCollisionCategoryBits() ; + + /// Set the collision category bits + void setCollisionCategoryBits(unsigned short collisionCategoryBits); + + /// Return the next proxy shape in the linked list of proxy shapes + ProxyShape* getNext(); + + /// Return the next proxy shape in the linked list of proxy shapes + ProxyShape* getNext() ; + + /// Return the pointer to the cached collision data + void** getCachedCollisionData(); + + /// Return the local scaling vector of the collision shape + vec3 getLocalScaling() ; + + /// Set the local scaling vector of the collision shape + virtual void setLocalScaling( vec3 scaling); + + friend class OverlappingPair; + friend class CollisionBody; + friend class RigidBody; + friend class BroadPhaseAlgorithm; + friend class DynamicAABBTree; + friend class CollisionDetection; + friend class CollisionWorld; + friend class DynamicsWorld; + friend class EPAAlgorithm; + friend class GJKAlgorithm; + friend class ConvexMeshShape; + + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/RaycastInfo.cpp b/src/org/atriaSoft/ephysics/collision/RaycastInfo.cpp new file mode 100644 index 0000000..495cf05 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/RaycastInfo.cpp @@ -0,0 +1,30 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +// Ray cast test against a proxy shape +float RaycastTest::raycastAgainstShape(ProxyShape* shape, Ray ray) { + + // Ray casting test against the collision shape + RaycastInfo raycastInfo; + 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 userCallback->notifyRaycastHit(raycastInfo); + } + + return ray.maxFraction; +} diff --git a/src/org/atriaSoft/ephysics/collision/RaycastInfo.hpp b/src/org/atriaSoft/ephysics/collision/RaycastInfo.hpp new file mode 100644 index 0000000..40e0715 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/RaycastInfo.hpp @@ -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, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +#pragma once + +#include +#include + +namespace ephysics { + class CollisionBody; + class ProxyShape; + class CollisionShape; + /** + * @brief It contains the information about a raycast hit. + */ + struct RaycastInfo { + private: + /// Private copy ructor + RaycastInfo( RaycastInfo) = delete; + /// Private assignment operator + RaycastInfo operator=( RaycastInfo) = delete; + public: + vec3 worldPoint; //!< Hit point in world-space coordinates + vec3 worldNormal; //!< Surface normal at hit point in world-space coordinates + float hitFraction; //!< 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) + int meshSubpart; //!< Mesh subpart index that has been hit (only used for triangles mesh and -1 otherwise) + int triangleIndex; //!< Hit triangle index (only used for triangles mesh and -1 otherwise) + CollisionBody* body; //!< Pointer to the hit collision body + ProxyShape* proxyShape; //!< Pointer to the hit proxy collision shape + /// Constructor + RaycastInfo() : + meshSubpart(-1), + triangleIndex(-1), + body(null), + proxyShape(null) { + + } + + /// Destructor + virtual ~RaycastInfo() = default; + }; + + /** + * @brief It can be used to register a callback for ray casting 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. + */ + class RaycastCallback { + public: + /// Destructor + virtual ~RaycastCallback() = default; + /** + * @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 + */ + virtual float notifyRaycastHit( RaycastInfo raycastInfo)=0; + }; + + struct RaycastTest { + public: + RaycastCallback* userCallback; //!< User callback class + /// Constructor + RaycastTest(RaycastCallback* callback) { + userCallback = callback; + } + /// Ray cast test against a proxy shape + float raycastAgainstShape(ProxyShape* shape, Ray ray); + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/TriangleMesh.cpp b/src/org/atriaSoft/ephysics/collision/TriangleMesh.cpp new file mode 100644 index 0000000..60cada0 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/TriangleMesh.cpp @@ -0,0 +1,14 @@ +/** @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) + */ +#include + +ephysics::TriangleMesh::TriangleMesh() { + +} + diff --git a/src/org/atriaSoft/ephysics/collision/TriangleMesh.hpp b/src/org/atriaSoft/ephysics/collision/TriangleMesh.hpp new file mode 100644 index 0000000..1e956d4 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/TriangleMesh.hpp @@ -0,0 +1,55 @@ +/** @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) + */ +#pragma once +#include +#include + +namespace ephysics { + /** + * @brief Represents a mesh made of triangles. A TriangleMesh contains + * one or several parts. Each part is a set of triangles represented in a + * TriangleVertexArray object describing all the triangles vertices of the part. + * A TriangleMesh object is used to create a ConcaveMeshShape from a triangle + * mesh for instance. + */ + class TriangleMesh { + protected: + etk::Vector this.triangleArrays; //!< All the triangle arrays of the mesh (one triangle array per part) + public: + /** + * @brief Constructor + */ + TriangleMesh(); + /** + * @brief Virtualisation of Destructor + */ + virtual ~TriangleMesh() = default; + /** + * @brief Add a subpart of the mesh + */ + void addSubpart(TriangleVertexArray* triangleVertexArray) { + this.triangleArrays.pushBack(triangleVertexArray ); + } + /** + * @brief Get a pointer to a given subpart (triangle vertex array) of the mesh + */ + TriangleVertexArray* getSubpart(int indexSubpart) { + assert(indexSubpart < this.triangleArrays.size()); + return this.triangleArrays[indexSubpart]; + } + /** + * @brief Get the number of subparts of the mesh + */ + int getNbSubparts() { + return this.triangleArrays.size(); + } + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.cpp b/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.cpp new file mode 100644 index 0000000..f00137e --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.cpp @@ -0,0 +1,40 @@ +/** @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) + */ +#include + + +ephysics::TriangleVertexArray::TriangleVertexArray( etk::Vector vertices, etk::Vector triangles): + this.vertices(vertices), + this.triangles(triangles) { + +} + +sizet ephysics::TriangleVertexArray::getNbVertices() { + return this.vertices.size(); +} + +sizet ephysics::TriangleVertexArray::getNbTriangles() { + return this.triangles.size()/3; +} + + etk::Vector ephysics::TriangleVertexArray::getVertices() { + return this.vertices; +} + + etk::Vector ephysics::TriangleVertexArray::getIndices() { + return this.triangles; +} + +ephysics::Triangle ephysics::TriangleVertexArray::getTriangle(int id) { + ephysics::Triangle out; + out[0] = this.vertices[this.triangles[id*3]]; + out[1] = this.vertices[this.triangles[id*3+1]]; + out[2] = this.vertices[this.triangles[id*3+2]]; + return out; +} \ No newline at end of file diff --git a/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.hpp b/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.hpp new file mode 100644 index 0000000..7c66039 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/TriangleVertexArray.hpp @@ -0,0 +1,73 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + class Triangle { + public: + vec3 value[3]; + vec3 operator[] (sizet id) { + return value[id]; + } + }; + /** + * 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 + * into 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. + */ + class TriangleVertexArray { + protected: + etk::Vector this.vertices; //!< Vertice list + etk::Vector this.triangles; //!< List of triangle (3 pos for each triangle) + public: + /** + * @brief Constructor + * @param[in] vertices List Of all vertices + * @param[in] triangles List of all linked points + */ + TriangleVertexArray( etk::Vector vertices, + etk::Vector triangles); + /** + * @brief Get the number of vertices + * @return Number of vertices + */ + sizet getNbVertices() ; + /** + * @brief Get the number of triangle + * @return Number of triangles + */ + sizet getNbTriangles() ; + /** + * @brief Get The table of the vertices + * @return reference on the vertices + */ + etk::Vector getVertices() ; + /** + * @brief Get The table of the triangle indice + * @return reference on the triangle indice + */ + etk::Vector getIndices() ; + /** + * @brief Get a triangle at the specific ID + * @return Buffer of 3 points + */ + ephysics::Triangle getTriangle(int id) ; + }; + + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.cpp new file mode 100644 index 0000000..b94c15f --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.cpp @@ -0,0 +1,203 @@ +/** @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) + */ +#include +#include +#include + +using namespace ephysics; + +BroadPhaseAlgorithm::BroadPhaseAlgorithm(CollisionDetection collisionDetection): + this.dynamicAABBTree(DYNAMICTREEAABBGAP), + this.collisionDetection(collisionDetection) { + this.movedShapes.reserve(8); + this.potentialPairs.reserve(8); +} + +BroadPhaseAlgorithm::~BroadPhaseAlgorithm() { + +} + +void BroadPhaseAlgorithm::addMovedCollisionShape(int broadPhaseID) { + this.movedShapes.pushBack(broadPhaseID); +} + +void BroadPhaseAlgorithm::removeMovedCollisionShape(int broadPhaseID) { + auto it = this.movedShapes.begin(); + while (it != this.movedShapes.end()) { + if (*it == broadPhaseID) { + it = this.movedShapes.erase(it); + } else { + ++it; + } + } + /* + assert(this.numberNonUsedMovedShapes <= this.numberMovedShapes); + + // If less than the quarter of allocated elements of the non-static shapes IDs array + // are used, we release some allocated memory + if ((this.numberMovedShapes - this.numberNonUsedMovedShapes) < this.numberAllocatedMovedShapes / 4 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj + this.numberAllocatedMovedShapes > 8) { + + this.numberAllocatedMovedShapes /= 2; + int* oldArray = this.movedShapes; + this.movedShapes = (int*) malloc(this.numberAllocatedMovedShapes * sizeof(int)); + assert(this.movedShapes != NULL); + int nbElements = 0; + for (int i=0; ithis.broadPhaseID = nodeId; + // Add the collision shape into the array of bodies that have moved (or have been created) + // during the last simulation step + addMovedCollisionShape(proxyShape->this.broadPhaseID); +} + +void BroadPhaseAlgorithm::removeProxyCollisionShape(ProxyShape* proxyShape) { + int broadPhaseID = proxyShape->this.broadPhaseID; + // Remove the collision shape from the dynamic AABB tree + this.dynamicAABBTree.removeObject(broadPhaseID); + // Remove the collision shape into the array of shapes that have moved (or have been created) + // during the last simulation step + removeMovedCollisionShape(broadPhaseID); +} + +void BroadPhaseAlgorithm::updateProxyCollisionShape(ProxyShape* proxyShape, + AABB aabb, + vec3 displacement, + boolean forceReinsert) { + int broadPhaseID = proxyShape->this.broadPhaseID; + assert(broadPhaseID >= 0); + // Update the dynamic AABB tree according to the movement of the collision shape + 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 + // into the tree). + if (hasBeenReInserted) { + // Add the collision shape into the array of shapes that have moved (or have been created) + // during the last simulation step + addMovedCollisionShape(broadPhaseID); + } +} + +static boolean sortFunction( etk::Pair pair1, etk::Pair pair2) { + if (pair1.first < pair2.first) { + return true; + } + if (pair1.first == pair2.first) { + return pair1.second < pair2.second; + } + return false; +} + +void BroadPhaseAlgorithm::computeOverlappingPairs() { + this.potentialPairs.clear(); + // For all collision shapes that have moved (or have been created) during the + // last simulation step + for (auto it: this.movedShapes) { + if (it == -1) { + // impossible case ... + continue; + } + // Get the AABB of the shape + AABB shapeAABB = this.dynamicAABBTree.getFatAABB(it); + // 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, [](int nodeId) mutable { + // If both the nodes are the same, we do not create store the overlapping pair + if (it == nodeId) { + return; + } + // Add the new potential pair into the array of potential overlapping pairs + this.potentialPairs.pushBack(etk::makePair(etk::min(it, nodeId), etk::max(it, nodeId) )); + }); + } + // 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 + etk::algorithm::quickSort(this.potentialPairs, sortFunction); + // 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 + etk::Pair pair = (this.potentialPairs[iii]); + ++iii; + // Get the two collision shapes of the pair + ProxyShape* shape1 = staticcast(this.dynamicAABBTree.getNodeDataPointer(pair.first)); + ProxyShape* shape2 = staticcast(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 + etk::Pair nextPair = this.potentialPairs[iii]; + // If the next pair is different from the previous one, we stop skipping pairs + if ( nextPair.first != pair.first + || nextPair.second != pair.second) { + break; + } + ++iii; + } + } +} + +float BroadPhaseRaycastCallback::operator()(int nodeId, Ray ray) { + float hitFraction = float(-1.0); + // Get the proxy shape from the node + ProxyShape* proxyShape = staticcast(this.dynamicAABBTree.getNodeDataPointer(nodeId)); + // 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; +} + +boolean BroadPhaseAlgorithm::testOverlappingShapes( ProxyShape* shape1, + ProxyShape* shape2) { + // Get the two AABBs of the collision shapes + AABB aabb1 = this.dynamicAABBTree.getFatAABB(shape1->this.broadPhaseID); + AABB aabb2 = this.dynamicAABBTree.getFatAABB(shape2->this.broadPhaseID); + // Check if the two AABBs are overlapping + return aabb1.testCollision(aabb2); +} + +void BroadPhaseAlgorithm::raycast( Ray ray, + RaycastTest raycastTest, + unsigned short raycastWithCategoryMaskBits) { + PROFILE("BroadPhaseAlgorithm::raycast()"); + BroadPhaseRaycastCallback broadPhaseRaycastCallback(this.dynamicAABBTree, raycastWithCategoryMaskBits, raycastTest); + this.dynamicAABBTree.raycast(ray, broadPhaseRaycastCallback); +} + diff --git a/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.hpp new file mode 100644 index 0000000..905f219 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/broadphase/BroadPhaseAlgorithm.hpp @@ -0,0 +1,94 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace ephysics { + + class CollisionDetection; + class BroadPhaseAlgorithm; + + // TODO : remove this as callback ... DynamicAABBTreeOverlapCallback { + /** + * Callback called when the AABB of a leaf node is hit by a ray the + * broad-phase Dynamic AABB Tree. + */ + class BroadPhaseRaycastCallback { + private : + DynamicAABBTree this.dynamicAABBTree; + unsigned short this.raycastWithCategoryMaskBits; + RaycastTest this.raycastTest; + public: + // Constructor + BroadPhaseRaycastCallback( DynamicAABBTree dynamicAABBTree, + unsigned short raycastWithCategoryMaskBits, + RaycastTest raycastTest): + this.dynamicAABBTree(dynamicAABBTree), + this.raycastWithCategoryMaskBits(raycastWithCategoryMaskBits), + this.raycastTest(raycastTest) { + + } + // Called for a broad-phase shape that has to be tested for raycast + float operator()(int nodeId, Ray ray); + }; + + /** + * @brief 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. + */ + class BroadPhaseAlgorithm { + protected : + DynamicAABBTree this.dynamicAABBTree; //!< Dynamic AABB tree + etk::Vector this.movedShapes; //!< 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. + etk::Vector> this.potentialPairs; //!< Temporary array of potential overlapping pairs (with potential duplicates) + CollisionDetection this.collisionDetection; //!< Reference to the collision detection object + /// Private copy-ructor + BroadPhaseAlgorithm( BroadPhaseAlgorithm obj); + /// Private assignment operator + BroadPhaseAlgorithm operator=( BroadPhaseAlgorithm obj); + public : + /// Constructor + BroadPhaseAlgorithm(CollisionDetection collisionDetection); + /// Destructor + virtual ~BroadPhaseAlgorithm(); + /// Add a proxy collision shape into the broad-phase collision detection + void addProxyCollisionShape(ProxyShape* proxyShape, AABB aabb); + /// Remove a proxy collision shape from the broad-phase collision detection + void removeProxyCollisionShape(ProxyShape* proxyShape); + /// Notify the broad-phase that a collision shape has moved and need to be updated + void updateProxyCollisionShape(ProxyShape* proxyShape, + AABB aabb, + vec3 displacement, + boolean forceReinsert = false); + /// 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. + void addMovedCollisionShape(int broadPhaseID); + /// 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. + void removeMovedCollisionShape(int broadPhaseID); + /// Compute all the overlapping pairs of collision shapes + void computeOverlappingPairs(); + /// Return true if the two broad-phase collision shapes are overlapping + boolean testOverlappingShapes( ProxyShape* shape1, ProxyShape* shape2) ; + /// Ray casting method + void raycast( Ray ray, + RaycastTest raycastTest, + unsigned short raycastWithCategoryMaskBits) ; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.cpp b/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.cpp new file mode 100644 index 0000000..b879aae --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.cpp @@ -0,0 +1,688 @@ +/** @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) + */ +#include +#include +#include +#include +#include + +using namespace ephysics; + + int TreeNode::NULLTREENODE = -1; + +DynamicAABBTree::DynamicAABBTree(float extraAABBGap): + this.extraAABBGap(extraAABBGap) { + init(); +} + +DynamicAABBTree::~DynamicAABBTree() { + free(this.nodes); +} + +// Initialize the tree +void DynamicAABBTree::init() { + this.rootNodeID = TreeNode::NULLTREENODE; + this.numberNodes = 0; + this.numberAllocatedNodes = 8; + // Allocate memory for the nodes of the tree + this.nodes = (TreeNode*) malloc(this.numberAllocatedNodes * sizeof(TreeNode)); + assert(this.nodes); + memset(this.nodes, 0, this.numberAllocatedNodes * sizeof(TreeNode)); + // Initialize the allocated nodes + for (int i=0; i 0); + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].height >= 0); + this.nodes[nodeID].nextNodeID = this.freeNodeID; + this.nodes[nodeID].height = -1; + this.freeNodeID = nodeID; + this.numberNodes--; +} + +// Internally add an object into the tree +int DynamicAABBTree::addObjectInternal( AABB aabb) { + // Get the next available node (or allocate new ones if necessary) + int nodeID = allocateNode(); + // Create the fat aabb to use in the tree + vec3 gap(this.extraAABBGap, this.extraAABBGap, this.extraAABBGap); + this.nodes[nodeID].aabb.setMin(aabb.getMin() - gap); + this.nodes[nodeID].aabb.setMax(aabb.getMax() + gap); + // Set the height of the node in the tree + this.nodes[nodeID].height = 0; + // Insert the new leaf node in the tree + insertLeafNode(nodeID); + assert(this.nodes[nodeID].isLeaf()); + assert(nodeID >= 0); + // Return the Id of the node + return nodeID; +} + +// Remove an object from the tree +void DynamicAABBTree::removeObject(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].isLeaf()); + // Remove the node from the tree + removeLeafNode(nodeID); + releaseNode(nodeID); +} + + +// 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). +boolean DynamicAABBTree::updateObject(int nodeID, AABB newAABB, vec3 displacement, bool forceReinsert) { + PROFILE("DynamicAABBTree::updateObject()"); + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].isLeaf()); + assert(this.nodes[nodeID].height >= 0); + Log.verbose(" compare : " << this.nodes[nodeID].aabb.this.minCoordinates << " " << this.nodes[nodeID].aabb.this.maxCoordinates); + Log.verbose(" : " << newAABB.this.minCoordinates << " " << newAABB.this.maxCoordinates); + // If the new AABB is still inside the fat AABB of the node + if ( forceReinsert == false + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.nodes[nodeID].aabb.contains(newAABB)) { + return false; + } + // If the new AABB is outside the fat AABB, we remove the corresponding node + removeLeafNode(nodeID); + // Compute the fat AABB by inflating the AABB with a ant gap + this.nodes[nodeID].aabb = newAABB; + vec3 gap(this.extraAABBGap, this.extraAABBGap, this.extraAABBGap); + this.nodes[nodeID].aabb.this.minCoordinates -= gap; + this.nodes[nodeID].aabb.this.maxCoordinates += gap; + // Inflate the fat AABB in direction of the linear motion of the AABB + if (displacement.x() < 0.0f) { + this.nodes[nodeID].aabb.this.minCoordinates.setX(this.nodes[nodeID].aabb.this.minCoordinates.x() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.x()); + } else { + this.nodes[nodeID].aabb.this.maxCoordinates.setX(this.nodes[nodeID].aabb.this.maxCoordinates.x() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.x()); + } + if (displacement.y() < 0.0f) { + this.nodes[nodeID].aabb.this.minCoordinates.setY(this.nodes[nodeID].aabb.this.minCoordinates.y() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.y()); + } else { + this.nodes[nodeID].aabb.this.maxCoordinates.setY(this.nodes[nodeID].aabb.this.maxCoordinates.y() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.y()); + } + if (displacement.z() < 0.0f) { + this.nodes[nodeID].aabb.this.minCoordinates.setZ(this.nodes[nodeID].aabb.this.minCoordinates.z() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.z()); + } else { + this.nodes[nodeID].aabb.this.maxCoordinates.setZ(this.nodes[nodeID].aabb.this.maxCoordinates.z() + DYNAMICTREEAABBLINGAPMULTIPLIER *displacement.z()); + } + Log.error(" compare : " << this.nodes[nodeID].aabb.this.minCoordinates << " " << this.nodes[nodeID].aabb.this.maxCoordinates); + Log.error(" : " << newAABB.this.minCoordinates << " " << newAABB.this.maxCoordinates); + if (this.nodes[nodeID].aabb.contains(newAABB) == false) { + //Log.critical("ERROR"); + } + assert(this.nodes[nodeID].aabb.contains(newAABB)); + // Reinsert the node into the tree + insertLeafNode(nodeID); + return true; +} + +// Insert a leaf node in the tree. The process of inserting a new leaf node +// in the dynamic tree is described in the book "Introduction to Game Physics +// with Box2D" by Ian Parberry. +void DynamicAABBTree::insertLeafNode(int nodeID) { + // If the tree is empty + if (this.rootNodeID == TreeNode::NULLTREENODE) { + this.rootNodeID = nodeID; + this.nodes[this.rootNodeID].parentID = TreeNode::NULLTREENODE; + return; + } + assert(this.rootNodeID != TreeNode::NULLTREENODE); + // Find the best sibling node for the new node + AABB newNodeAABB = this.nodes[nodeID].aabb; + int currentNodeID = this.rootNodeID; + while (!this.nodes[currentNodeID].isLeaf()) { + int leftChild = this.nodes[currentNodeID].children[0]; + int rightChild = this.nodes[currentNodeID].children[1]; + // Compute the merged AABB + float volumeAABB = this.nodes[currentNodeID].aabb.getVolume(); + AABB mergedAABBs; + mergedAABBs.mergeTwoAABBs(this.nodes[currentNodeID].aabb, newNodeAABB); + float mergedVolume = mergedAABBs.getVolume(); + // Compute the cost of making the current node the sibbling of the new node + float costS = float(2.0) * mergedVolume; + // Compute the minimum cost of pushing the new node further down the tree (inheritance cost) + float costI = float(2.0) * (mergedVolume - volumeAABB); + // Compute the cost of descending into the left child + float costLeft; + AABB currentAndLeftAABB; + currentAndLeftAABB.mergeTwoAABBs(newNodeAABB, this.nodes[leftChild].aabb); + if (this.nodes[leftChild].isLeaf()) { // If the left child is a leaf + costLeft = currentAndLeftAABB.getVolume() + costI; + } else { + float leftChildVolume = this.nodes[leftChild].aabb.getVolume(); + costLeft = costI + currentAndLeftAABB.getVolume() - leftChildVolume; + } + // Compute the cost of descending into the right child + float costRight; + AABB currentAndRightAABB; + currentAndRightAABB.mergeTwoAABBs(newNodeAABB, this.nodes[rightChild].aabb); + if (this.nodes[rightChild].isLeaf()) { // If the right child is a leaf + costRight = currentAndRightAABB.getVolume() + costI; + } else { + float rightChildVolume = this.nodes[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 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj costS < costRight) { + break; + } + // It is cheaper to go down into a child of the current node, choose the best child + if (costLeft < costRight) { + currentNodeID = leftChild; + } else { + currentNodeID = rightChild; + } + } + int siblingNode = currentNodeID; + // Create a new parent for the new node and the sibling node + int oldParentNode = this.nodes[siblingNode].parentID; + int newParentNode = allocateNode(); + this.nodes[newParentNode].parentID = oldParentNode; + this.nodes[newParentNode].aabb.mergeTwoAABBs(this.nodes[siblingNode].aabb, newNodeAABB); + this.nodes[newParentNode].height = this.nodes[siblingNode].height + 1; + assert(this.nodes[newParentNode].height > 0); + // If the sibling node was not the root node + if (oldParentNode != TreeNode::NULLTREENODE) { + assert(!this.nodes[oldParentNode].isLeaf()); + if (this.nodes[oldParentNode].children[0] == siblingNode) { + this.nodes[oldParentNode].children[0] = newParentNode; + } else { + this.nodes[oldParentNode].children[1] = newParentNode; + } + this.nodes[newParentNode].children[0] = siblingNode; + this.nodes[newParentNode].children[1] = nodeID; + this.nodes[siblingNode].parentID = newParentNode; + this.nodes[nodeID].parentID = newParentNode; + } else { + // If the sibling node was the root node + this.nodes[newParentNode].children[0] = siblingNode; + this.nodes[newParentNode].children[1] = nodeID; + this.nodes[siblingNode].parentID = newParentNode; + this.nodes[nodeID].parentID = newParentNode; + this.rootNodeID = newParentNode; + } + // Move up in the tree to change the AABBs that have changed + currentNodeID = this.nodes[nodeID].parentID; + assert(!this.nodes[currentNodeID].isLeaf()); + while (currentNodeID != TreeNode::NULLTREENODE) { + // Balance the sub-tree of the current node if it is not balanced + currentNodeID = balanceSubTreeAtNode(currentNodeID); + assert(this.nodes[nodeID].isLeaf()); + assert(!this.nodes[currentNodeID].isLeaf()); + int leftChild = this.nodes[currentNodeID].children[0]; + int rightChild = this.nodes[currentNodeID].children[1]; + assert(leftChild != TreeNode::NULLTREENODE); + assert(rightChild != TreeNode::NULLTREENODE); + // Recompute the height of the node in the tree + this.nodes[currentNodeID].height = etk::max(this.nodes[leftChild].height, + this.nodes[rightChild].height) + 1; + assert(this.nodes[currentNodeID].height > 0); + // Recompute the AABB of the node + this.nodes[currentNodeID].aabb.mergeTwoAABBs(this.nodes[leftChild].aabb, this.nodes[rightChild].aabb); + currentNodeID = this.nodes[currentNodeID].parentID; + } + assert(this.nodes[nodeID].isLeaf()); +} + +// Remove a leaf node from the tree +void DynamicAABBTree::removeLeafNode(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].isLeaf()); + // If we are removing the root node (root node is a leaf in this case) + if (this.rootNodeID == nodeID) { + this.rootNodeID = TreeNode::NULLTREENODE; + return; + } + int parentNodeID = this.nodes[nodeID].parentID; + int grandParentNodeID = this.nodes[parentNodeID].parentID; + int siblingNodeID; + if (this.nodes[parentNodeID].children[0] == nodeID) { + siblingNodeID = this.nodes[parentNodeID].children[1]; + } else { + siblingNodeID = this.nodes[parentNodeID].children[0]; + } + // If the parent of the node to remove is not the root node + if (grandParentNodeID != TreeNode::NULLTREENODE) { + // Destroy the parent node + if (this.nodes[grandParentNodeID].children[0] == parentNodeID) { + this.nodes[grandParentNodeID].children[0] = siblingNodeID; + } else { + assert(this.nodes[grandParentNodeID].children[1] == parentNodeID); + this.nodes[grandParentNodeID].children[1] = siblingNodeID; + } + this.nodes[siblingNodeID].parentID = grandParentNodeID; + releaseNode(parentNodeID); + // 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 + int currentNodeID = grandParentNodeID; + while(currentNodeID != TreeNode::NULLTREENODE) { + // Balance the current sub-tree if necessary + currentNodeID = balanceSubTreeAtNode(currentNodeID); + assert(!this.nodes[currentNodeID].isLeaf()); + // Get the two children of the current node + int leftChildID = this.nodes[currentNodeID].children[0]; + int rightChildID = this.nodes[currentNodeID].children[1]; + // Recompute the AABB and the height of the current node + this.nodes[currentNodeID].aabb.mergeTwoAABBs(this.nodes[leftChildID].aabb, + this.nodes[rightChildID].aabb); + this.nodes[currentNodeID].height = etk::max(this.nodes[leftChildID].height, + this.nodes[rightChildID].height) + 1; + assert(this.nodes[currentNodeID].height > 0); + currentNodeID = this.nodes[currentNodeID].parentID; + } + } else { // If the parent of the node to remove is the root node + // The sibling node becomes the new root node + this.rootNodeID = siblingNodeID; + this.nodes[siblingNodeID].parentID = TreeNode::NULLTREENODE; + releaseNode(parentNodeID); + } +} + +// Balance the sub-tree of a given node using left or right rotations. +/// The rotation schemes are described in the book "Introduction to Game Physics +/// with Box2D" by Ian Parberry. This method returns the new root node ID. +int DynamicAABBTree::balanceSubTreeAtNode(int nodeID) { + assert(nodeID != TreeNode::NULLTREENODE); + TreeNode* nodeA = this.nodes + nodeID; + // If the node is a leaf or the height of A's sub-tree is less than 2 + if (nodeA->isLeaf() || nodeA->height < 2) { + // Do not perform any rotation + return nodeID; + } + // Get the two children nodes + int nodeBID = nodeA->children[0]; + int nodeCID = nodeA->children[1]; + assert(nodeBID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeBID < this.numberAllocatedNodes); + assert(nodeCID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeCID < this.numberAllocatedNodes); + TreeNode* nodeB = this.nodes + nodeBID; + TreeNode* nodeC = this.nodes + nodeCID; + // Compute the factor of the left and right sub-trees + int balanceFactor = nodeC->height - nodeB->height; + // If the right node C is 2 higher than left node B + if (balanceFactor > 1) { + assert(!nodeC->isLeaf()); + int nodeFID = nodeC->children[0]; + int nodeGID = nodeC->children[1]; + assert(nodeFID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeFID < this.numberAllocatedNodes); + assert(nodeGID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeGID < this.numberAllocatedNodes); + TreeNode* nodeF = this.nodes + nodeFID; + TreeNode* nodeG = this.nodes + nodeGID; + nodeC->children[0] = nodeID; + nodeC->parentID = nodeA->parentID; + nodeA->parentID = nodeCID; + if (nodeC->parentID != TreeNode::NULLTREENODE) { + if (this.nodes[nodeC->parentID].children[0] == nodeID) { + this.nodes[nodeC->parentID].children[0] = nodeCID; + } else { + assert(this.nodes[nodeC->parentID].children[1] == nodeID); + this.nodes[nodeC->parentID].children[1] = nodeCID; + } + } else { + this.rootNodeID = nodeCID; + } + 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) { + nodeC->children[1] = nodeFID; + nodeA->children[1] = nodeGID; + nodeG->parentID = nodeID; + // 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 = etk::max(nodeB->height, nodeG->height) + 1; + nodeC->height = etk::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 + nodeC->children[1] = nodeGID; + nodeA->children[1] = nodeFID; + nodeF->parentID = nodeID; + // 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 = etk::max(nodeB->height, nodeF->height) + 1; + nodeC->height = etk::max(nodeA->height, nodeG->height) + 1; + assert(nodeA->height > 0); + assert(nodeC->height > 0); + } + // Return the new root of the sub-tree + return nodeCID; + } + // If the left node B is 2 higher than right node C + if (balanceFactor < -1) { + assert(!nodeB->isLeaf()); + int nodeFID = nodeB->children[0]; + int nodeGID = nodeB->children[1]; + assert(nodeFID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeFID < this.numberAllocatedNodes); + assert(nodeGID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeGID < this.numberAllocatedNodes); + TreeNode* nodeF = this.nodes + nodeFID; + TreeNode* nodeG = this.nodes + nodeGID; + nodeB->children[0] = nodeID; + nodeB->parentID = nodeA->parentID; + nodeA->parentID = nodeBID; + if (nodeB->parentID != TreeNode::NULLTREENODE) { + if (this.nodes[nodeB->parentID].children[0] == nodeID) { + this.nodes[nodeB->parentID].children[0] = nodeBID; + } else { + assert(this.nodes[nodeB->parentID].children[1] == nodeID); + this.nodes[nodeB->parentID].children[1] = nodeBID; + } + } else { + this.rootNodeID = nodeBID; + } + 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) { + nodeB->children[1] = nodeFID; + nodeA->children[0] = nodeGID; + nodeG->parentID = nodeID; + // 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 = etk::max(nodeC->height, nodeG->height) + 1; + nodeB->height = etk::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 + nodeB->children[1] = nodeGID; + nodeA->children[0] = nodeFID; + nodeF->parentID = nodeID; + // 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 = etk::max(nodeC->height, nodeF->height) + 1; + nodeB->height = etk::max(nodeA->height, nodeG->height) + 1; + assert(nodeA->height > 0); + assert(nodeB->height > 0); + } + // Return the new root of the sub-tree + return nodeBID; + } + // If the sub-tree is balanced, return the current root node + return nodeID; +} + +/// Report all shapes overlapping with the AABB given in parameter. +void DynamicAABBTree::reportAllShapesOverlappingWithAABB( AABB aabb, etk::Function callback) { + if (callback == null) { + Log.error("call with null callback"); + return; + } + // Create a stack with the nodes to visit + Stack stack; + stack.push(this.rootNodeID); + // While there are still nodes to visit + while(stack.getNbElements() > 0) { + // Get the next node ID to visit + int nodeIDToVisit = stack.pop(); + // Skip it if it is a null node + if (nodeIDToVisit == TreeNode::NULLTREENODE) { + continue; + } + // Get the corresponding node + TreeNode* nodeToVisit = this.nodes + 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()) { + // Notify the broad-phase about a new potential overlapping pair + callback(nodeIDToVisit); + } else { + // If the node is not a leaf + // We need to visit its children + stack.push(nodeToVisit->children[0]); + stack.push(nodeToVisit->children[1]); + } + } + } +} + +// Ray casting method +void DynamicAABBTree::raycast( ephysics::Ray ray, etk::Function callback) { + PROFILE("DynamicAABBTree::raycast()"); + if (callback == null) { + Log.error("call with null callback"); + return; + } + float maxFraction = ray.maxFraction; + Stack stack; + stack.push(this.rootNodeID); + // Walk through the tree from the root looking for proxy shapes + // that overlap with the ray AABB + while (stack.getNbElements() > 0) { + // Get the next node in the stack + int nodeID = stack.pop(); + // If it is a null node, skip it + if (nodeID == TreeNode::NULLTREENODE) { + continue; + } + // Get the corresponding node + TreeNode* node = this.nodes + nodeID; + Ray rayTemp(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 + float hitFraction = callback(nodeID, 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 + // Push its children in the stack of nodes to explore + stack.push(node->children[0]); + stack.push(node->children[1]); + } + } +} + +// Return true if the node is a leaf of the tree +boolean TreeNode::isLeaf() { + return (height == 0); +} + +// Return the fat AABB corresponding to a given node ID + AABB DynamicAABBTree::getFatAABB(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + return this.nodes[nodeID].aabb; +} + +// Return the pointer to the data array of a given leaf node of the tree +int* DynamicAABBTree::getNodeDataInt(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].isLeaf()); + return this.nodes[nodeID].dataInt; +} + +// Return the pointer to the data pointer of a given leaf node of the tree +void* DynamicAABBTree::getNodeDataPointer(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + assert(this.nodes[nodeID].isLeaf()); + return this.nodes[nodeID].dataPointer; +} + +// Return the root AABB of the tree +AABB DynamicAABBTree::getRootAABB() { + return getFatAABB(this.rootNodeID); +} + +// Add an object into the tree. This method creates a new leaf node in the tree and +// returns the ID of the corresponding node. +int DynamicAABBTree::addObject( AABB aabb, int data1, int data2) { + int nodeId = addObjectInternal(aabb); + this.nodes[nodeId].dataInt[0] = data1; + this.nodes[nodeId].dataInt[1] = data2; + return nodeId; +} + +// Add an object into the tree. This method creates a new leaf node in the tree and +// returns the ID of the corresponding node. +int DynamicAABBTree::addObject( AABB aabb, void* data) { + int nodeId = addObjectInternal(aabb); + this.nodes[nodeId].dataPointer = data; + return nodeId; +} + + +#ifdef DEBUG + +// Check if the tree structure is valid (for debugging purpose) +void DynamicAABBTree::check() { + // Recursively check each node + checkNode(this.rootNodeID); + int nbFreeNodes = 0; + int freeNodeID = this.freeNodeID; + // Check the free nodes + while(freeNodeID != TreeNode::NULLTREENODE) { + assert(0 <= freeNodeID hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj freeNodeID < this.numberAllocatedNodes); + freeNodeID = this.nodes[freeNodeID].nextNodeID; + nbFreeNodes++; + } + assert(this.numberNodes + nbFreeNodes == this.numberAllocatedNodes); +} + +// Check if the node structure is valid (for debugging purpose) +void DynamicAABBTree::checkNode(int nodeID) { + if (nodeID == TreeNode::NULLTREENODE) { + return; + } + // If it is the root + if (nodeID == this.rootNodeID) { + assert(this.nodes[nodeID].parentID == TreeNode::NULLTREENODE); + } + // Get the children nodes + TreeNode* pNode = this.nodes + nodeID; + assert(!pNode->isLeaf()); + int leftChild = pNode->children[0]; + int rightChild = pNode->children[1]; + assert(pNode->height >= 0); + assert(pNode->aabb.getVolume() > 0); + // If the current node is a leaf + if (pNode->isLeaf()) { + // Check that there are no children + assert(leftChild == TreeNode::NULLTREENODE); + assert(rightChild == TreeNode::NULLTREENODE); + assert(pNode->height == 0); + } else { + // Check that the children node IDs are valid + assert(0 <= leftChild hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj leftChild < this.numberAllocatedNodes); + assert(0 <= rightChild hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj rightChild < this.numberAllocatedNodes); + // Check that the children nodes have the correct parent node + assert(this.nodes[leftChild].parentID == nodeID); + assert(this.nodes[rightChild].parentID == nodeID); + // Check the height of node + int height = 1 + etk::max(this.nodes[leftChild].height, this.nodes[rightChild].height); + assert(this.nodes[nodeID].height == height); + // Check the AABB of the node + AABB aabb; + aabb.mergeTwoAABBs(this.nodes[leftChild].aabb, this.nodes[rightChild].aabb); + assert(aabb.getMin() == this.nodes[nodeID].aabb.getMin()); + assert(aabb.getMax() == this.nodes[nodeID].aabb.getMax()); + // Recursively check the children nodes + checkNode(leftChild); + checkNode(rightChild); + } +} + +// Compute the height of the tree +int DynamicAABBTree::computeHeight() { + return computeHeight(this.rootNodeID); +} + +// Compute the height of a given node in the tree +int DynamicAABBTree::computeHeight(int nodeID) { + assert(nodeID >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nodeID < this.numberAllocatedNodes); + TreeNode* node = this.nodes + nodeID; + // If the node is a leaf, its height is zero + if (node->isLeaf()) { + return 0; + } + // Compute the height of the left and right sub-tree + int leftHeight = computeHeight(node->children[0]); + int rightHeight = computeHeight(node->children[1]); + // Return the height of the node + return 1 + etk::max(leftHeight, rightHeight); +} + +#endif diff --git a/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.hpp b/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.hpp new file mode 100644 index 0000000..293ef42 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/broadphase/DynamicAABBTree.hpp @@ -0,0 +1,116 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include + +namespace ephysics { + // TODO: to replace this, create a Tree template (multiple child) or TreeRedBlack + /** + * @brief It represents a node of the dynamic AABB tree. + */ + struct TreeNode { + static int NULLTREENODE; //!< Null tree node ant + /** + * @brief A node is either in the tree (has a parent) or in the free nodes list (has a next node) + */ + union { + int parentID; //!< Parent node ID + int nextNodeID; //!< Next allocated node ID + }; + /** + * @brief A node is either a leaf (has data) or is an internal node (has children) + */ + union { + int children[2]; //!< Left and right child of the node (children[0] = left child) + //! Two pieces of data stored at that node (in case the node is a leaf) + union { + int dataInt[2]; + void* dataPointer; + }; + }; + int16t height; //!< Height of the node in the tree + AABB aabb; //!< Fat axis aligned bounding box (AABB) corresponding to the node + /// Return true if the node is a leaf of the tree + boolean isLeaf() ; + }; + + /** + * @brief 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. + */ + class DynamicAABBTree { + private: + TreeNode* this.nodes; //!< Pointer to the memory location of the nodes of the tree + int this.rootNodeID; //!< ID of the root node of the tree + int this.freeNodeID; //!< ID of the first node of the list of free (allocated) nodes in the tree that we can use + int this.numberAllocatedNodes; //!< Number of allocated nodes in the tree + int this.numberNodes; //!< Number of nodes in the tree + float this.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 + int allocateNode(); + /// Release a node + void releaseNode(int nodeID); + /// Insert a leaf node in the tree + void insertLeafNode(int nodeID); + /// Remove a leaf node from the tree + void removeLeafNode(int nodeID); + /// Balance the sub-tree of a given node using left or right rotations. + int balanceSubTreeAtNode(int nodeID); + /// Compute the height of a given node in the tree + int computeHeight(int nodeID); + /// Internally add an object into the tree + int addObjectInternal( AABB aabb); + /// Initialize the tree + void init(); + #ifndef NDEBUG + /// Check if the tree structure is valid (for debugging purpose) + void check() ; + /// Check if the node structure is valid (for debugging purpose) + void checkNode(int nodeID) ; + #endif + public: + /// Constructor + DynamicAABBTree(float extraAABBGap = 0.0f); + /// Destructor + virtual ~DynamicAABBTree(); + /// Add an object into the tree (where node data are two integers) + int addObject( AABB aabb, int data1, int data2); + /// Add an object into the tree (where node data is a pointer) + int addObject( AABB aabb, void* data); + /// Remove an object from the tree + void removeObject(int nodeID); + /// Update the dynamic tree after an object has moved. + boolean updateObject(int nodeID, AABB newAABB, vec3 displacement, bool forceReinsert = false); + /// Return the fat AABB corresponding to a given node ID + AABB getFatAABB(int nodeID) ; + /// Return the pointer to the data array of a given leaf node of the tree + int* getNodeDataInt(int nodeID) ; + /// Return the data pointer of a given leaf node of the tree + void* getNodeDataPointer(int nodeID) ; + /// Report all shapes overlapping with the AABB given in parameter. + void reportAllShapesOverlappingWithAABB( AABB aabb, etk::Function callback) ; + /// Ray casting method + void raycast( Ray ray, etk::Function callback) ; + /// Compute the height of the tree + int computeHeight(); + /// Return the root AABB of the tree + AABB getRootAABB() ; + /// Clear all the nodes and reset the tree + void reset(); + }; + + +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/CollisionDispatch.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/CollisionDispatch.hpp new file mode 100644 index 0000000..d9eefd9 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/CollisionDispatch.hpp @@ -0,0 +1,37 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + /** + * @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. + */ + class CollisionDispatch { + public: + /// Constructor + CollisionDispatch() {} + /// Destructor + virtual ~CollisionDispatch() = default; + /// Initialize the collision dispatch configuration + virtual void init(CollisionDetection* collisionDetection) { + // Nothing to do ... + } + /// Select and return the narrow-phase collision detection algorithm to + /// use between two types of collision shapes. + virtual NarrowPhaseAlgorithm* selectAlgorithm(int shape1Type, + int shape2Type) = 0; + }; + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.cpp new file mode 100644 index 0000000..4f6590d --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.cpp @@ -0,0 +1,241 @@ +/** @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) + */ +#include +#include +#include +#include +#include +#include + +using namespace ephysics; + +ConcaveVsConvexAlgorithm::ConcaveVsConvexAlgorithm() { + +} + +void ConcaveVsConvexAlgorithm::testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + 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 = staticcast< ConvexShape*>(shape1Info.collisionShape); + concaveProxyShape = shape2Info.proxyShape; + concaveShape = staticcast< ConcaveShape*>(shape2Info.collisionShape); + } else { + // Collision shape 2 is convex, collision shape 1 is concave + convexProxyShape = shape2Info.proxyShape; + convexShape = staticcast< ConvexShape*>(shape2Info.collisionShape); + concaveProxyShape = shape1Info.proxyShape; + concaveShape = staticcast< ConcaveShape*>(shape1Info.collisionShape); + } + // Set the parameters of the callback object + ConvexVsTriangleCallback 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 + AABB aabb; + convexShape->computeAABB(aabb, convexProxyShape->getLocalToWorldTransform()); + // If smooth mesh collision is enabled for the concave mesh + if (concaveShape->getIsSmoothMeshCollisionEnabled()) { + etk::Vector contactPoints; + SmoothCollisionNarrowPhaseCallback smoothNarrowPhaseCallback(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); + } +} + +void ConvexVsTriangleCallback::testTriangle( vec3* trianglePoints) { + // Create a triangle collision shape + float margin = this.concaveShape->getTriangleMargin(); + TriangleShape triangleShape(trianglePoints[0], trianglePoints[1], trianglePoints[2], margin); + // Select the collision algorithm to use between the triangle and the convex shape + 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 + CollisionShapeInfo shapeConvexInfo(this.convexProxyShape, + this.convexShape, + this.convexProxyShape->getLocalToWorldTransform(), + this.overlappingPair, + this.convexProxyShape->getCachedCollisionData()); + CollisionShapeInfo shapeConcaveInfo(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); +} + +static boolean sortFunction( SmoothMeshContactInfo contact1, SmoothMeshContactInfo contact2) { + return contact1.contactInfo.penetrationDepth <= contact2.contactInfo.penetrationDepth; +} + +void ConcaveVsConvexAlgorithm::processSmoothMeshCollision(OverlappingPair* overlappingPair, + etk::Vector contactPoints, + NarrowPhaseCallback* callback) { + // Set with the triangle vertices already processed to void further contacts with same triangle + etk::Vector> processTriangleVertices; + // Sort the list of narrow-phase contacts according to their penetration depth + etk::algorithm::quickSort(contactPoints, sortFunction); + // For each contact point (from smaller penetration depth to larger) + etk::Vector::Iterator it; + for (it = contactPoints.begin(); it != contactPoints.end(); ++it) { + SmoothMeshContactInfo info = *it; + vec3 contactPoint = info.isFirstShapeTriangle ? info.contactInfo.localPoint1 : info.contactInfo.localPoint2; + // Compute the barycentric coordinates of the point in the triangle + float u, v, w; + computeBarycentricCoordinatesInTriangle(info.triangleVertices[0], + info.triangleVertices[1], + info.triangleVertices[2], + contactPoint, u, v, w); + int nbZeros = 0; + boolean isUZero = approxEqual(u, 0, 0.0001); + boolean isVZero = approxEqual(v, 0, 0.0001); + boolean isWZero = approxEqual(w, 0, 0.0001); + if (isUZero) { + nbZeros++; + } + if (isVZero) { + nbZeros++; + } + if (isWZero) { + nbZeros++; + } + // If it is a vertex contact + if (nbZeros == 2) { + vec3 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 + vec3 contactVertex1 = isUZero ? info.triangleVertices[1] : (isVZero ? info.triangleVertices[0] : info.triangleVertices[0]); + vec3 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) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj + !hasVertexBeenProcessed(processTriangleVertices, contactVertex2)) { + // Keep the contact as it is and report it + callback->notifyContact(overlappingPair, info.contactInfo); + } + } else { + // If it is a face contact + ContactPointInfo newContactInfo(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 + vec3 a = info.triangleVertices[1] - info.triangleVertices[0]; + vec3 b = info.triangleVertices[2] - info.triangleVertices[0]; + vec3 localNormal = a.cross(b); + newContactInfo.normal = firstShape->getLocalToWorldTransform().getOrientation() * localNormal; + vec3 firstLocalPoint = info.isFirstShapeTriangle ? info.contactInfo.localPoint1 : info.contactInfo.localPoint2; + vec3 firstWorldPoint = firstShape->getLocalToWorldTransform() * firstLocalPoint; + newContactInfo.normal.normalize(); + if (newContactInfo.normal.dot(info.contactInfo.normal) < 0) { + newContactInfo.normal = -newContactInfo.normal; + } + // 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 + etk::Transform3D worldToLocalSecondPoint = secondShape->getLocalToWorldTransform().getInverse(); + if (info.isFirstShapeTriangle) { + vec3 newSecondWorldPoint = firstWorldPoint + newContactInfo.normal; + newContactInfo.localPoint2 = worldToLocalSecondPoint * newSecondWorldPoint; + } else { + vec3 newSecondWorldPoint = firstWorldPoint - newContactInfo.normal; + newContactInfo.localPoint1 = worldToLocalSecondPoint * 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]); + } +} + +boolean ConcaveVsConvexAlgorithm::hasVertexBeenProcessed( etk::Vector> processTriangleVertices, vec3 vertex) { + /* TODO : etk::Vector> 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.equalrange(key); + for (auto it = range.first; it != range.second; ++it) { + if ( vertex.x() == it->second.x() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vertex.y() == it->second.y() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vertex.z() == it->second.z()) { + return true; + } + } + return false; + */ + // TODO : This is not really the same ... + for (auto it: processTriangleVertices) { + if ( vertex.x() == it.second.x() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vertex.y() == it.second.y() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vertex.z() == it.second.z()) { + return true; + } + } + return false; +} + +void SmoothCollisionNarrowPhaseCallback::notifyContact(OverlappingPair* overlappingPair, + ContactPointInfo contactInfo) { + vec3 triangleVertices[3]; + boolean isFirstShapeTriangle; + // If the collision shape 1 is the triangle + if (contactInfo.collisionShape1->getType() == TRIANGLE) { + assert(contactInfo.collisionShape2->getType() != TRIANGLE); + TriangleShape* triangleShape = staticcast< 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() == TRIANGLE); + TriangleShape* triangleShape = staticcast< TriangleShape*>(contactInfo.collisionShape2); + triangleVertices[0] = triangleShape->getVertex(0); + triangleVertices[1] = triangleShape->getVertex(1); + triangleVertices[2] = triangleShape->getVertex(2); + isFirstShapeTriangle = false; + } + SmoothMeshContactInfo smoothContactInfo(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.pushBack(smoothContactInfo); +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.hpp new file mode 100644 index 0000000..98b10b6 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/ConcaveVsConvexAlgorithm.hpp @@ -0,0 +1,148 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + + /** + * @brief 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 : public TriangleCallback { + protected: + CollisionDetection* this.collisionDetection; //!< Pointer to the collision detection object + NarrowPhaseCallback* this.narrowPhaseCallback; //!< Narrow-phase collision callback + ConvexShape* this.convexShape; //!< Convex collision shape to test collision with + ConcaveShape* this.concaveShape; //!< Concave collision shape + ProxyShape* this.convexProxyShape; //!< Proxy shape of the convex collision shape + ProxyShape* this.concaveProxyShape; //!< Proxy shape of the concave collision shape + OverlappingPair* this.overlappingPair; //!< Broadphase overlapping pair + static boolean contactsDepthCompare( ContactPointInfo contact1, + ContactPointInfo contact2); + public: + /// Set the collision detection pointer + void setCollisionDetection(CollisionDetection* collisionDetection) { + this.collisionDetection = collisionDetection; + } + /// Set the narrow-phase collision callback + void setNarrowPhaseCallback(NarrowPhaseCallback* callback) { + this.narrowPhaseCallback = callback; + } + /// Set the convex collision shape to test collision with + void setConvexShape( ConvexShape* convexShape) { + this.convexShape = convexShape; + } + /// Set the concave collision shape + void setConcaveShape( ConcaveShape* concaveShape) { + this.concaveShape = concaveShape; + } + /// Set the broadphase overlapping pair + void setOverlappingPair(OverlappingPair* overlappingPair) { + this.overlappingPair = overlappingPair; + } + /// Set the proxy shapes of the two collision shapes + void setProxyShapes(ProxyShape* convexProxyShape, ProxyShape* concaveProxyShape) { + this.convexProxyShape = convexProxyShape; + this.concaveProxyShape = concaveProxyShape; + } + /// Test collision between a triangle and the convex mesh shape + virtual void testTriangle( vec3* trianglePoints); + }; + + /** + * @brief This class is used to store data about a contact with a triangle for the smooth + * mesh algorithm. + */ + class SmoothMeshContactInfo { + public: + ContactPointInfo contactInfo; + boolean isFirstShapeTriangle; + vec3 triangleVertices[3]; + /// Constructor + SmoothMeshContactInfo( ContactPointInfo contact, + boolean firstShapeTriangle, + vec3 trianglePoint1, + vec3 trianglePoint2, + vec3 trianglePoint3): + contactInfo(contact) { + isFirstShapeTriangle = firstShapeTriangle; + triangleVertices[0] = trianglePoint1; + triangleVertices[1] = trianglePoint2; + triangleVertices[2] = trianglePoint3; + } + SmoothMeshContactInfo() { + // TODO: add it for etk::Vector + } + }; + + /* + struct ContactsDepthCompare { + boolean operator()( SmoothMeshContactInfo contact1, SmoothMeshContactInfo contact2) { + return contact1.contactInfo.penetrationDepth < contact2.contactInfo.penetrationDepth; + } + }; + */ + + /** + * @brief 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 : public NarrowPhaseCallback { + private: + etk::Vector this.contactPoints; + public: + // Constructor + SmoothCollisionNarrowPhaseCallback(etk::Vector contactPoints): + this.contactPoints(contactPoints) { + + } + /// Called by a narrow-phase collision algorithm when a new contact has been found + virtual void notifyContact(OverlappingPair* overlappingPair, ContactPointInfo contactInfo); + }; + + /** + * @brief 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. + */ + class ConcaveVsConvexAlgorithm : public NarrowPhaseAlgorithm { + protected : + /// Private copy-ructor + ConcaveVsConvexAlgorithm( ConcaveVsConvexAlgorithm algorithm); + /// Private assignment operator + ConcaveVsConvexAlgorithm operator=( ConcaveVsConvexAlgorithm algorithm); + /// Process the concave triangle mesh collision using the smooth mesh collision algorithm + void processSmoothMeshCollision(OverlappingPair* overlappingPair, + etk::Vector contactPoints, + NarrowPhaseCallback* narrowPhaseCallback); + /// Add a triangle vertex into the set of processed triangles + void addProcessedVertex(etk::Vector> processTriangleVertices, vec3 vertex) { + processTriangleVertices.pushBack(etk::makePair(int(vertex.x() * vertex.y() * vertex.z()), vertex)); + } + /// Return true if the vertex is in the set of already processed vertices + boolean hasVertexBeenProcessed( etk::Vector> processTriangleVertices, + vec3 vertex) ; + public : + /// Constructor + ConcaveVsConvexAlgorithm(); + /// Compute a contact info if the two bounding volume collide + virtual void testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + NarrowPhaseCallback* narrowPhaseCallback); + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.cpp new file mode 100644 index 0000000..2fe455a --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.cpp @@ -0,0 +1,47 @@ +/** @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) + */ +// Libraries +#include +#include + +using namespace ephysics; + + +DefaultCollisionDispatch::DefaultCollisionDispatch() { + +} + + +void DefaultCollisionDispatch::init(CollisionDetection* collisionDetection) { + // Initialize the collision algorithms + this.sphereVsSphereAlgorithm.init(collisionDetection); + this.GJKAlgorithm.init(collisionDetection); + this.concaveVsConvexAlgorithm.init(collisionDetection); +} + + +NarrowPhaseAlgorithm* DefaultCollisionDispatch::selectAlgorithm(int type1, int type2) { + CollisionShapeType shape1Type = staticcast(type1); + CollisionShapeType shape2Type = staticcast(type2); + // Sphere vs Sphere algorithm + if (shape1Type == SPHERE hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shape2Type == SPHERE) { + return this.sphereVsSphereAlgorithm; + } else if ( ( !CollisionShape::isConvex(shape1Type) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj CollisionShape::isConvex(shape2Type) ) + || ( !CollisionShape::isConvex(shape2Type) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj CollisionShape::isConvex(shape1Type) ) ) { + // Concave vs Convex algorithm + return this.concaveVsConvexAlgorithm; + } else if (CollisionShape::isConvex(shape1Type) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj CollisionShape::isConvex(shape2Type)) { + // Convex vs Convex algorithm (GJK algorithm) + return this.GJKAlgorithm; + } else { + return null; + } +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.hpp new file mode 100644 index 0000000..fa1bac6 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/DefaultCollisionDispatch.hpp @@ -0,0 +1,37 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include + +namespace ephysics { + /** + * @brief 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. + */ + class DefaultCollisionDispatch : public CollisionDispatch { + protected: + SphereVsSphereAlgorithm this.sphereVsSphereAlgorithm; //!< Sphere vs Sphere collision algorithm + ConcaveVsConvexAlgorithm this.concaveVsConvexAlgorithm; //!< Concave vs Convex collision algorithm + GJKAlgorithm this.GJKAlgorithm; //!< GJK Algorithm + public: + /** + * @brief Constructor + */ + DefaultCollisionDispatch(); + void init(CollisionDetection* collisionDetection) override; + NarrowPhaseAlgorithm* selectAlgorithm(int type1, int type2) override; + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.cpp new file mode 100644 index 0000000..c5eeb6c --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.cpp @@ -0,0 +1,353 @@ +/** @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) + */ +#include +#include +#include +#include + +using namespace ephysics; + +EPAAlgorithm::EPAAlgorithm() { + +} + +EPAAlgorithm::~EPAAlgorithm() { + +} + +int EPAAlgorithm::isOriginInTetrahedron( vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + // Check vertex 1 + vec3 normal1 = (p2-p1).cross(p3-p1); + if ((normal1.dot(p1) > 0.0) == (normal1.dot(p4) > 0.0)) { + return 4; + } + // Check vertex 2 + vec3 normal2 = (p4-p2).cross(p3-p2); + if ((normal2.dot(p2) > 0.0) == (normal2.dot(p1) > 0.0)) { + return 1; + } + // Check vertex 3 + vec3 normal3 = (p4-p3).cross(p1-p3); + if ((normal3.dot(p3) > 0.0) == (normal3.dot(p2) > 0.0)) { + return 2; + } + // Check vertex 4 + vec3 normal4 = (p2-p4).cross(p1-p4); + if ((normal4.dot(p4) > 0.0) == (normal4.dot(p3) > 0.0)) { + return 3; + } + // The origin is in the tetrahedron, we return 0 + return 0; +} + +void EPAAlgorithm::computePenetrationDepthAndContactPoints( Simplex simplex, + CollisionShapeInfo shape1Info, + etk::Transform3D transform1, + CollisionShapeInfo shape2Info, + etk::Transform3D transform2, + vec3 vector, + NarrowPhaseCallback* narrowPhaseCallback) { + PROFILE("EPAAlgorithm::computePenetrationDepthAndContactPoints()"); + assert(shape1Info.collisionShape->isConvex()); + assert(shape2Info.collisionShape->isConvex()); + ConvexShape* shape1 = staticcast< ConvexShape*>(shape1Info.collisionShape); + ConvexShape* shape2 = staticcast< ConvexShape*>(shape2Info.collisionShape); + void** shape1CachedCollisionData = shape1Info.cachedCollisionData; + void** shape2CachedCollisionData = shape2Info.cachedCollisionData; + vec3 suppPointsA[MAXSUPPORTPOINTS]; // Support points of object A in local coordinates + vec3 suppPointsB[MAXSUPPORTPOINTS]; // Support points of object B in local coordinates + vec3 points[MAXSUPPORTPOINTS]; // Current points + TrianglesStore triangleStore; // Store the triangles + etk::Set triangleHeap; // list of face candidate of the EPA algorithm sorted lower square dist to upper square dist + triangleHeap.setComparator([](TriangleEPA * face1, TriangleEPA * face2) { + return (face1->getDistSquare() < face2->getDistSquare()); + }); + // etk::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) + etk::Transform3D body2Tobody1 = transform1.getInverse() * transform2; + // Matrix that transform a direction from local + // space of body 1 into local space of body 2 + etk::Quaternion rotateToBody2 = transform2.getOrientation().getInverse() * transform1.getOrientation(); + // Get the simplex computed previously by the GJK algorithm + int nbVertices = simplex.getSimplex(suppPointsA, suppPointsB, points); + // Compute the tolerance + float tolerance = FLTEPSILON * 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. + 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 + vec3 d = (points[1] - points[0]).safeNormalized(); + // Choose the coordinate axis from the minimal absolute component of the vector d + int minAxis = d.absolute().getMinAxis(); + // Compute sin(60) + float sin60 = float(sqrt(3.0)) * 0.5f; + // Create a rotation quaternion to rotate the vector v1 to get the vectors + // v2 and v3 + etk::Quaternion rotationQuat(d.x() * sin60, d.y() * sin60, d.z() * sin60, 0.5); + // Compute the vector v1, v2, v3 + vec3 v1 = d.cross(vec3(minAxis == 0, minAxis == 1, minAxis == 2)); + vec3 v2 = rotationQuat * v1; + vec3 v3 = rotationQuat * v2; + // Compute the support point in the direction of v1 + suppPointsA[2] = shape1->getLocalSupportPointWithMargin(v1, shape1CachedCollisionData); + suppPointsB[2] = body2Tobody1 * + shape2->getLocalSupportPointWithMargin(rotateToBody2 * (-v1), shape2CachedCollisionData); + points[2] = suppPointsA[2] - suppPointsB[2]; + // Compute the support point in the direction of v2 + suppPointsA[3] = shape1->getLocalSupportPointWithMargin(v2, shape1CachedCollisionData); + suppPointsB[3] = body2Tobody1 * + shape2->getLocalSupportPointWithMargin(rotateToBody2 * (-v2), shape2CachedCollisionData); + points[3] = suppPointsA[3] - suppPointsB[3]; + // Compute the support point in the direction of v3 + suppPointsA[4] = shape1->getLocalSupportPointWithMargin(v3, shape1CachedCollisionData); + suppPointsB[4] = body2Tobody1 * + shape2->getLocalSupportPointWithMargin(rotateToBody2 * (-v3), shape2CachedCollisionData); + points[4] = suppPointsA[4] - 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) + 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 + TriangleEPA* face0 = triangleStore.newTriangle(points, 0, 1, 2); + TriangleEPA* face1 = triangleStore.newTriangle(points, 0, 3, 1); + TriangleEPA* face2 = triangleStore.newTriangle(points, 0, 2, 3); + TriangleEPA* face3 = triangleStore.newTriangle(points, 1, 3, 2); + // If the ructed tetrahedron is not correct + if (!((face0 != NULL) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (face1 != NULL) hjkhjkhjkhkj (face2 != NULL) hjkhjkhjkhkj (face3 != NULL) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face0->getDistSquare() > 0.0 hjkhjkhjkhkj face1->getDistSquare() > 0.0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face2->getDistSquare() > 0.0 hjkhjkhjkhkj face3->getDistSquare() > 0.0)) { + return; + } + // Associate the edges of neighbouring triangle faces + link(EdgeEPA(face0, 0), EdgeEPA(face1, 2)); + link(EdgeEPA(face0, 1), EdgeEPA(face3, 2)); + link(EdgeEPA(face0, 2), EdgeEPA(face2, 0)); + link(EdgeEPA(face1, 0), EdgeEPA(face2, 2)); + link(EdgeEPA(face1, 1), EdgeEPA(face3, 0)); + link(EdgeEPA(face2, 1), EdgeEPA(face3, 1)); + // Add the triangle faces in the candidate heap + addFaceCandidate(face0, triangleHeap, FLTMAX); + addFaceCandidate(face1, triangleHeap, FLTMAX); + addFaceCandidate(face2, triangleHeap, FLTMAX); + addFaceCandidate(face3, triangleHeap, FLTMAX); + 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 + vec3 v1 = points[1] - points[0]; + vec3 v2 = points[2] - points[0]; + vec3 n = v1.cross(v2); + // Compute the two new vertices to obtain a hexahedron + suppPointsA[3] = shape1->getLocalSupportPointWithMargin(n, shape1CachedCollisionData); + suppPointsB[3] = body2Tobody1 * + shape2->getLocalSupportPointWithMargin(rotateToBody2 * (-n), shape2CachedCollisionData); + points[3] = suppPointsA[3] - suppPointsB[3]; + suppPointsA[4] = shape1->getLocalSupportPointWithMargin(-n, shape1CachedCollisionData); + suppPointsB[4] = body2Tobody1 * + shape2->getLocalSupportPointWithMargin(rotateToBody2 * n, shape2CachedCollisionData); + points[4] = suppPointsA[4] - suppPointsB[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 + face0 = triangleStore.newTriangle(points, 0, 1, 2); + face1 = triangleStore.newTriangle(points, 0, 3, 1); + face2 = triangleStore.newTriangle(points, 0, 2, 3); + 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 + face0 = triangleStore.newTriangle(points, 0, 1, 2); + face1 = triangleStore.newTriangle(points, 0, 4, 1); + face2 = triangleStore.newTriangle(points, 0, 2, 4); + face3 = triangleStore.newTriangle(points, 1, 4, 2); + } + else { + return; + } + // If the ructed tetrahedron is not correct + if (!( face0 != null + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face1 != null + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face2 != null + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face3 != null + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face0->getDistSquare() > 0.0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face1->getDistSquare() > 0.0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face2->getDistSquare() > 0.0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj face3->getDistSquare() > 0.0) ) { + return; + } + // Associate the edges of neighbouring triangle faces + link(EdgeEPA(face0, 0), EdgeEPA(face1, 2)); + link(EdgeEPA(face0, 1), EdgeEPA(face3, 2)); + link(EdgeEPA(face0, 2), EdgeEPA(face2, 0)); + link(EdgeEPA(face1, 0), EdgeEPA(face2, 2)); + link(EdgeEPA(face1, 1), EdgeEPA(face3, 0)); + link(EdgeEPA(face2, 1), EdgeEPA(face3, 1)); + // Add the triangle faces in the candidate heap + addFaceCandidate(face0, triangleHeap, FLTMAX); + addFaceCandidate(face1, triangleHeap, FLTMAX); + addFaceCandidate(face2, triangleHeap, FLTMAX); + addFaceCandidate(face3, triangleHeap, FLTMAX); + 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 = 0; + float upperBoundSquarePenDepth = FLTMAX; + do { + triangle = triangleHeap[0]; + triangleHeap.popFront(); + Log.info("rm from heap:"); + for (sizet iii=0; iiigetDistSquare()); + } + // 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 == MAXSUPPORTPOINTS) { + 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 * shape2->getLocalSupportPointWithMargin(rotateToBody2 * (-triangle->getClosestPoint()), shape2CachedCollisionData); + points[nbVertices] = suppPointsA[nbVertices] - suppPointsB[nbVertices]; + int indexNewVertex = nbVertices; + nbVertices++; + // Update the upper bound of the penetration depth + float wDotv = points[indexNewVertex].dot(triangle->getClosestPoint()); + Log.info(" point=" << points[indexNewVertex]); + Log.info("close point=" << triangle->getClosestPoint()); + Log.info(" ==>" << wDotv); + if (wDotv < 0.0) { + Log.error("depth penetration error " << wDotv); + continue; + } + EPHYASSERT(wDotv >= 0.0, "depth penetration error " << wDotv); + float wDotVSquare = wDotv * wDotv / triangle->getDistSquare(); + if (wDotVSquare < upperBoundSquarePenDepth) { + upperBoundSquarePenDepth = wDotVSquare; + } + // Compute the error + float error = wDotv - triangle->getDistSquare(); + if (error <= etk::max(tolerance, RELERRORSQUARE * wDotv) || + points[indexNewVertex] == points[(*triangle)[0]] || + points[indexNewVertex] == points[(*triangle)[1]] || + points[indexNewVertex] == points[(*triangle)[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. + sizet 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()) { + TriangleEPA* newTriangle = triangleStore[i]; + addFaceCandidate(newTriangle, triangleHeap, upperBoundSquarePenDepth); + i++; + } + } + } while( triangleHeap.size() > 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj triangleHeap[0]->getDistSquare() <= upperBoundSquarePenDepth); + // Compute the contact info + vector = transform1.getOrientation() * triangle->getClosestPoint(); + vec3 pALocal = triangle->computeClosestPointOfObject(suppPointsA); + vec3 pBLocal = body2Tobody1.getInverse() * triangle->computeClosestPointOfObject(suppPointsB); + vec3 normal = vector.safeNormalized(); + float penetrationDepth = vector.length(); + EPHYASSERT(penetrationDepth >= 0.0, "penetration depth <0"); + if (normal.length2() < FLTEPSILON) { + return; + } + // Create the contact info object + ContactPointInfo contactInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, shape2Info.collisionShape, normal, penetrationDepth, pALocal, pBLocal); + narrowPhaseCallback->notifyContact(shape1Info.overlappingPair, contactInfo); +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.hpp new file mode 100644 index 0000000..d60186a --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EPAAlgorithm.hpp @@ -0,0 +1,91 @@ +/** @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) + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + /// Maximum number of support points of the polytope + int MAXSUPPORTPOINTS = 100; + /// Maximum number of facets of the polytope + int MAXFACETS = 200; + /** + * @brief 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". + */ + class EPAAlgorithm { + private: + /// Private copy-ructor + EPAAlgorithm( EPAAlgorithm algorithm); + /// Private assignment operator + EPAAlgorithm operator=( EPAAlgorithm algorithm); + /// Add a triangle face in the candidate triangle heap + void addFaceCandidate(TriangleEPA* triangle, + etk::Set heap, + float 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 + if ( triangle->isClosestPointInternalToTriangle() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj triangle->getDistSquare() <= upperBoundSquarePenDepth) { + // Add the triangle face to the list of candidates + heap.add(triangle); + Log.info("add in heap:"); + for (sizet iii=0; iiigetDistSquare()); + } + } + } + // 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 + int isOriginInTetrahedron( vec3 p1, vec3 p2, vec3 p3, vec3 p4) ; + public: + /// Constructor + EPAAlgorithm(); + /// Destructor + ~EPAAlgorithm(); + /// Initalize the algorithm + void init() { + + } + // 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 + void computePenetrationDepthAndContactPoints( Simplex simplex, + CollisionShapeInfo shape1Info, + etk::Transform3D transform1, + CollisionShapeInfo shape2Info, + etk::Transform3D transform2, + vec3 v, + NarrowPhaseCallback* narrowPhaseCallback); + }; +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.cpp new file mode 100644 index 0000000..05d587e --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.cpp @@ -0,0 +1,97 @@ +/** @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) + */ +#include +#include +#include +#include + +using namespace ephysics; + + +EdgeEPA::EdgeEPA() { + +} + +EdgeEPA::EdgeEPA(TriangleEPA* ownerTriangle, int index): + this.ownerTriangle(ownerTriangle), + this.index(index) { + assert(index >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < 3); +} + +EdgeEPA::EdgeEPA( EdgeEPA obj): + this.ownerTriangle(obj.this.ownerTriangle), + this.index(obj.this.index) { + +} + +EdgeEPA::EdgeEPA(EdgeEPAhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj obj): + this.ownerTriangle(null) { + etk::swap(this.ownerTriangle, obj.this.ownerTriangle); + etk::swap(this.index, obj.this.index); +} + +int EdgeEPA::getSourceVertexIndex() { + return (*this.ownerTriangle)[this.index]; +} + +int EdgeEPA::getTargetVertexIndex() { + return (*this.ownerTriangle)[indexOfNextCounterClockwiseEdge(this.index)]; +} + +boolean EdgeEPA::computeSilhouette( vec3* vertices, int indexNewVertex, + TrianglesStore triangleStore) { + // 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)) { + TriangleEPA* triangle = triangleStore.newTriangle(vertices, indexNewVertex, + getTargetVertexIndex(), + getSourceVertexIndex()); + // If the triangle has been created + if (triangle != null) { + halfLink(EdgeEPA(triangle, 1), *this); + return true; + } + return false; + } else { + // The current triangle is visible and therefore obsolete + this.ownerTriangle->setIsObsolete(true); + int backup = triangleStore.getNbTriangles(); + if(!this.ownerTriangle->getAdjacentEdge(indexOfNextCounterClockwiseEdge(this->this.index)).computeSilhouette(vertices, + indexNewVertex, + triangleStore)) { + this.ownerTriangle->setIsObsolete(false); + TriangleEPA* triangle = triangleStore.newTriangle(vertices, indexNewVertex, + getTargetVertexIndex(), + getSourceVertexIndex()); + // If the triangle has been created + if (triangle != null) { + halfLink(EdgeEPA(triangle, 1), *this); + return true; + } + return false; + } else if (!this.ownerTriangle->getAdjacentEdge(indexOfPreviousCounterClockwiseEdge(this->this.index)).computeSilhouette(vertices, + indexNewVertex, + triangleStore)) { + this.ownerTriangle->setIsObsolete(false); + triangleStore.resize(backup); + TriangleEPA* triangle = triangleStore.newTriangle(vertices, indexNewVertex, + getTargetVertexIndex(), + getSourceVertexIndex()); + if (triangle != NULL) { + halfLink(EdgeEPA(triangle, 1), *this); + return true; + } + return false; + } + } + } + return true; +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.hpp new file mode 100644 index 0000000..9580aaf --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/EdgeEPA.hpp @@ -0,0 +1,75 @@ +/** @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) + */ +#pragma once +#include + +namespace ephysics { +class TriangleEPA; +class TrianglesStore; +/** + * @brief Class EdgeEPA + * This class represents an edge of the current polytope in the EPA algorithm. + */ +class EdgeEPA { + private: + /// Pointer to the triangle that contains this edge + TriangleEPA* this.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) + int this.index; + public: + /// Constructor + EdgeEPA(); + /// Constructor + EdgeEPA(TriangleEPA* ownerTriangle, int index); + /// Copy-ructor + EdgeEPA( EdgeEPA obj); + /// Move-ructor + EdgeEPA(EdgeEPAhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj obj); + /// Return the pointer to the owner triangle + TriangleEPA* getOwnerTriangle() { + return this.ownerTriangle; + } + /// Return the index of the edge in the triangle + int getIndex() { + return this.index; + } + /// Return index of the source vertex of the edge + int getSourceVertexIndex() ; + /// Return the index of the target vertex of the edge + int getTargetVertexIndex() ; + /// Execute the recursive silhouette algorithm from this edge + boolean computeSilhouette( vec3* vertices, int index, TrianglesStore triangleStore); + /// Assignment operator + EdgeEPA operator=( EdgeEPA obj) { + this.ownerTriangle = obj.this.ownerTriangle; + this.index = obj.this.index; + return *this; + } + /// Move operator + EdgeEPA operator=(EdgeEPAhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj obj) { + etk::swap(this.ownerTriangle, obj.this.ownerTriangle); + etk::swap(this.index, obj.this.index); + return *this; + } +}; + +// Return the index of the next counter-clockwise edge of the ownver triangle +inline int indexOfNextCounterClockwiseEdge(int iii) { + return (iii + 1) % 3; +} + +// Return the index of the previous counter-clockwise edge of the ownver triangle +inline int indexOfPreviousCounterClockwiseEdge(int iii) { + return (iii + 2) % 3; +} + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.cpp new file mode 100644 index 0000000..44ce827 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.cpp @@ -0,0 +1,102 @@ +/** @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) + */ +#include +#include +#include + +using namespace ephysics; + +TriangleEPA::TriangleEPA() { + +} + +TriangleEPA::TriangleEPA(int indexVertex1, int indexVertex2, int indexVertex3): + this.isObsolete(false) { + this.indicesVertices[0] = indexVertex1; + this.indicesVertices[1] = indexVertex2; + this.indicesVertices[2] = indexVertex3; +} + +void TriangleEPA::set(int indexVertex1, int indexVertex2, int indexVertex3) { + this.isObsolete = false; + this.indicesVertices[0] = indexVertex1; + this.indicesVertices[1] = indexVertex2; + this.indicesVertices[2] = indexVertex3; +} + +TriangleEPA::~TriangleEPA() { + +} + +boolean TriangleEPA::computeClosestPoint( vec3* vertices) { + vec3 p0 = vertices[this.indicesVertices[0]]; + vec3 v1 = vertices[this.indicesVertices[1]] - p0; + vec3 v2 = vertices[this.indicesVertices[2]] - p0; + float v1Dotv1 = v1.dot(v1); + float v1Dotv2 = v1.dot(v2); + float v2Dotv2 = v2.dot(v2); + float p0Dotv1 = p0.dot(v1); + 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.0) { + // Compute the closest point v + this.closestPoint = p0 + 1.0f / this.determinant * (this.lambda1 * v1 + this.lambda2 * v2); + // Compute the square distance of closest point to the origin + this.distSquare = this.closestPoint.dot(this.closestPoint); + return true; + } + return false; +} + +boolean ephysics::link( EdgeEPA edge0, EdgeEPA edge1) { + if ( edge0.getSourceVertexIndex() == edge1.getTargetVertexIndex() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj edge0.getTargetVertexIndex() == edge1.getSourceVertexIndex() ) { + edge0.getOwnerTriangle()->this.adjacentEdges[edge0.getIndex()] = edge1; + edge1.getOwnerTriangle()->this.adjacentEdges[edge1.getIndex()] = edge0; + return true; + } + return false; +} + +void ephysics::halfLink( EdgeEPA edge0, EdgeEPA edge1) { + assert( edge0.getSourceVertexIndex() == edge1.getTargetVertexIndex() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj edge0.getTargetVertexIndex() == edge1.getSourceVertexIndex()); + edge0.getOwnerTriangle()->this.adjacentEdges[edge0.getIndex()] = edge1; +} + + +boolean TriangleEPA::computeSilhouette( vec3* vertices, int indexNewVertex, + TrianglesStore triangleStore) { + 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 + boolean result = this.adjacentEdges[0].computeSilhouette(vertices, indexNewVertex, triangleStore) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj + this.adjacentEdges[1].computeSilhouette(vertices, indexNewVertex, triangleStore) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj + 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++) { + TriangleEPA* triangle = triangleStore[i]; + halfLink(triangle->getAdjacentEdge(1), EdgeEPA(triangle, 1)); + if (!link(EdgeEPA(triangle, 0), EdgeEPA(triangleStore[j], 2))) { + return false; + } + } + } + return result; +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.hpp new file mode 100644 index 0000000..9b6bd1b --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TriangleEPA.hpp @@ -0,0 +1,141 @@ + /** @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) + */ +#pragma once +#include +#include +#include +namespace ephysics { + boolean link( EdgeEPA edge0, EdgeEPA edge1); + void halfLink( EdgeEPA edge0, EdgeEPA edge1); + /** + * @brief Class TriangleEPA + * This class represents a triangle face of the current polytope in the EPA algorithm. + */ + class TriangleEPA { + private: + int this.indicesVertices[3]; //!< Indices of the vertices yi of the triangle + EdgeEPA this.adjacentEdges[3]; //!< Three adjacent edges of the triangle (edges of other triangles) + boolean this.isObsolete; //!< True if the triangle face is visible from the new support point + float this.determinant; //!< Determinant + vec3 this.closestPoint; //!< Point v closest to the origin on the affine hull of the triangle + float this.lambda1; //!< Lambda1 value such that v = lambda0 * y0 + lambda1 * y1 + lambda2 * y2 + float this.lambda2; //!< Lambda1 value such that v = lambda0 * y0 + lambda1 * y1 + lambda2 * y2 + float this.distSquare; //!< Square distance of the point closest point v to the origin + public: + /// Private copy-ructor + TriangleEPA( TriangleEPA triangle) { + this.indicesVertices[0] = triangle.this.indicesVertices[0]; + this.indicesVertices[1] = triangle.this.indicesVertices[1]; + this.indicesVertices[2] = triangle.this.indicesVertices[2]; + this.adjacentEdges[0] = triangle.this.adjacentEdges[0]; + this.adjacentEdges[1] = triangle.this.adjacentEdges[1]; + this.adjacentEdges[2] = triangle.this.adjacentEdges[2]; + this.isObsolete = triangle.this.isObsolete; + this.determinant = triangle.this.determinant; + this.closestPoint = triangle.this.closestPoint; + this.lambda1 = triangle.this.lambda1; + this.lambda2 = triangle.this.lambda2; + this.distSquare = triangle.this.distSquare; + } + /// Private assignment operator + TriangleEPA operator=( TriangleEPA triangle) { + this.indicesVertices[0] = triangle.this.indicesVertices[0]; + this.indicesVertices[1] = triangle.this.indicesVertices[1]; + this.indicesVertices[2] = triangle.this.indicesVertices[2]; + this.adjacentEdges[0] = triangle.this.adjacentEdges[0]; + this.adjacentEdges[1] = triangle.this.adjacentEdges[1]; + this.adjacentEdges[2] = triangle.this.adjacentEdges[2]; + this.isObsolete = triangle.this.isObsolete; + this.determinant = triangle.this.determinant; + this.closestPoint = triangle.this.closestPoint; + this.lambda1 = triangle.this.lambda1; + this.lambda2 = triangle.this.lambda2; + this.distSquare = triangle.this.distSquare; + return *this; + } + /// Constructor + TriangleEPA(); + /// Constructor + TriangleEPA(int v1, int v2, int v3); + /// Constructor + void set(int v1, int v2, int v3); + /// Destructor + ~TriangleEPA(); + /// Return an adjacent edge of the triangle + EdgeEPA getAdjacentEdge(int index) { + assert(index >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < 3); + return this.adjacentEdges[index]; + } + /// Set an adjacent edge of the triangle + void setAdjacentEdge(int index, EdgeEPA edge) { + assert(index >=0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < 3); + this.adjacentEdges[index] = edge; + } + /// Return the square distance of the closest point to origin + float getDistSquare() { + return this.distSquare; + } + /// Set the isObsolete value + void setIsObsolete(boolean isObsolete) { + this.isObsolete = isObsolete; + } + /// Return true if the triangle face is obsolete + boolean getIsObsolete() { + return this.isObsolete; + } + /// Return the point closest to the origin + vec3 getClosestPoint() { + return this.closestPoint; + } + // Return true if the closest point on affine hull is inside the triangle + boolean isClosestPointInternalToTriangle() { + return (this.lambda1 >= 0.0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.lambda2 >= 0.0 hjkhjkhjkhkj (this.lambda1 + this.lambda2) <= this.determinant); + } + /// Return true if the triangle is visible from a given vertex + boolean isVisibleFromVertex( vec3* vertices, int index) { + vec3 closestToVert = vertices[index] - this.closestPoint; + return (this.closestPoint.dot(closestToVert) > 0.0); + } + /// Compute the point v closest to the origin of this triangle + boolean computeClosestPoint( vec3* vertices); + /// Compute the point of an object closest to the origin + vec3 computeClosestPointOfObject( vec3* supportPointsOfObject) { + vec3 p0 = supportPointsOfObject[this.indicesVertices[0]]; + return p0 + 1.0f/this.determinant * (this.lambda1 * (supportPointsOfObject[this.indicesVertices[1]] - p0) + + this.lambda2 * (supportPointsOfObject[this.indicesVertices[2]] - p0)); + } + // 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 considered as being a candidate face in the future. + boolean computeSilhouette( vec3* vertices, int index, TrianglesStore triangleStore); + /// Access operator + int operator[](int pos) { + assert(pos >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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 along both edges). + friend boolean link( EdgeEPA edge0, EdgeEPA edge1); + /// 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. + friend void halfLink( EdgeEPA edge0, EdgeEPA edge1); + }; + +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.cpp new file mode 100644 index 0000000..6abfd36 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.cpp @@ -0,0 +1,10 @@ +/** @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) + */ +#include + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.hpp new file mode 100644 index 0000000..49c63d2 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/EPA/TrianglesStore.hpp @@ -0,0 +1,68 @@ +/** @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) + */ +#pragma once +#include +#include +#include + +namespace ephysics { + int MAXTRIANGLES = 200; // Maximum number of triangles + /** + * @brief This class stores several triangles of the polytope in the EPA algorithm. + */ + class TrianglesStore { + private: + etk::Array this.triangles; //!< Triangles + /// Private copy-ructor + TrianglesStore( TrianglesStore triangleStore) = delete; + /// Private assignment operator + TrianglesStore operator=( TrianglesStore triangleStore) = delete; + public: + /// Constructor + TrianglesStore() = default; + /// Clear all the storage + void clear() { + this.triangles.clear(); + } + /// Return the number of triangles + sizet getNbTriangles() { + return this.triangles.size(); + } + /// Set the number of triangles + void resize(int backup) { + if (backup > this.triangles.size()) { + Log.error("RESIZE BIGGER : " << backup << " > " << this.triangles.size()); + } + this.triangles.resize(backup); + } + /// Return the last triangle + TriangleEPA last() { + return this.triangles.back(); + } + /// Create a new triangle + TriangleEPA* newTriangle( vec3* vertices, int v0, int v1, int v2) { + // If we have not reached the maximum number of triangles + if (this.triangles.size() < MAXTRIANGLES) { + TriangleEPA tmp(v0, v1, v2); + if (!tmp.computeClosestPoint(vertices)) { + return null; + } + this.triangles.pushBack(etk::move(tmp)); + return this.triangles.back(); + } + // We are at the limit (internal) + return null; + } + /// Access operator + TriangleEPA operator[](int id) { + return this.triangles[id]; + } + }; +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.cpp new file mode 100644 index 0000000..c920b37 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.cpp @@ -0,0 +1,370 @@ +/** @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) + */ +#include +#include +#include +#include +#include + +using namespace ephysics; + +GJKAlgorithm::GJKAlgorithm() : NarrowPhaseAlgorithm() { + +} + +GJKAlgorithm::~GJKAlgorithm() { + +} + +void GJKAlgorithm::testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + NarrowPhaseCallback* narrowPhaseCallback) { + PROFILE("GJKAlgorithm::testCollision()"); + vec3 suppA; // Support point of object A + vec3 suppB; // Support point of object B + vec3 w; // Support point of Minkowski difference A-B + vec3 pA; // Closest point of object A + vec3 pB; // Closest point of object B + float vDotw; + float prevDistSquare; + assert(shape1Info.collisionShape->isConvex()); + assert(shape2Info.collisionShape->isConvex()); + ConvexShape* shape1 = staticcast< ConvexShape*>(shape1Info.collisionShape); + ConvexShape* shape2 = staticcast< ConvexShape*>(shape2Info.collisionShape); + void** shape1CachedCollisionData = shape1Info.cachedCollisionData; + void** shape2CachedCollisionData = shape2Info.cachedCollisionData; + // Get the local-space to world-space transforms + etk::Transform3D transform1 = shape1Info.shapeToWorldTransform; + etk::Transform3D transform2 = shape2Info.shapeToWorldTransform; + // etk::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) + etk::Transform3D body2Tobody1 = transform1.getInverse() * transform2; + // Matrix that transform a direction from local + // space of body 1 into local space of body 2 + etk::Matrix3x3 rotateToBody2 = transform2.getOrientation().getMatrix().getTranspose() * + transform1.getOrientation().getMatrix(); + // Initialize the margin (sum of margins of both objects) + float margin = shape1->getMargin() + shape2->getMargin(); + float marginSquare = margin * margin; + assert(margin > 0.0); + // Create a simplex set + Simplex simplex; + // Get the previous point V (last cached separating axis) + vec3 v = this.currentOverlappingPair->getCachedSeparatingAxis(); + // Initialize the upper bound for the square distance + float distSquare = FLTMAX; + do { + // Compute the support points for original objects (without margins) A and B + suppA = shape1->getLocalSupportPointWithoutMargin(-v, shape1CachedCollisionData); + suppB = body2Tobody1 * shape2->getLocalSupportPointWithoutMargin(rotateToBody2 * v, shape2CachedCollisionData); + // Compute the support point for the Minkowski difference A-B + w = suppA - suppB; + vDotw = v.dot(w); + // If the enlarge objects (with margins) do not intersect + if (vDotw > 0.0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vDotw * vDotw > distSquare * marginSquare) { + // Cache the current separating axis for frame coherence + this.currentOverlappingPair->setCachedSeparatingAxis(v); + // No intersection, we return + return; + } + // If the objects intersect only in the margins + if (simplex.isPointInSimplex(w) || distSquare - vDotw <= distSquare * RELERRORSQUARE) { + // 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 + float dist = sqrt(distSquare); + assert(dist > 0.0); + pA = (pA - (shape1->getMargin() / dist) * v); + pB = body2Tobody1.getInverse() * (pB + (shape2->getMargin() / dist) * v); + // Compute the contact info + vec3 normal = transform1.getOrientation() * (-v.safeNormalized()); + float penetrationDepth = margin - dist; + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0) return; + // Create the contact info object + ContactPointInfo contactInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, + shape2Info.collisionShape, normal, penetrationDepth, pA, pB); + narrowPhaseCallback->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()) { + // 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 + float dist = sqrt(distSquare); + assert(dist > 0.0); + pA = (pA - (shape1->getMargin() / dist) * v); + pB = body2Tobody1.getInverse() * (pB + (shape2->getMargin() / dist) * v); + // Compute the contact info + vec3 normal = transform1.getOrientation() * (-v.safeNormalized()); + float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0) return; + + // Create the contact info object + ContactPointInfo contactInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, + shape2Info.collisionShape, normal, penetrationDepth, pA, pB); + narrowPhaseCallback->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(v)) { + // 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 + float dist = sqrt(distSquare); + assert(dist > 0.0); + pA = (pA - (shape1->getMargin() / dist) * v); + pB = body2Tobody1.getInverse() * (pB + (shape2->getMargin() / dist) * v); + // Compute the contact info + vec3 normal = transform1.getOrientation() * (-v.safeNormalized()); + float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0) return; + + // Create the contact info object + ContactPointInfo contactInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, + shape2Info.collisionShape, normal, penetrationDepth, pA, pB); + narrowPhaseCallback->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 = v.length2(); + // If the distance to the closest point doesn't improve a lot + if (prevDistSquare - distSquare <= FLTEPSILON * prevDistSquare) { + simplex.backupClosestPointInSimplex(v); + + // Get the new squared distance + distSquare = v.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 + float dist = sqrt(distSquare); + assert(dist > 0.0); + pA = (pA - (shape1->getMargin() / dist) * v); + pB = body2Tobody1.getInverse() * (pB + (shape2->getMargin() / dist) * v); + // Compute the contact info + vec3 normal = transform1.getOrientation() * (-v.safeNormalized()); + float penetrationDepth = margin - dist; + + // Reject the contact if the penetration depth is negative (due too numerical errors) + if (penetrationDepth <= 0.0) return; + + // Create the contact info object + ContactPointInfo contactInfo(shape1Info.proxyShape, shape2Info.proxyShape, shape1Info.collisionShape, + shape2Info.collisionShape, normal, penetrationDepth, pA, pB); + narrowPhaseCallback->notifyContact(shape1Info.overlappingPair, contactInfo); + // There is an intersection, therefore we return + return; + } + } while(!simplex.isFull() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj distSquare > FLTEPSILON * + 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. + return computePenetrationDepthForEnlargedObjects(shape1Info, transform1, shape2Info, + transform2, narrowPhaseCallback, v); +} + +void GJKAlgorithm::computePenetrationDepthForEnlargedObjects( CollisionShapeInfo shape1Info, + etk::Transform3D transform1, + CollisionShapeInfo shape2Info, + etk::Transform3D transform2, + NarrowPhaseCallback* narrowPhaseCallback, + vec3 v) { + PROFILE("GJKAlgorithm::computePenetrationDepthForEnlargedObjects()"); + Simplex simplex; + vec3 suppA; + vec3 suppB; + vec3 w; + float vDotw; + float distSquare = FLTMAX; + float prevDistSquare; + assert(shape1Info.collisionShape->isConvex()); + assert(shape2Info.collisionShape->isConvex()); + ConvexShape* shape1 = staticcast< ConvexShape*>(shape1Info.collisionShape); + ConvexShape* shape2 = staticcast< ConvexShape*>(shape2Info.collisionShape); + void** shape1CachedCollisionData = shape1Info.cachedCollisionData; + void** shape2CachedCollisionData = shape2Info.cachedCollisionData; + // etk::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) + etk::Transform3D body2ToBody1 = transform1.getInverse() * transform2; + // Matrix that transform a direction from local space of body 1 into local space of body 2 + etk::Matrix3x3 rotateToBody2 = transform2.getOrientation().getMatrix().getTranspose() * + transform1.getOrientation().getMatrix(); + do { + // Compute the support points for the enlarged object A and B + suppA = shape1->getLocalSupportPointWithMargin(-v, shape1CachedCollisionData); + suppB = body2ToBody1 * shape2->getLocalSupportPointWithMargin(rotateToBody2 * v, shape2CachedCollisionData); + // Compute the support point for the Minkowski difference A-B + w = suppA - suppB; + vDotw = v.dot(w); + // If the enlarge objects do not intersect + if (vDotw > 0.0) { + // No intersection, we return + return; + } + // Add the new support point to the simplex + simplex.addPoint(w, suppA, suppB); + if (simplex.isAffinelyDependent()) { + return; + } + if (!simplex.computeClosestPoint(v)) { + return; + } + // Store and update the square distance + prevDistSquare = distSquare; + distSquare = v.length2(); + if (prevDistSquare - distSquare <= FLTEPSILON * prevDistSquare) { + return; + } + } while(!simplex.isFull() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj distSquare > FLTEPSILON * + 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 + return this.algoEPA.computePenetrationDepthAndContactPoints(simplex, shape1Info, + transform1, shape2Info, transform2, + v, narrowPhaseCallback); +} + +boolean GJKAlgorithm::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + vec3 suppA; // Support point of object A + vec3 w; // Support point of Minkowski difference A-B + float prevDistSquare; + assert(proxyShape->getCollisionShape()->isConvex()); + ConvexShape* shape = staticcast< ConvexShape*>(proxyShape->getCollisionShape()); + void** shapeCachedCollisionData = proxyShape->getCachedCollisionData(); + // Support point of object B (object B is a single point) + vec3 suppB(localPoint); + // Create a simplex set + Simplex simplex; + // Initial supporting direction + vec3 v(1, 1, 1); + // Initialize the upper bound for the square distance + float distSquare = FLTMAX; + do { + // Compute the support points for original objects (without margins) A and B + suppA = shape->getLocalSupportPointWithoutMargin(-v, shapeCachedCollisionData); + // Compute the support point for the Minkowski difference A-B + w = suppA - 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 <= FLTEPSILON * prevDistSquare) { + return false; + } + } while( !simplex.isFull() + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj distSquare > FLTEPSILON * simplex.getMaxLengthSquareOfAPoint()); + // The point is inside the collision shape + return true; +} + +boolean GJKAlgorithm::raycast( Ray ray, ProxyShape* proxyShape, RaycastInfo raycastInfo) { + assert(proxyShape->getCollisionShape()->isConvex()); + ConvexShape* shape = staticcast< ConvexShape*>(proxyShape->getCollisionShape()); + void** shapeCachedCollisionData = proxyShape->getCachedCollisionData(); + vec3 suppA; // Current lower bound point on the ray (starting at ray's origin) + vec3 suppB; // Support point on the collision shape + float machineEpsilonSquare = FLTEPSILON * FLTEPSILON; + float epsilon = float(0.0001); + // Convert the ray origin and direction into the local-space of the collision shape + vec3 rayDirection = ray.point2 - ray.point1; + // If the points of the segment are two close, return no hit + if (rayDirection.length2() < machineEpsilonSquare) return false; + vec3 w; + // Create a simplex set + Simplex simplex; + vec3 n(0.0f, float(0.0), float(0.0)); + float lambda = 0.0f; + suppA = ray.point1; // Current lower bound point on the ray (starting at ray's origin) + suppB = shape->getLocalSupportPointWithoutMargin(rayDirection, shapeCachedCollisionData); + vec3 v = suppA - suppB; + float vDotW, vDotR; + float distSquare = v.length2(); + int nbIterations = 0; + // GJK Algorithm loop + while (distSquare > epsilon hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nbIterations < MAXITERATIONSGJKRAYCAST) { + // Compute the support points + suppB = shape->getLocalSupportPointWithoutMargin(v, shapeCachedCollisionData); + w = suppA - suppB; + vDotW = v.dot(w); + if (vDotW > float(0)) { + vDotR = v.dot(rayDirection); + if (vDotR >= -machineEpsilonSquare) { + return false; + } else { + // We have found a better lower bound for the hit point along the ray + lambda = lambda - vDotW / vDotR; + suppA = ray.point1 + lambda * rayDirection; + w = suppA - 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 < FLTEPSILON) { + return false; + } + // Compute the closet points of both objects (without the margins) + vec3 pointA; + vec3 pointB; + 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 = vec3(float(0), float(0), float(0)); + } + return true; +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.hpp new file mode 100644 index 0000000..9754f25 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/GJKAlgorithm.hpp @@ -0,0 +1,83 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include + +namespace ephysics { + float RELERROR = float(1.0e-3); + float RELERRORSQUARE = RELERROR * RELERROR; + int MAXITERATIONSGJKRAYCAST = 32; + /** + * @brief 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. + */ + class GJKAlgorithm : public NarrowPhaseAlgorithm { + private : + EPAAlgorithm this.algoEPA; //!< EPA Algorithm + /// Private copy-ructor + GJKAlgorithm( GJKAlgorithm algorithm); + /// Private assignment operator + GJKAlgorithm operator=( GJKAlgorithm 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. + void computePenetrationDepthForEnlargedObjects( CollisionShapeInfo shape1Info, + etk::Transform3D transform1, + CollisionShapeInfo shape2Info, + etk::Transform3D transform2, + NarrowPhaseCallback* narrowPhaseCallback, + vec3 v); + public : + /// Constructor + GJKAlgorithm(); + /// Destructor + ~GJKAlgorithm(); + /// Initalize the algorithm + virtual void init(CollisionDetection* collisionDetection) { + NarrowPhaseAlgorithm::init(collisionDetection); + this.algoEPA.init(); + }; + // Compute a contact info if the two collision shapes collide. + /// This method implements the Hybrid Technique for computing the penetration depth by + /// running the GJK algorithm on original objects (without margin). If the shapes intersect + /// only in the margins, the method compute the penetration depth and contact points + /// (of enlarged objects). If the original objects (without margin) intersect, we + /// call the computePenetrationDepthForEnlargedObjects() method that run the GJK + /// algorithm on the enlarged object to obtain a simplex polytope that contains the + /// origin, they we give that simplex polytope to the EPA algorithm which will compute + /// the correct penetration depth and contact points between the enlarged objects. + virtual void testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + NarrowPhaseCallback* narrowPhaseCallback); + /// Use the GJK Algorithm to find if a point is inside a convex collision shape + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape); + /// 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". + boolean raycast( Ray ray, ProxyShape* proxyShape, RaycastInfo raycastInfo); + }; +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.cpp new file mode 100644 index 0000000..7218e74 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.cpp @@ -0,0 +1,368 @@ +/** @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) + */ +#include + +using namespace ephysics; + +Simplex::Simplex() : mBitsCurrentSimplex(0x0), mAllBits(0x0) { + +} + +// 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 +void Simplex::addPoint( vec3 point, vec3 suppPointA, vec3 suppPointB) { + assert(!isFull()); + + mLastFound = 0; + mLastFoundBit = 0x1; + + // Look for the bit corresponding to one of the four point that is not in + // the current simplex + while (overlap(mBitsCurrentSimplex, mLastFoundBit)) { + mLastFound++; + mLastFoundBit <<= 1; + } + + assert(mLastFound < 4); + + // Add the point into the simplex + mPoints[mLastFound] = point; + mPointsLengthSquare[mLastFound] = point.dot(point); + mAllBits = mBitsCurrentSimplex | mLastFoundBit; + + // Update the cached values + updateCache(); + + // Compute the cached determinant values + computeDeterminants(); + + // Add the support points of objects A and B + mSuppPointsA[mLastFound] = suppPointA; + mSuppPointsB[mLastFound] = suppPointB; +} + +// Return true if the point is in the simplex +boolean Simplex::isPointInSimplex( vec3 point) { + int i; + Bits bit; + + // For each four possible points in the simplex + for (i=0, bit = 0x1; i<4; i++, bit <<= 1) { + // Check if the current point is in the simplex + if (overlap(mAllBits, bit) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj point == mPoints[i]) return true; + } + + return false; +} + +// Update the cached values used during the GJK algorithm +void Simplex::updateCache() { + int i; + Bits bit; + + // For each of the four possible points of the simplex + for (i=0, bit = 0x1; i<4; i++, bit <<= 1) { + // If the current points is in the simplex + if (overlap(mBitsCurrentSimplex, bit)) { + + // Compute the distance between two points in the possible simplex set + mDiffLength[i][mLastFound] = mPoints[i] - mPoints[mLastFound]; + mDiffLength[mLastFound][i] = -mDiffLength[i][mLastFound]; + + // Compute the squared length of the vector + // distances from points in the possible simplex set + mNormSquare[i][mLastFound] = mNormSquare[mLastFound][i] = + mDiffLength[i][mLastFound].dot(mDiffLength[i][mLastFound]); + } + } +} + +// Return the points of the simplex +int Simplex::getSimplex(vec3* suppPointsA, vec3* suppPointsB, + vec3* points) { + int nbVertices = 0; + int i; + Bits bit; + + // For each four point in the possible simplex + for (i=0, bit=0x1; i<4; i++, bit <<=1) { + + // If the current point is in the simplex + if (overlap(mBitsCurrentSimplex, bit)) { + + // Store the points + suppPointsA[nbVertices] = this->mSuppPointsA[nbVertices]; + suppPointsB[nbVertices] = this->mSuppPointsB[nbVertices]; + points[nbVertices] = this->mPoints[nbVertices]; + + nbVertices++; + } + } + + // Return the number of points in the simplex + return nbVertices; +} + +// Compute the cached determinant values +void Simplex::computeDeterminants() { + mDet[mLastFoundBit][mLastFound] = 1.0; + + // If the current simplex is not empty + if (!isEmpty()) { + int i; + Bits bitI; + + // For each possible four points in the simplex set + for (i=0, bitI = 0x1; i<4; i++, bitI <<= 1) { + + // If the current point is in the simplex + if (overlap(mBitsCurrentSimplex, bitI)) { + Bits bit2 = bitI | mLastFoundBit; + + mDet[bit2][i] = mDiffLength[mLastFound][i].dot(mPoints[mLastFound]); + mDet[bit2][mLastFound] = mDiffLength[i][mLastFound].dot(mPoints[i]); + + + int j; + Bits bitJ; + + for (j=0, bitJ = 0x1; j 0 for each "i" in Ix and +/// 2. delta(X U {yj})j <= 0 for each "j" not in Ix +boolean Simplex::isValidSubset(Bits subset) { + int i; + Bits bit; + + // For each four point in the possible simplex set + for (i=0, bit=0x1; i<4; i++, bit <<= 1) { + if (overlap(mAllBits, 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 (mDet[subset][i] <= 0.0) { + // The subset is not valid + return false; + } + } + // If the point is not in the subset and the value delta(X U {yj})j + // is bigger than zero + else if (mDet[subset | bit][i] > 0.0) { + // The subset is not valid + return false; + } + } + } + + return true; +} + +// Compute the closest points "pA" and "pB" of object A and B. +/// The points are computed as follows : +/// pA = sum(lambdai * ai) where "ai" are the support points of object A +/// pB = sum(lambdai * bi) where "bi" are the support points of object B +/// with lambdai = deltaXi / deltaX +void Simplex::computeClosestPointsOfAandB(vec3 pA, vec3 pB) { + float deltaX = 0.0; + pA.setValue(0.0, 0.0, 0.0); + pB.setValue(0.0, 0.0, 0.0); + int i; + Bits bit; + + // For each four points in the possible simplex set + for (i=0, bit=0x1; i<4; i++, bit <<= 1) { + // If the current point is part of the simplex + if (overlap(mBitsCurrentSimplex, bit)) { + deltaX += mDet[mBitsCurrentSimplex][i]; + pA += mDet[mBitsCurrentSimplex][i] * mSuppPointsA[i]; + pB += mDet[mBitsCurrentSimplex][i] * mSuppPointsB[i]; + } + } + + assert(deltaX > 0.0); + float factor = 1.0f / deltaX; + pA *= factor; + pB *= factor; +} + +// Compute the closest point "v" 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. +boolean Simplex::computeClosestPoint(vec3 v) { + Bits subset; + + // For each possible simplex set + for (subset=mBitsCurrentSimplex; 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, mBitsCurrentSimplex) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj isValidSubset(subset | mLastFoundBit)) { + mBitsCurrentSimplex = subset | mLastFoundBit; // Add the last added point to the current simplex + v = computeClosestPointForSubset(mBitsCurrentSimplex); // 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(mLastFoundBit)) { + mBitsCurrentSimplex = mLastFoundBit; // Set the current simplex to the set that contains only the last added point + mMaxLengthSquare = mPointsLengthSquare[mLastFound]; // Update the maximum square length + v = mPoints[mLastFound]; // The closest point of the simplex "v" is the last added point + return true; + } + + // The algorithm failed to found a point + return false; +} + +// Backup the closest point +void Simplex::backupClosestPointInSimplex(vec3 v) { + float minDistSquare = FLTMAX; + Bits bit; + + for (bit = mAllBits; bit != 0x0; bit--) { + if (isSubset(bit, mAllBits) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj isProperSubset(bit)) { + vec3 u = computeClosestPointForSubset(bit); + float distSquare = u.dot(u); + if (distSquare < minDistSquare) { + minDistSquare = distSquare; + mBitsCurrentSimplex = bit; + v = u; + } + } + } +} + +// Return the closest point "v" in the convex hull of the points in the subset +// represented by the bits "subset" +vec3 Simplex::computeClosestPointForSubset(Bits subset) { + vec3 v(0.0, 0.0, 0.0); // Closet point v = sum(lambdai * points[i]) + mMaxLengthSquare = 0.0; + float deltaX = 0.0; // deltaX = sum of all det[subset][i] + int i; + Bits bit; + + // For each four point in the possible simplex set + for (i=0, bit=0x1; i<4; i++, bit <<= 1) { + // If the current point is in the subset + if (overlap(subset, bit)) { + // deltaX = sum of all det[subset][i] + deltaX += mDet[subset][i]; + + if (mMaxLengthSquare < mPointsLengthSquare[i]) { + mMaxLengthSquare = mPointsLengthSquare[i]; + } + + // Closest point v = sum(lambdai * points[i]) + v += mDet[subset][i] * mPoints[i]; + } + } + + assert(deltaX > 0.0); + + // Return the closet point "v" in the convex hull for the given subset + return (1.0f / deltaX) * v; +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.hpp new file mode 100644 index 0000000..0508c68 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/GJK/Simplex.hpp @@ -0,0 +1,167 @@ +/** @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) + */ +#pragma once + +// Libraries +#include +#include + +/// ReactPhysics3D namespace +namespace ephysics { + +// Type definitions +typedef int Bits; + +// Class Simplex +/** + * 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. + */ +class Simplex { + + private: + + // -------------------- Attributes -------------------- // + + /// Current points + vec3 mPoints[4]; + + /// pointsLengthSquare[i] = (points[i].length)^2 + float mPointsLengthSquare[4]; + + /// Maximum length of pointsLengthSquare[i] + float mMaxLengthSquare; + + /// Support points of object A in local coordinates + vec3 mSuppPointsA[4]; + + /// Support points of object B in local coordinates + vec3 mSuppPointsB[4]; + + /// diff[i][j] contains points[i] - points[j] + vec3 mDiffLength[4][4]; + + /// Cached determinant values + float mDet[16][4]; + + /// norm[i][j] = (diff[i][j].length())^2 + float mNormSquare[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 + Bits mBitsCurrentSimplex; + + /// Number between 1 and 4 that identify the last found support point + Bits mLastFound; + + /// Position of the last found support point (lastFoundBit = 0x1 << lastFound) + Bits mLastFoundBit; + + /// allBits = bitsCurrentSimplex | lastFoundBit; + Bits mAllBits; + + // -------------------- Methods -------------------- // + + /// Private copy-ructor + Simplex( Simplex simplex); + + /// Private assignment operator + Simplex operator=( Simplex simplex); + + /// Return true if some bits of "a" overlap with bits of "b" + boolean overlap(Bits a, Bits b) ; + + /// Return true if the bits of "b" is a subset of the bits of "a" + boolean isSubset(Bits a, Bits b) ; + + /// Return true if the subset is a valid one for the closest point computation. + boolean isValidSubset(Bits subset) ; + + /// Return true if the subset is a proper subset. + boolean isProperSubset(Bits subset) ; + + /// Update the cached values used during the GJK algorithm + void updateCache(); + + /// Compute the cached determinant values + void computeDeterminants(); + + /// Return the closest point "v" in the convex hull of a subset of points + vec3 computeClosestPointForSubset(Bits subset); + + public: + + // -------------------- Methods -------------------- // + + /// Constructor + Simplex(); + + /// Return true if the simplex contains 4 points + boolean isFull() ; + + /// Return true if the simplex is empty + boolean isEmpty() ; + + /// Return the points of the simplex + int getSimplex(vec3* mSuppPointsA, vec3* mSuppPointsB, + vec3* mPoints) ; + + /// Return the maximum squared length of a point + float getMaxLengthSquareOfAPoint() ; + + /// Add a new support point of (A-B) into the simplex. + void addPoint( vec3 point, vec3 suppPointA, vec3 suppPointB); + + /// Return true if the point is in the simplex + boolean isPointInSimplex( vec3 point) ; + + /// Return true if the set is affinely dependent + boolean isAffinelyDependent() ; + + /// Backup the closest point + void backupClosestPointInSimplex(vec3 point); + + /// Compute the closest points "pA" and "pB" of object A and B. + void computeClosestPointsOfAandB(vec3 pA, vec3 pB) ; + + /// Compute the closest point to the origin of the current simplex. + boolean computeClosestPoint(vec3 v); +}; + +// Return true if some bits of "a" overlap with bits of "b" +inline boolean Simplex::overlap(Bits a, Bits b) { + return ((a b) != 0x0); +} + +// Return true if the bits of "b" is a subset of the bits of "a" +inline boolean Simplex::isSubset(Bits a, Bits b) { + return ((a b) == a); +} + +// Return true if the simplex contains 4 points +inline boolean Simplex::isFull() { + return (mBitsCurrentSimplex == 0xf); +} + +// Return true if the simple is empty +inline boolean Simplex::isEmpty() { + return (mBitsCurrentSimplex == 0x0); +} + +// Return the maximum squared length of a point +inline float Simplex::getMaxLengthSquareOfAPoint() { + return mMaxLengthSquare; +} + +} + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.cpp new file mode 100644 index 0000000..cf55205 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.cpp @@ -0,0 +1,24 @@ +/** @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) + */ +#include + +using namespace ephysics; + +NarrowPhaseAlgorithm::NarrowPhaseAlgorithm(): + this.currentOverlappingPair(null) { + +} + +void NarrowPhaseAlgorithm::init(CollisionDetection* collisionDetection) { + this.collisionDetection = collisionDetection; +} + +void NarrowPhaseAlgorithm::setCurrentOverlappingPair(OverlappingPair* overlappingPair) { + this.currentOverlappingPair = overlappingPair; +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.hpp new file mode 100644 index 0000000..5abb43f --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/NarrowPhaseAlgorithm.hpp @@ -0,0 +1,62 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include + +namespace ephysics { + + class CollisionDetection; + + /** + * @brief It is the base class for a narrow-phase collision + * callback class. + */ + class NarrowPhaseCallback { + public: + virtual ~NarrowPhaseCallback() = default; + /// Called by a narrow-phase collision algorithm when a new contact has been found + virtual void notifyContact(OverlappingPair* overlappingPair, + ContactPointInfo contactInfo) = 0; + + }; + + /** + * @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. + */ + class NarrowPhaseAlgorithm { + protected : + CollisionDetection* this.collisionDetection; //!< Pointer to the collision detection object + OverlappingPair* this.currentOverlappingPair; //!< Overlapping pair of the bodies currently tested for collision + /// Private copy-ructor + NarrowPhaseAlgorithm( NarrowPhaseAlgorithm algorithm) = delete; + /// Private assignment operator + NarrowPhaseAlgorithm operator=( NarrowPhaseAlgorithm algorithm) = delete; + public : + /// Constructor + NarrowPhaseAlgorithm(); + /// Destructor + virtual ~NarrowPhaseAlgorithm() = default; + /// Initalize the algorithm + virtual void init(CollisionDetection* collisionDetection); + /// Set the current overlapping pair of bodies + void setCurrentOverlappingPair(OverlappingPair* overlappingPair); + /// Compute a contact info if the two bounding volume collide + virtual void testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + NarrowPhaseCallback* narrowPhaseCallback) = 0; + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.cpp b/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.cpp new file mode 100644 index 0000000..9a45810 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.cpp @@ -0,0 +1,51 @@ +/** @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) + */ +#include +#include + +ephysics::SphereVsSphereAlgorithm::SphereVsSphereAlgorithm() : + NarrowPhaseAlgorithm() { + +} + +void ephysics::SphereVsSphereAlgorithm::testCollision( ephysics::CollisionShapeInfo shape1Info, + ephysics::CollisionShapeInfo shape2Info, + ephysics::NarrowPhaseCallback* narrowPhaseCallback) { + // Get the sphere collision shapes + ephysics::SphereShape* sphereShape1 = staticcast< ephysics::SphereShape*>(shape1Info.collisionShape); + ephysics::SphereShape* sphereShape2 = staticcast< ephysics::SphereShape*>(shape2Info.collisionShape); + // Get the local-space to world-space transforms + etk::Transform3D transform1 = shape1Info.shapeToWorldTransform; + etk::Transform3D transform2 = shape2Info.shapeToWorldTransform; + // Compute the distance between the centers + vec3 vectorBetweenCenters = transform2.getPosition() - transform1.getPosition(); + float squaredDistanceBetweenCenters = vectorBetweenCenters.length2(); + // Compute the sum of the radius + float sumRadius = sphereShape1->getRadius() + sphereShape2->getRadius(); + // If the sphere collision shapes intersect + if (squaredDistanceBetweenCenters <= sumRadius * sumRadius) { + vec3 centerSphere2InBody1LocalSpace = transform1.getInverse() * transform2.getPosition(); + vec3 centerSphere1InBody2LocalSpace = transform2.getInverse() * transform1.getPosition(); + vec3 intersectionOnBody1 = sphereShape1->getRadius() * centerSphere2InBody1LocalSpace.safeNormalized(); + vec3 intersectionOnBody2 = sphereShape2->getRadius() * centerSphere1InBody2LocalSpace.safeNormalized(); + float penetrationDepth = sumRadius - etk::sqrt(squaredDistanceBetweenCenters); + + // Create the contact info object + ephysics::ContactPointInfo contactInfo(shape1Info.proxyShape, + shape2Info.proxyShape, + shape1Info.collisionShape, + shape2Info.collisionShape, + vectorBetweenCenters.safeNormalized(), + penetrationDepth, + intersectionOnBody1, + intersectionOnBody2); + // Notify about the new contact + narrowPhaseCallback->notifyContact(shape1Info.overlappingPair, contactInfo); + } +} diff --git a/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.hpp b/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.hpp new file mode 100644 index 0000000..1790f54 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/narrowphase/SphereVsSphereAlgorithm.hpp @@ -0,0 +1,42 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + /** + * @brief It is used to compute the narrow-phase collision detection + * between two sphere collision shapes. + */ + class SphereVsSphereAlgorithm : public NarrowPhaseAlgorithm { + protected : + SphereVsSphereAlgorithm( SphereVsSphereAlgorithm) = delete; + SphereVsSphereAlgorithm operator=( SphereVsSphereAlgorithm) = delete; + public : + /** + * @brief Constructor + */ + SphereVsSphereAlgorithm(); + /** + * @brief Destructor + */ + virtual ~SphereVsSphereAlgorithm() = default; + /** + * @brief Compute a contact info if the two bounding volume collide + */ + virtual void testCollision( CollisionShapeInfo shape1Info, + CollisionShapeInfo shape2Info, + NarrowPhaseCallback* narrowPhaseCallback); + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/AABB.cpp b/src/org/atriaSoft/ephysics/collision/shapes/AABB.cpp new file mode 100644 index 0000000..858a9d2 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/AABB.cpp @@ -0,0 +1,210 @@ +/** @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) + */ + +// Libraries +#include +#include + +using namespace ephysics; +using namespace std; + +AABB::AABB(): + this.minCoordinates(0,0,0), + this.maxCoordinates(0,0,0) { + +} + +AABB::AABB( vec3 minCoordinates, vec3 maxCoordinates): + this.minCoordinates(minCoordinates), + this.maxCoordinates(maxCoordinates) { + +} + +AABB::AABB( AABB aabb): + this.minCoordinates(aabb.this.minCoordinates), + this.maxCoordinates(aabb.this.maxCoordinates) { + +} + +void AABB::mergeWithAABB( AABB aabb) { + this.minCoordinates.setX(etk::min(this.minCoordinates.x(), aabb.this.minCoordinates.x())); + this.minCoordinates.setY(etk::min(this.minCoordinates.y(), aabb.this.minCoordinates.y())); + this.minCoordinates.setZ(etk::min(this.minCoordinates.z(), aabb.this.minCoordinates.z())); + this.maxCoordinates.setX(etk::max(this.maxCoordinates.x(), aabb.this.maxCoordinates.x())); + this.maxCoordinates.setY(etk::max(this.maxCoordinates.y(), aabb.this.maxCoordinates.y())); + this.maxCoordinates.setZ(etk::max(this.maxCoordinates.z(), aabb.this.maxCoordinates.z())); +} + +void AABB::mergeTwoAABBs( AABB aabb1, AABB aabb2) { + this.minCoordinates.setX(etk::min(aabb1.this.minCoordinates.x(), aabb2.this.minCoordinates.x())); + this.minCoordinates.setY(etk::min(aabb1.this.minCoordinates.y(), aabb2.this.minCoordinates.y())); + this.minCoordinates.setZ(etk::min(aabb1.this.minCoordinates.z(), aabb2.this.minCoordinates.z())); + this.maxCoordinates.setX(etk::max(aabb1.this.maxCoordinates.x(), aabb2.this.maxCoordinates.x())); + this.maxCoordinates.setY(etk::max(aabb1.this.maxCoordinates.y(), aabb2.this.maxCoordinates.y())); + this.maxCoordinates.setZ(etk::max(aabb1.this.maxCoordinates.z(), aabb2.this.maxCoordinates.z())); +} + +boolean AABB::contains( AABB aabb) { + boolean isInside = true; + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.minCoordinates.x() <= aabb.this.minCoordinates.x(); + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.minCoordinates.y() <= aabb.this.minCoordinates.y(); + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.minCoordinates.z() <= aabb.this.minCoordinates.z(); + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.maxCoordinates.x() >= aabb.this.maxCoordinates.x(); + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.maxCoordinates.y() >= aabb.this.maxCoordinates.y(); + isInside = isInside hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.maxCoordinates.z() >= aabb.this.maxCoordinates.z(); + return isInside; +} + +AABB AABB::createAABBForTriangle( vec3* trianglePoints) { + vec3 minCoords(trianglePoints[0].x(), trianglePoints[0].y(), trianglePoints[0].z()); + vec3 maxCoords(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 AABB(minCoords, maxCoords); +} + +boolean AABB::testRayIntersect( Ray ray) { + vec3 point2 = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); + vec3 e = this.maxCoordinates - this.minCoordinates; + vec3 d = point2 - ray.point1; + vec3 m = ray.point1 + point2 - this.minCoordinates - this.maxCoordinates; + // Test if the AABB face normals are separating axis + float adx = etk::abs(d.x()); + if (etk::abs(m.x()) > e.x() + adx) { + return false; + } + float ady = etk::abs(d.y()); + if (etk::abs(m.y()) > e.y() + ady) { + return false; + } + float adz = etk::abs(d.z()); + if (etk::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) + float epsilon = 0.00001; + adx += epsilon; + ady += epsilon; + adz += epsilon; + // Test if the cross products between face normals and ray direction are + // separating axis + if (etk::abs(m.y() * d.z() - m.z() * d.y()) > e.y() * adz + e.z() * ady) { + return false; + } + if (etk::abs(m.z() * d.x() - m.x() * d.z()) > e.x() * adz + e.z() * adx) { + return false; + } + if (etk::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; +} + +vec3 AABB::getExtent() { + return this.maxCoordinates - this.minCoordinates; +} + +void AABB::inflate(float dx, float dy, float dz) { + this.maxCoordinates += vec3(dx, dy, dz); + this.minCoordinates -= vec3(dx, dy, dz); +} + +boolean AABB::testCollision( AABB aabb) { + if ( this.maxCoordinates.x() < aabb.this.minCoordinates.x() + || aabb.this.maxCoordinates.x() < this.minCoordinates.x()) { + return false; + } + if ( this.maxCoordinates.y() < aabb.this.minCoordinates.y() + || aabb.this.maxCoordinates.y() < this.minCoordinates.y()) { + return false; + } + if ( this.maxCoordinates.z() < aabb.this.minCoordinates.z() + || aabb.this.maxCoordinates.z() < this.minCoordinates.z()) { + return false; + } + return true; +} + +float AABB::getVolume() { + vec3 diff = this.maxCoordinates - this.minCoordinates; + return (diff.x() * diff.y() * diff.z()); +} + +boolean AABB::testCollisionTriangleAABB( vec3* trianglePoints) { + if (min3(trianglePoints[0].x(), trianglePoints[1].x(), trianglePoints[2].x()) > this.maxCoordinates.x()) { + return false; + } + if (min3(trianglePoints[0].y(), trianglePoints[1].y(), trianglePoints[2].y()) > this.maxCoordinates.y()) { + return false; + } + if (min3(trianglePoints[0].z(), trianglePoints[1].z(), trianglePoints[2].z()) > this.maxCoordinates.z()) { + return false; + } + if (max3(trianglePoints[0].x(), trianglePoints[1].x(), trianglePoints[2].x()) < this.minCoordinates.x()) { + return false; + } + if (max3(trianglePoints[0].y(), trianglePoints[1].y(), trianglePoints[2].y()) < this.minCoordinates.y()) { + return false; + } + if (max3(trianglePoints[0].z(), trianglePoints[1].z(), trianglePoints[2].z()) < this.minCoordinates.z()) { + return false; + } + return true; +} + +boolean AABB::contains( vec3 point) { + return point.x() >= this.minCoordinates.x() - FLTEPSILON hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj point.x() <= this.maxCoordinates.x() + FLTEPSILON + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj point.y() >= this.minCoordinates.y() - FLTEPSILON hjkhjkhjkhkj point.y() <= this.maxCoordinates.y() + FLTEPSILON + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj point.z() >= this.minCoordinates.z() - FLTEPSILON hjkhjkhjkhkj point.z() <= this.maxCoordinates.z() + FLTEPSILON; +} + +AABB AABB::operator=( AABB aabb) { + if (this != aabb) { + this.minCoordinates = aabb.this.minCoordinates; + this.maxCoordinates = aabb.this.maxCoordinates; + } + return *this; +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/AABB.hpp b/src/org/atriaSoft/ephysics/collision/shapes/AABB.hpp new file mode 100644 index 0000000..0a7f97b --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/AABB.hpp @@ -0,0 +1,153 @@ +/** @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) + */ +#pragma once +#include +namespace ephysics { + /** + * @brief 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 + * minimum and maximum world coordinates of the three axis. + */ + class AABB { + private : + /// Minimum world coordinates of the AABB on the x,y and z axis + vec3 this.minCoordinates; + /// Maximum world coordinates of the AABB on the x,y and z axis + vec3 this.maxCoordinates; + public : + /** + * @brief default contructor + */ + AABB(); + /** + * @brief contructor Whit sizes + * @param[in] minCoordinates Minimum coordinates + * @param[in] maxCoordinates Maximum coordinates + */ + AABB( vec3 minCoordinates, vec3 maxCoordinates); + /** + * @brief Copy-contructor + * @param[in] aabb the object to copy + */ + AABB( AABB aabb); + /** + * @brief Get the center point of the AABB box + * @return The 3D position of the center + */ + vec3 getCenter() { + return (this.minCoordinates + this.maxCoordinates) * 0.5f; + } + /** + * @brief Get the minimum coordinates of the AABB + * @return The 3d minimum coordonates + */ + vec3 getMin() { + return this.minCoordinates; + } + /** + * @brief Set the minimum coordinates of the AABB + * @param[in] min The 3d minimum coordonates + */ + void setMin( vec3 min) { + this.minCoordinates = min; + } + /** + * @brief Return the maximum coordinates of the AABB + * @return The 3d maximum coordonates + */ + vec3 getMax() { + return this.maxCoordinates; + } + /** + * @brief Set the maximum coordinates of the AABB + * @param[in] max The 3d maximum coordonates + */ + void setMax( vec3 max) { + this.maxCoordinates = max; + } + /** + * @brief Get the size of the AABB in the three dimension x, y and z + * @return the AABB 3D size + */ + vec3 getExtent() ; + /** + * @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 + */ + void inflate(float dx, float dy, float dz); + /** + * @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 + */ + boolean testCollision( AABB aabb) ; + /** + * @brief Get the volume of the AABB + * @return The 3D volume. + */ + float getVolume() ; + /** + * @brief Merge the AABB in parameter with the current one + * @param[in] aabb Other AABB box to merge. + */ + void mergeWithAABB( AABB aabb); + /** + * @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. + */ + void mergeTwoAABBs( AABB aabb1, AABB aabb2); + /** + * @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 + */ + boolean contains( AABB aabb) ; + /** + * @brief Return true if a point is inside the AABB + * @param[in] point Point to check. + * @return true The point in contained inside + */ + boolean contains( vec3 point) ; + /** + * @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 + */ + boolean testCollisionTriangleAABB( vec3* trianglePoints) ; + /** + * @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 + */ + boolean testRayIntersect( Ray ray) ; + /** + * @brief Create and return an AABB for a triangle + * @param[in] trianglePoints List of 3 point od a triangle + * @return An AABB box + */ + static AABB createAABBForTriangle( vec3* trianglePoints); + /** + * @brief Assignment operator + * @param[in] aabb The other box to compare + * @return reference on this + */ + AABB operator=( AABB aabb); + friend class DynamicAABBTree; + }; + + +} \ No newline at end of file diff --git a/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.cpp new file mode 100644 index 0000000..7812eaf --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.cpp @@ -0,0 +1,133 @@ +/** @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) + */ +// Libraries +#include +#include +#include +#include + +using namespace ephysics; + +BoxShape::BoxShape( vec3 extent, float margin): + ConvexShape(BOX, margin), + this.extent(extent - vec3(margin, margin, margin)) { + assert(extent.x() > 0.0f hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj extent.x() > margin); + assert(extent.y() > 0.0f hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj extent.y() > margin); + assert(extent.z() > 0.0f hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj extent.z() > margin); +} + +void BoxShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + float factor = (1.0f / float(3.0)) * mass; + vec3 realExtent = this.extent + vec3(this.margin, this.margin, this.margin); + float xSquare = realExtent.x() * realExtent.x(); + float ySquare = realExtent.y() * realExtent.y(); + float zSquare = realExtent.z() * realExtent.z(); + tensor.setValue(factor * (ySquare + zSquare), 0.0, 0.0, + 0.0, factor * (xSquare + zSquare), 0.0, + 0.0, 0.0, factor * (xSquare + ySquare)); +} + +boolean BoxShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + vec3 rayDirection = ray.point2 - ray.point1; + float tMin = FLTMIN; + float tMax = FLTMAX; + vec3 normalDirection(0,0,0); + vec3 currentNormal(0,0,0); + // For each of the three slabs + for (int iii=0; iii<3; ++iii) { + // If ray is parallel to the slab + if (etk::abs(rayDirection[iii]) < FLTEPSILON) { + // If the ray's origin is not inside the slab, there is no hit + if (ray.point1[iii] > this.extent[iii] || ray.point1[iii] < -this.extent[iii]) { + return false; + } + } else { + // Compute the intersection of the ray with the near and far plane of the slab + float oneOverD = 1.0f / rayDirection[iii]; + float t1 = (-this.extent[iii] - ray.point1[iii]) * oneOverD; + float t2 = (this.extent[iii] - ray.point1[iii]) * oneOverD; + currentNormal[0] = (iii == 0) ? -this.extent[iii] : 0.0f; + currentNormal[1] = (iii == 1) ? -this.extent[iii] : 0.0f; + currentNormal[2] = (iii == 2) ? -this.extent[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) { + etk::swap(t1, t2); + currentNormal = -currentNormal; + } + // Compute the intersection of the of slab intersection interval with previous slabs + if (t1 > tMin) { + tMin = t1; + normalDirection = currentNormal; + } + tMax = etk::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 == vec3(0,0,0)) { + return false; + } + // The ray intersects the three slabs, we compute the hit point + vec3 localHitPoint = ray.point1 + tMin * rayDirection; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = tMin; + raycastInfo.worldPoint = localHitPoint; + raycastInfo.worldNormal = normalDirection; + return true; +} + +vec3 BoxShape::getExtent() { + return this.extent + vec3(this.margin, this.margin, this.margin); +} + +void BoxShape::setLocalScaling( vec3 scaling) { + this.extent = (this.extent / this.scaling) * scaling; + CollisionShape::setLocalScaling(scaling); +} + +void BoxShape::getLocalBounds(vec3 min, vec3 max) { + // Maximum bounds + max = this.extent + vec3(this.margin, this.margin, this.margin); + // Minimum bounds + min = -max; +} + +sizet BoxShape::getSizeInBytes() { + return sizeof(BoxShape); +} + +vec3 BoxShape::getLocalSupportPointWithoutMargin( vec3 direction, + void** cachedCollisionData) { + return vec3(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()); +} + +boolean BoxShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + return ( localPoint.x() < this.extent[0] + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.x() > -this.extent[0] + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() < this.extent[1] + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() > -this.extent[1] + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.z() < this.extent[2] + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.z() > -this.extent[2]); +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.hpp new file mode 100644 index 0000000..9a19b89 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/BoxShape.hpp @@ -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, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + +/** + * @brief It 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 + * ructor of the box shape. Otherwise, it is recommended to use the + * default margin distance by not using the "margin" parameter in the ructor. + */ +class BoxShape : public ConvexShape { + public: + /** + * @brief Constructor + * @param extent The vector with the three extents of the box (in meters) + * @param margin The collision margin (in meters) around the collision shape + */ + BoxShape( vec3 extent, float margin = OBJECTMARGIN); + /// DELETE copy-ructor + BoxShape( BoxShape shape) = delete; + /// DELETE assignment operator + BoxShape operator=( BoxShape shape) = delete; + /** + * @brief Return the extents of the box + * @return The vector with the three extents of the box shape (in meters) + */ + vec3 getExtent() ; + void setLocalScaling( vec3 scaling) override; + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + protected: + vec3 this.extent; //!< Extent sizes of the box in the x, y and z direction + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; +}; + +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.cpp new file mode 100644 index 0000000..4351fc6 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.cpp @@ -0,0 +1,260 @@ +/** @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) + */ +#include +#include +#include + +using namespace ephysics; + +CapsuleShape::CapsuleShape(float radius, float height): + ConvexShape(CAPSULE, radius), + this.halfHeight(height * 0.5f) { + assert(radius > 0.0f); + assert(height > 0.0f); +} + +void CapsuleShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + // The inertia tensor formula for a capsule can be found in : Game Engine Gems, Volume 1 + float height = this.halfHeight + this.halfHeight; + float radiusSquare = this.margin * this.margin; + float heightSquare = height * height; + float radiusSquareDouble = radiusSquare + radiusSquare; + float factor1 = float(2.0) * this.margin / (float(4.0) * this.margin + float(3.0) * height); + float factor2 = float(3.0) * height / (float(4.0) * this.margin + float(3.0) * height); + float sum1 = float(0.4) * radiusSquareDouble; + float sum2 = float(0.75) * height * this.margin + 0.5f * heightSquare; + float sum3 = float(0.25) * radiusSquare + float(1.0 / 12.0) * heightSquare; + float IxxAndzz = factor1 * mass * (sum1 + sum2) + factor2 * mass * sum3; + float Iyy = factor1 * mass * sum1 + factor2 * mass * float(0.25) * radiusSquareDouble; + tensor.setValue(IxxAndzz, 0.0, 0.0, + 0.0, Iyy, 0.0, + 0.0, 0.0, IxxAndzz); +} + +boolean CapsuleShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + float diffYCenterSphere1 = localPoint.y() - this.halfHeight; + float diffYCenterSphere2 = localPoint.y() + this.halfHeight; + float xSquare = localPoint.x() * localPoint.x(); + float zSquare = localPoint.z() * localPoint.z(); + 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 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj + localPoint.y() < this.halfHeight hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() > -this.halfHeight) || + (xSquare + zSquare + diffYCenterSphere1 * diffYCenterSphere1) < squareRadius || + (xSquare + zSquare + diffYCenterSphere2 * diffYCenterSphere2) < squareRadius; +} + +boolean CapsuleShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + vec3 n = ray.point2 - ray.point1; + float epsilon = float(0.01); + vec3 p(float(0), -this.halfHeight, float(0)); + vec3 q(float(0), this.halfHeight, float(0)); + vec3 d = q - p; + vec3 m = ray.point1 - p; + float t; + float mDotD = m.dot(d); + float nDotD = n.dot(d); + float dDotD = d.dot(d); + // Test if the segment is outside the cylinder + float vec1DotD = (ray.point1 - vec3(0.0f, -this.halfHeight - this.margin, float(0.0))).dot(d); + if ( vec1DotD < 0.0f + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vec1DotD + nDotD < float(0.0)) { + return false; + } + float ddotDExtraCaps = float(2.0) * this.margin * d.y(); + if ( vec1DotD > dDotD + ddotDExtraCaps + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj vec1DotD + nDotD > dDotD + ddotDExtraCaps) { + return false; + } + float nDotN = n.dot(n); + float mDotN = m.dot(n); + float a = dDotD * nDotN - nDotD * nDotD; + float k = m.dot(m) - this.margin * this.margin; + float c = dDotD * k - mDotD * mDotD; + // If the ray is parallel to the capsule axis + if (etk::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 intersect an endcap of the capsule + // If the ray intersects with the "p" endcap of the capsule + if (mDotD < 0.0f) { + // Check intersection between the ray and the "p" sphere endcap of the capsule + vec3 hitLocalPoint; + float hitFraction; + if (raycastWithSphereEndCap(ray.point1, ray.point2, p, ray.maxFraction, hitLocalPoint, hitFraction)) { + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = hitFraction; + raycastInfo.worldPoint = hitLocalPoint; + vec3 normalDirection = hitLocalPoint - p; + raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else if (mDotD > dDotD) { // If the ray intersects with the "q" endcap of the cylinder + // Check intersection between the ray and the "q" sphere endcap of the capsule + vec3 hitLocalPoint; + float hitFraction; + if (raycastWithSphereEndCap(ray.point1, ray.point2, q, ray.maxFraction, hitLocalPoint, hitFraction)) { + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = hitFraction; + raycastInfo.worldPoint = hitLocalPoint; + vec3 normalDirection = hitLocalPoint - q; + raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else { + // If the origin is inside the cylinder, we return no hit + return false; + } + } + float b = dDotD * mDotN - nDotD * mDotD; + 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 intersection along the ray) + float t0 = t = (-b - etk::sqrt(discriminant)) / a; + // If the intersection is outside the finite cylinder of the capsule on "p" endcap side + float value = mDotD + t * nDotD; + if (value < 0.0f) { + // Check intersection between the ray and the "p" sphere endcap of the capsule + vec3 hitLocalPoint; + float hitFraction; + if (raycastWithSphereEndCap(ray.point1, ray.point2, p, ray.maxFraction, hitLocalPoint, hitFraction)) { + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = hitFraction; + raycastInfo.worldPoint = hitLocalPoint; + vec3 normalDirection = hitLocalPoint - p; + raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } else if (value > dDotD) { // If the intersection is outside the finite cylinder on the "q" side + // Check intersection between the ray and the "q" sphere endcap of the capsule + vec3 hitLocalPoint; + float hitFraction; + if (raycastWithSphereEndCap(ray.point1, ray.point2, q, ray.maxFraction, hitLocalPoint, hitFraction)) { + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = hitFraction; + raycastInfo.worldPoint = hitLocalPoint; + vec3 normalDirection = hitLocalPoint - q; + raycastInfo.worldNormal = normalDirection; + return true; + } + return false; + } + t = t0; + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 v = localHitPoint - p; + vec3 w = (v.dot(d) / d.length2()) * d; + vec3 normalDirection = (localHitPoint - (p + w)).safeNormalized(); + raycastInfo.worldNormal = normalDirection; + return true; +} + +boolean CapsuleShape::raycastWithSphereEndCap( vec3 point1, + vec3 point2, + vec3 sphereCenter, + float maxFraction, + vec3 hitLocalPoint, + float hitFraction) { + vec3 m = point1 - sphereCenter; + float c = m.dot(m) - this.margin * this.margin; + // If the origin of the ray is inside the sphere, we return no intersection + if (c < 0.0f) { + return false; + } + vec3 rayDirection = point2 - point1; + 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 intersection + if (b > 0.0f) { + return false; + } + float raySquareLength = rayDirection.length2(); + // Compute the discriminant of the quadratic equation + float discriminant = b * b - raySquareLength * c; + // If the discriminant is negative or the ray length is very small, there is no intersection + if ( discriminant < 0.0f + || raySquareLength < FLTEPSILON) { + return false; + } + // Compute the solution "t" closest to the origin + float t = -b - etk::sqrt(discriminant); + assert(t >= 0.0f); + // If the hit point is withing the segment ray fraction + if (t < maxFraction * raySquareLength) { + // Compute the intersection information + t /= raySquareLength; + hitFraction = t; + hitLocalPoint = point1 + t * rayDirection; + return true; + } + return false; +} + +float CapsuleShape::getRadius() { + return this.margin; +} + +float CapsuleShape::getHeight() { + return this.halfHeight + this.halfHeight; +} + +void CapsuleShape::setLocalScaling( vec3 scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y()) * scaling.y(); + this.margin = (this.margin / this.scaling.x()) * scaling.x(); + CollisionShape::setLocalScaling(scaling); +} + +sizet CapsuleShape::getSizeInBytes() { + return sizeof(CapsuleShape); +} + +void CapsuleShape::getLocalBounds(vec3 min, vec3 max) { + // Maximum bounds + max.setX(this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(this.margin); + // Minimum bounds + min.setX(-this.margin); + min.setY(-max.y()); + min.setZ(min.x()); +} + +vec3 CapsuleShape::getLocalSupportPointWithoutMargin( vec3 direction, + void** cachedCollisionData) { + // Support point top sphere + float dotProductTop = this.halfHeight * direction.y(); + // Support point bottom sphere + float dotProductBottom = -this.halfHeight * direction.y(); + // Return the point with the maximum dot product + if (dotProductTop > dotProductBottom) { + return vec3(0, this.halfHeight, 0); + } + return vec3(0, -this.halfHeight, 0); +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.hpp new file mode 100644 index 0000000..6eb9f12 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CapsuleShape.hpp @@ -0,0 +1,66 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + /** + * @brief It 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. + */ + class CapsuleShape : public ConvexShape { + public : + /** + * @brief Constructor + * @param radius The radius of the capsule (in meters) + * @param height The height of the capsule (in meters) + */ + CapsuleShape(float radius, float height); + /// DELETE copy-ructor + CapsuleShape( CapsuleShape shape) = delete; + /// DELETE assignment operator + CapsuleShape operator=( CapsuleShape shape) = delete; + /** + * Get the radius of the capsule + * @return The radius of the capsule shape (in meters) + */ + float getRadius() ; + /** + * @brief Return the height of the capsule + * @return The height of the capsule shape (in meters) + */ + float getHeight() ; + void setLocalScaling( vec3 scaling) override; + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + protected: + float this.halfHeight; //!< Half height of the capsule (height = distance between the centers of the two spheres) + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + /** + * @brief Raycasting method between a ray one of the two spheres end cap of the capsule + */ + boolean raycastWithSphereEndCap( vec3 point1, + vec3 point2, + vec3 sphereCenter, + float maxFraction, + vec3 hitLocalPoint, + float hitFraction) ; + sizet getSizeInBytes() override; + }; +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.cpp new file mode 100644 index 0000000..e3865e1 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.cpp @@ -0,0 +1,53 @@ +/** @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) + */ +#include +#include +#include + +// We want to use the ReactPhysics3D namespace +using namespace ephysics; + +CollisionShape::CollisionShape(CollisionShapeType type) : + this.type(type), + this.scaling(1.0f, 1.0f, 1.0f) { + +} + +void CollisionShape::computeAABB(AABB aabb, etk::Transform3D transform) { + PROFILE("CollisionShape::computeAABB()"); + // Get the local bounds in x,y and z direction + vec3 minBounds(0,0,0); + vec3 maxBounds(0,0,0); + getLocalBounds(minBounds, maxBounds); + // Rotate the local bounds according to the orientation of the body + etk::Matrix3x3 worldAxis = transform.getOrientation().getMatrix().getAbsolute(); + vec3 worldMinBounds(worldAxis.getColumn(0).dot(minBounds), + worldAxis.getColumn(1).dot(minBounds), + worldAxis.getColumn(2).dot(minBounds)); + vec3 worldMaxBounds(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 + vec3 minCoordinates = transform.getPosition() + worldMinBounds; + vec3 maxCoordinates = transform.getPosition() + worldMaxBounds; + // Update the AABB with the new minimum and maximum coordinates + aabb.setMin(minCoordinates); + aabb.setMax(maxCoordinates); +} + +int CollisionShape::computeNbMaxContactManifolds(CollisionShapeType shapeType1, + CollisionShapeType shapeType2) { + // If both shapes are convex + if (isConvex(shapeType1) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj isConvex(shapeType2)) { + return NBMAXCONTACTMANIFOLDSCONVEXSHAPE; + } + // If there is at least one concave shape + return NBMAXCONTACTMANIFOLDSCONCAVESHAPE; +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.hpp new file mode 100644 index 0000000..6ecd0d1 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CollisionShape.hpp @@ -0,0 +1,114 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ephysics { +enum CollisionShapeType {TRIANGLE, BOX, SPHERE, CONE, CYLINDER, + CAPSULE, CONVEXMESH, CONCAVEMESH, HEIGHTFIELD}; + int NBCOLLISIONSHAPETYPES = 9; + +class ProxyShape; +class CollisionBody; + +/** + * This abstract class represents the collision shape associated with a + * body that is used during the narrow-phase collision detection. + */ +class CollisionShape { + public : + /// Constructor + CollisionShape(CollisionShapeType type); + /// DELETE copy-ructor + CollisionShape( CollisionShape shape) = delete; + /// DELETE assignment operator + CollisionShape operator=( CollisionShape shape) = delete; + /// Virtualize destructor + virtual ~CollisionShape() {}; + /** + * @brief Get the type of the collision shapes + * @return The type of the collision shape (box, sphere, cylinder, ...) + */ + 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 + */ + virtual boolean isConvex() = 0; + /** + * @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 + */ + virtual void getLocalBounds(vec3 min, vec3 max) =0; + /// Return the scaling vector of the collision shape + vec3 getScaling() { + return this.scaling; + } + /** + * @brief Set the scaling vector of the collision shape + */ + virtual void setLocalScaling( vec3 scaling) { + this.scaling = scaling; + } + /** + * @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 + */ + virtual void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) =0; + /** + * @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 etk::Transform3D used to compute the AABB of the collision shape + */ + virtual void computeAABB(AABB aabb, etk::Transform3D transform) ; + /** + * @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 + */ + static boolean isConvex(CollisionShapeType shapeType) { + return shapeType != CONCAVEMESH + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shapeType != HEIGHTFIELD; + } + /** + * @brief Get the maximum number of contact + * @return The maximum number of contact manifolds in an overlapping pair given two shape types + */ + static int computeNbMaxContactManifolds(CollisionShapeType shapeType1, + CollisionShapeType shapeType2); + friend class ProxyShape; + friend class CollisionWorld; + protected : + CollisionShapeType this.type; //!< Type of the collision shape + vec3 this.scaling; //!< Scaling vector of the collision shape + /// Return true if a point is inside the collision shape + virtual boolean testPointInside( vec3 worldPoint, ProxyShape* proxyShape) = 0; + /// Raycast method with feedback information + virtual boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) = 0; + /// Return the number of bytes used by the collision shape + virtual sizet getSizeInBytes() = 0; +}; + + + +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.cpp new file mode 100644 index 0000000..f44d7e7 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.cpp @@ -0,0 +1,147 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +ConcaveMeshShape::ConcaveMeshShape(TriangleMesh* triangleMesh): + ConcaveShape(CONCAVEMESH) { + this.triangleMesh = triangleMesh; + this.raycastTestType = FRONT; + initBVHTree(); +} + +void ConcaveMeshShape::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; subPartgetNbSubparts(); subPart++) { + // Get the triangle vertex array of the current sub-part + TriangleVertexArray* triangleVertexArray = this.triangleMesh->getSubpart(subPart); + // For each triangle of the concave mesh + for (sizet iii=0; iiigetNbTriangles(); ++iii) { + ephysics::Triangle trianglePoints = triangleVertexArray->getTriangle(iii); + vec3 trianglePoints2[3]; + trianglePoints2[0] = trianglePoints[0]; + trianglePoints2[1] = trianglePoints[1]; + trianglePoints2[2] = trianglePoints[2]; + // Create the AABB for the triangle + AABB aabb = AABB::createAABBForTriangle(trianglePoints2); + aabb.inflate(this.triangleMargin, this.triangleMargin, this.triangleMargin); + // Add the AABB with the index of the triangle into the dynamic AABB tree + this.dynamicAABBTree.addObject(aabb, subPart, iii); + } + } +} + +void ConcaveMeshShape::getTriangleVerticesWithIndexPointer(int subPart, int triangleIndex, vec3* outTriangleVertices) { + EPHYASSERT(outTriangleVertices != null, "Input check error"); + // Get the triangle vertex array of the current sub-part + TriangleVertexArray* triangleVertexArray = this.triangleMesh->getSubpart(subPart); + if (triangleVertexArray == null) { + Log.error("get null ..."); + } + ephysics::Triangle trianglePoints = triangleVertexArray->getTriangle(triangleIndex); + outTriangleVertices[0] = trianglePoints[0] * this.scaling; + outTriangleVertices[1] = trianglePoints[1] * this.scaling; + outTriangleVertices[2] = trianglePoints[2] * this.scaling; +} + +void ConcaveMeshShape::testAllTriangles(TriangleCallback callback, AABB localAABB) { + // Ask the Dynamic AABB Tree to report all the triangles that are overlapping + // with the AABB of the convex shape. + this.dynamicAABBTree.reportAllShapesOverlappingWithAABB(localAABB, [](int nodeId) { + // Get the node data (triangle index and mesh subpart index) + int* data = this.dynamicAABBTree.getNodeDataInt(nodeId); + // Get the triangle vertices for this node from the concave mesh shape + vec3 trianglePoints[3]; + getTriangleVerticesWithIndexPointer(data[0], data[1], trianglePoints); + // Call the callback to test narrow-phase collision with this triangle + callback.testTriangle(trianglePoints); + }); +} + +boolean ConcaveMeshShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + PROFILE("ConcaveMeshShape::raycast()"); + // Create the callback object that will compute ray casting against triangles + ConcaveMeshRaycastCallback raycastCallback(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, [](int nodeId, ephysics::Ray ray) mutable { return raycastCallback(nodeId, ray);}); + raycastCallback.raycastTriangles(); + return raycastCallback.getIsHit(); +} + +float ConcaveMeshRaycastCallback::operator()(int nodeId, Ray ray) { + // Add the id of the hit AABB node into + this.hitAABBNodes.pushBack(nodeId); + return ray.maxFraction; +} + +void ConcaveMeshRaycastCallback::raycastTriangles() { + etk::Vector::Iterator it; + float smallestHitFraction = this.ray.maxFraction; + for (it = this.hitAABBNodes.begin(); it != this.hitAABBNodes.end(); ++it) { + // Get the node data (triangle index and mesh subpart index) + int* data = this.dynamicAABBTree.getNodeDataInt(*it); + // Get the triangle vertices for this node from the concave mesh shape + vec3 trianglePoints[3]; + this.concaveMeshShape.getTriangleVerticesWithIndexPointer(data[0], data[1], trianglePoints); + // Create a triangle collision shape + float margin = this.concaveMeshShape.getTriangleMargin(); + TriangleShape triangleShape(trianglePoints[0], trianglePoints[1], trianglePoints[2], margin); + triangleShape.setRaycastTestType(this.concaveMeshShape.getRaycastTestType()); + // Ray casting test against the collision shape + RaycastInfo raycastInfo; + boolean isTriangleHit = triangleShape.raycast(this.ray, raycastInfo, this.proxyShape); + // If the ray hit the collision shape + if (isTriangleHit hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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; + } + } +} + +sizet ConcaveMeshShape::getSizeInBytes() { + return sizeof(ConcaveMeshShape); +} + +void ConcaveMeshShape::getLocalBounds(vec3 min, vec3 max) { + // Get the AABB of the whole tree + AABB treeAABB = this.dynamicAABBTree.getRootAABB(); + min = treeAABB.getMin(); + max = treeAABB.getMax(); +} + +void ConcaveMeshShape::setLocalScaling( vec3 scaling) { + CollisionShape::setLocalScaling(scaling); + this.dynamicAABBTree.reset(); + initBVHTree(); +} + +void ConcaveMeshShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, 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.setValue(mass, 0, 0, + 0, mass, 0, + 0, 0, mass); +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.hpp new file mode 100644 index 0000000..984e911 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveMeshShape.hpp @@ -0,0 +1,85 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace ephysics { + class ConcaveMeshShape; + class ConcaveMeshRaycastCallback { + private: + etk::Vector this.hitAABBNodes; + DynamicAABBTree this.dynamicAABBTree; + ConcaveMeshShape this.concaveMeshShape; + ProxyShape* this.proxyShape; + RaycastInfo this.raycastInfo; + Ray this.ray; + boolean this.isHit; + public: + // Constructor + ConcaveMeshRaycastCallback( DynamicAABBTree dynamicAABBTree, + ConcaveMeshShape concaveMeshShape, + ProxyShape* proxyShape, + RaycastInfo raycastInfo, + Ray ray): + this.dynamicAABBTree(dynamicAABBTree), + this.concaveMeshShape(concaveMeshShape), + this.proxyShape(proxyShape), + this.raycastInfo(raycastInfo), + this.ray(ray), + this.isHit(false) { + + } + /// Collect all the AABB nodes that are hit by the ray in the Dynamic AABB Tree + float operator()(int nodeId, ephysics::Ray ray); + /// Raycast all collision shapes that have been collected + void raycastTriangles(); + /// Return true if a raycast hit has been found + boolean getIsHit() { + return this.isHit; + } + }; + /** + * @brief 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. + */ + class ConcaveMeshShape : public ConcaveShape { + public: + /// Constructor + ConcaveMeshShape(TriangleMesh* triangleMesh); + /// DELETE copy-ructor + ConcaveMeshShape( ConcaveMeshShape shape) = delete; + /// DELETE assignment operator + ConcaveMeshShape operator=( ConcaveMeshShape shape) = delete; + virtual void getLocalBounds(vec3 min, vec3 max) override; + virtual void setLocalScaling( vec3 scaling) override; + virtual void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + virtual void testAllTriangles(TriangleCallback callback, AABB localAABB) override; + friend class ConvexTriangleAABBOverlapCallback; + friend class ConcaveMeshRaycastCallback; + protected: + TriangleMesh* this.triangleMesh; //!< Triangle mesh + DynamicAABBTree this.dynamicAABBTree; //!< Dynamic AABB tree to accelerate collision with the triangles + virtual boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + virtual sizet getSizeInBytes() override; + /// Insert all the triangles into the dynamic AABB tree + void initBVHTree(); + /// Return the three vertices coordinates (in the array outTriangleVertices) of a triangle + /// given the start vertex index pointer of the triangle. + void getTriangleVerticesWithIndexPointer(int subPart, + int triangleIndex, + vec3* outTriangleVertices) ; + }; + +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.cpp new file mode 100644 index 0000000..6c19feb --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.cpp @@ -0,0 +1,50 @@ +/** @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) + */ +#include + + +// We want to use the ReactPhysics3D namespace +using namespace ephysics; + +// Constructor +ConcaveShape::ConcaveShape(CollisionShapeType type): + CollisionShape(type), + this.isSmoothMeshCollisionEnabled(false), + this.triangleMargin(0), + this.raycastTestType(FRONT) { + +} + +float ConcaveShape::getTriangleMargin() { + return this.triangleMargin; +} + +boolean ConcaveShape::isConvex() { + return false; +} + +boolean ConcaveShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + return false; +} + +boolean ConcaveShape::getIsSmoothMeshCollisionEnabled() { + return this.isSmoothMeshCollisionEnabled; +} + +void ConcaveShape::setIsSmoothMeshCollisionEnabled(boolean isEnabled) { + this.isSmoothMeshCollisionEnabled = isEnabled; +} + +TriangleRaycastSide ConcaveShape::getRaycastTestType() { + return this.raycastTestType; +} + +void ConcaveShape::setRaycastTestType(TriangleRaycastSide testType) { + this.raycastTestType = testType; +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.hpp new file mode 100644 index 0000000..73ce9b9 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConcaveShape.hpp @@ -0,0 +1,70 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * @brief It is used to encapsulate a callback method for + * a single triangle of a ConcaveMesh. + */ + class TriangleCallback { + public: + virtual ~TriangleCallback() = default; + /// Report a triangle + virtual void testTriangle( vec3* trianglePoints)=0; + }; + + /** + * @brief This abstract class represents a concave collision shape associated with a + * body that is used during the narrow-phase collision detection. + */ + class ConcaveShape : public CollisionShape { + public : + /// Constructor + ConcaveShape(CollisionShapeType type); + /// DELETE copy-ructor + ConcaveShape( ConcaveShape shape) = delete; + /// DELETE assignment operator + ConcaveShape operator=( ConcaveShape shape) = delete; + protected : + boolean this.isSmoothMeshCollisionEnabled; //!< True if the smooth mesh collision algorithm is enabled + float this.triangleMargin; //!< Margin use for collision detection for each triangle + TriangleRaycastSide this.raycastTestType; //!< Raycast test type for the triangle (front, back, front-back) + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + public: + /// Return the triangle margin + float getTriangleMargin() ; + /// Return the raycast test type (front, back, front-back) + TriangleRaycastSide getRaycastTestType() ; + /** + * @brief Set the raycast test type (front, back, front-back) + * @param testType Raycast test type for the triangle (front, back, front-back) + */ + void setRaycastTestType(TriangleRaycastSide testType); + /// Return true if the collision shape is convex, false if it is concave + virtual boolean isConvex() override; + /// Use a callback method on all triangles of the concave shape inside a given AABB + virtual void testAllTriangles(TriangleCallback callback, AABB localAABB) =0; + /// Return true if the smooth mesh collision is enabled + boolean getIsSmoothMeshCollisionEnabled() ; + /** + * @brief Enable/disable the smooth mesh collision algorithm + * + * Smooth mesh collision is used to avoid collisions against some internal edges of the triangle mesh. + * If it is enabled, collsions with the mesh will be smoother but collisions computation is a bit more expensive. + */ + void setIsSmoothMeshCollisionEnabled(boolean isEnabled); + }; + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.cpp new file mode 100644 index 0000000..1503131 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.cpp @@ -0,0 +1,202 @@ +/** @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) + */ +#include +#include +#include + +using namespace ephysics; + +ConeShape::ConeShape(float radius, float height, float margin): + ConvexShape(CONE, margin), + this.radius(radius), + this.halfHeight(height * 0.5f) { + assert(this.radius > 0.0f); + assert(this.halfHeight > 0.0f); + // Compute the sine of the semi-angle at the apex point + this.sinTheta = this.radius / (sqrt(this.radius * this.radius + height * height)); +} + +vec3 ConeShape::getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) { + vec3 v = direction; + float sinThetaTimesLengthV = this.sinTheta * v.length(); + vec3 supportPoint; + if (v.y() > sinThetaTimesLengthV) { + supportPoint = vec3(0.0, this.halfHeight, 0.0); + } else { + float projectedLength = sqrt(v.x() * v.x() + v.z() * v.z()); + if (projectedLength > FLTEPSILON) { + float d = this.radius / projectedLength; + supportPoint = vec3(v.x() * d, -this.halfHeight, v.z() * d); + } else { + supportPoint = vec3(0.0, -this.halfHeight, 0.0); + } + } + return supportPoint; +} + +boolean ConeShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + vec3 r = ray.point2 - ray.point1; + float epsilon = float(0.00001); + vec3 V(0, this.halfHeight, 0); + vec3 centerBase(0, -this.halfHeight, 0); + vec3 axis(0, float(-1.0), 0); + float heightSquare = float(4.0) * this.halfHeight * this.halfHeight; + float cosThetaSquare = heightSquare / (heightSquare + this.radius * this.radius); + float factor = 1.0f - cosThetaSquare; + vec3 delta = ray.point1 - V; + float c0 = -cosThetaSquare * delta.x() * delta.x() + factor * delta.y() * delta.y() - cosThetaSquare * delta.z() * delta.z(); + float c1 = -cosThetaSquare * delta.x() * r.x() + factor * delta.y() * r.y() - cosThetaSquare * delta.z() * r.z(); + float c2 = -cosThetaSquare * r.x() * r.x() + factor * r.y() * r.y() - cosThetaSquare * r.z() * r.z(); + float tHit[] = {float(-1.0), float(-1.0), float(-1.0)}; + vec3 localHitPoint[3]; + vec3 localNormal[3]; + // If c2 is different from zero + if (etk::abs(c2) > FLTEPSILON) { + 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 intersections + float sqrRoot = etk::sqrt(gamma); + tHit[0] = (-c1 - sqrRoot) / c2; + tHit[1] = (-c1 + sqrRoot) / c2; + } else { // If the equation has a single real root + // Compute the intersection + tHit[0] = -c1 / c2; + } + } else { + // If c2 == 0 + if (etk::abs(c1) > FLTEPSILON) { + // If c2 = 0 and c1 != 0 + tHit[0] = -c0 / (float(2.0) * 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] = ray.point1 + tHit[0] * r; + localHitPoint[1] = ray.point1 + tHit[1] * r; + // Only keep hit points in one side of the double cone (the cone we are interested in) + if (axis.dot(localHitPoint[0] - V) < 0.0f) { + tHit[0] = float(-1.0); + } + if (axis.dot(localHitPoint[1] - V) < 0.0f) { + tHit[1] = float(-1.0); + } + // Only keep hit points that are within the correct height of the cone + if (localHitPoint[0].y() < float(-this.halfHeight)) { + tHit[0] = float(-1.0); + } + if (localHitPoint[1].y() < float(-this.halfHeight)) { + tHit[1] = float(-1.0); + } + // If the ray is in direction of the base plane of the cone + if (r.y() > epsilon) { + // Compute the intersection with the base plane of the cone + tHit[2] = (-ray.point1.y() - this.halfHeight) / (r.y()); + // Only keep this intersection if it is inside the cone radius + localHitPoint[2] = ray.point1 + tHit[2] * r; + if ((localHitPoint[2] - centerBase).length2() > this.radius * this.radius) { + tHit[2] = float(-1.0); + } + // Compute the normal direction + localNormal[2] = axis; + } + // Find the smallest positive t value + int hitIndex = -1; + float t = FLTMAX; + 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) { + float h = float(2.0) * this.halfHeight; + float value1 = (localHitPoint[hitIndex].x() * localHitPoint[hitIndex].x() + localHitPoint[hitIndex].z() * localHitPoint[hitIndex].z()); + float rOverH = this.radius / h; + float value2 = 1.0f + rOverH * rOverH; + float factor = 1.0f / etk::sqrt(value1 * value2); + float x = localHitPoint[hitIndex].x() * factor; + float z = localHitPoint[hitIndex].z() * factor; + localNormal[hitIndex].setX(x); + localNormal[hitIndex].setY(etk::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; +} + +float ConeShape::getRadius() { + return this.radius; +} + +float ConeShape::getHeight() { + return float(2.0) * this.halfHeight; +} + +void ConeShape::setLocalScaling( vec3 scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y()) * scaling.y(); + this.radius = (this.radius / this.scaling.x()) * scaling.x(); + CollisionShape::setLocalScaling(scaling); +} + +sizet ConeShape::getSizeInBytes() { + return sizeof(ConeShape); +} + +void ConeShape::getLocalBounds(vec3 min, vec3 max) { + // Maximum bounds + max.setX(this.radius + this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(max.x()); + // Minimum bounds + min.setX(-max.x()); + min.setY(-max.y()); + min.setZ(min.x()); +} + +void ConeShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + float rSquare = this.radius * this.radius; + float diagXZ = float(0.15) * mass * (rSquare + this.halfHeight); + tensor.setValue(diagXZ, 0.0, 0.0, + 0.0, float(0.3) * mass * rSquare, + 0.0, 0.0, 0.0, diagXZ); +} + +boolean ConeShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + float radiusHeight = this.radius + * (-localPoint.y() + this.halfHeight) + / (this.halfHeight * float(2.0)); + return ( localPoint.y() < this.halfHeight + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() > -this.halfHeight) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (localPoint.x() * localPoint.x() + localPoint.z() * localPoint.z() < radiusHeight *radiusHeight); +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.hpp new file mode 100644 index 0000000..2003894 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConeShape.hpp @@ -0,0 +1,67 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + /** + * @brief 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. + */ + class ConeShape : public ConvexShape { + public : + /** + * @brief 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 + */ + ConeShape(float radius, float height, float margin = OBJECTMARGIN); + /// DELETE copy-ructor + ConeShape( ConeShape shape) = delete; + /// DELETE assignment operator + ConeShape operator=( ConeShape shape) = delete; + protected : + float this.radius; //!< Radius of the base + float this.halfHeight; //!< Half height of the cone + float this.sinTheta; //!< sine of the semi angle at the apex point + virtual vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; + public: + /** + * @brief Return the radius + * @return Radius of the cone (in meters) + */ + float getRadius() ; + /** + * @brief Return the height + * @return Height of the cone (in meters) + */ + float getHeight() ; + + void setLocalScaling( vec3 scaling) override; + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + }; +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.cpp new file mode 100644 index 0000000..3166510 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.cpp @@ -0,0 +1,248 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +ConvexMeshShape::ConvexMeshShape( float* arrayVertices, + int nbVertices, + int stride, + float margin): + ConvexShape(CONVEXMESH, margin), + this.numberVertices(nbVertices), + this.minBounds(0, 0, 0), + this.maxBounds(0, 0, 0), + this.isEdgesInformationUsed(false) { + assert(nbVertices > 0); + assert(stride > 0); + unsigned char* vertexPointer = ( unsigned char*) arrayVertices; + // Copy all the vertices into the internal array + for (int iii=0; iiigetVertices()) { + this.vertices.pushBack(it*this.scaling); + } + // If we need to use the edges information of the mesh + if (this.isEdgesInformationUsed) { + // For each triangle of the mesh + for (sizet iii=0; iiigetNbTriangles(); iii++) { + int vertexIndex[3] = {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(); +} + +ConvexMeshShape::ConvexMeshShape(float margin): + ConvexShape(CONVEXMESH, margin), + this.numberVertices(0), + this.minBounds(0, 0, 0), + this.maxBounds(0, 0, 0), + this.isEdgesInformationUsed(false) { + +} + +vec3 ConvexMeshShape::getLocalSupportPointWithoutMargin( vec3 direction, + void** cachedCollisionData) { + assert(this.numberVertices == this.vertices.size()); + assert(cachedCollisionData != null); + // Allocate memory for the cached collision data if not allocated yet + if ((*cachedCollisionData) == null) { + *cachedCollisionData = (int*) malloc(sizeof(int)); + *((int*)(*cachedCollisionData)) = 0; + } + // If the edges information is used to speed up the collision detection + if (this.isEdgesInformationUsed) { + assert(this.edgesAdjacencyList.size() == this.numberVertices); + int maxVertex = *((int*)(*cachedCollisionData)); + float maxDotProduct = direction.dot(this.vertices[maxVertex]); + boolean isOptimal; + // Perform hill-climbing (local search) + do { + isOptimal = true; + assert(this.edgesAdjacencyList[maxVertex].size() > 0); + // For all neighbors of the current vertex + etk::Set::Iterator it; + etk::Set::Iterator itBegin = this.edgesAdjacencyList[maxVertex].begin(); + etk::Set::Iterator itEnd = this.edgesAdjacencyList[maxVertex].end(); + for (it = itBegin; it != itEnd; ++it) { + // Compute the dot product + float dotProduct = direction.dot(this.vertices[*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 + *((int*)(*cachedCollisionData)) = maxVertex; + // Return the support vertex + return this.vertices[maxVertex] * this.scaling; + } else { + // If the edges information is not used + double maxDotProduct = FLTMIN; + int indexMaxDotProduct = 0; + // For each vertex of the mesh + for (int i=0; i maxDotProduct) { + indexMaxDotProduct = i; + maxDotProduct = dotProduct; + } + } + assert(maxDotProduct >= 0.0f); + // Return the vertex with the largest dot product in the support direction + return this.vertices[indexMaxDotProduct] * this.scaling; + } +} + +// Recompute the bounds of the mesh +void ConvexMeshShape::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.maxBounds.x()) { + this.maxBounds.setX(this.vertices[i].x()); + } + if (this.vertices[i].x() < this.minBounds.x()) { + this.minBounds.setX(this.vertices[i].x()); + } + if (this.vertices[i].y() > this.maxBounds.y()) { + this.maxBounds.setY(this.vertices[i].y()); + } + if (this.vertices[i].y() < this.minBounds.y()) { + this.minBounds.setY(this.vertices[i].y()); + } + if (this.vertices[i].z() > this.maxBounds.z()) { + this.maxBounds.setZ(this.vertices[i].z()); + } + if (this.vertices[i].z() < this.minBounds.z()) { + this.minBounds.setZ(this.vertices[i].z()); + } + } + // Apply the local scaling factor + this.maxBounds = this.maxBounds * this.scaling; + this.minBounds = this.minBounds * this.scaling; + // Add the object margin to the bounds + this.maxBounds += vec3(this.margin, this.margin, this.margin); + this.minBounds -= vec3(this.margin, this.margin, this.margin); +} + +boolean ConvexMeshShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + return proxyShape->this.body->this.world.this.collisionDetection.this.narrowPhaseGJKAlgorithm.raycast(ray, proxyShape, raycastInfo); +} + +void ConvexMeshShape::setLocalScaling( vec3 scaling) { + ConvexShape::setLocalScaling(scaling); + recalculateBounds(); +} + +sizet ConvexMeshShape::getSizeInBytes() { + return sizeof(ConvexMeshShape); +} + +void ConvexMeshShape::getLocalBounds(vec3 min, vec3 max) { + min = this.minBounds; + max = this.maxBounds; +} + +void ConvexMeshShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + float factor = (1.0f / float(3.0)) * mass; + vec3 realExtent = 0.5f * (this.maxBounds - this.minBounds); + assert(realExtent.x() > 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj realExtent.y() > 0 hjkhjkhjkhkj realExtent.z() > 0); + float xSquare = realExtent.x() * realExtent.x(); + float ySquare = realExtent.y() * realExtent.y(); + float zSquare = realExtent.z() * realExtent.z(); + tensor.setValue(factor * (ySquare + zSquare), 0.0, 0.0, + 0.0, factor * (xSquare + zSquare), 0.0, + 0.0, 0.0, factor * (xSquare + ySquare)); +} + +void ConvexMeshShape::addVertex( vec3 vertex) { + // Add the vertex in to vertices array + this.vertices.pushBack(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()); + } +} + +void ConvexMeshShape::addEdge(int v1, int v2) { + // If the entry for vertex v1 does not exist in the adjacency list + if (this.edgesAdjacencyList.count(v1) == 0) { + this.edgesAdjacencyList.add(v1, etk::Set()); + } + // If the entry for vertex v2 does not exist in the adjacency list + if (this.edgesAdjacencyList.count(v2) == 0) { + this.edgesAdjacencyList.add(v2, etk::Set()); + } + // Add the edge in the adjacency list + this.edgesAdjacencyList[v1].add(v2); + this.edgesAdjacencyList[v2].add(v1); +} + +boolean ConvexMeshShape::isEdgesInformationUsed() { + return this.isEdgesInformationUsed; +} + +void ConvexMeshShape::setIsEdgesInformationUsed(boolean isEdgesUsed) { + this.isEdgesInformationUsed = isEdgesUsed; +} + +boolean ConvexMeshShape::testPointInside( vec3 localPoint, + ProxyShape* proxyShape) { + // Use the GJK algorithm to test if the point is inside the convex mesh + return proxyShape->this.body->this.world.this.collisionDetection.this.narrowPhaseGJKAlgorithm.testPointInside(localPoint, proxyShape); +} \ No newline at end of file diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.hpp new file mode 100644 index 0000000..b9cbf92 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConvexMeshShape.hpp @@ -0,0 +1,114 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + class CollisionWorld; + /** + * @brief 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. + */ + class ConvexMeshShape : public ConvexShape { + protected : + etk::Vector this.vertices; //!< Array with the vertices of the mesh + int this.numberVertices; //!< Number of vertices in the mesh + vec3 this.minBounds; //!< Mesh minimum bounds in the three local x, y and z directions + vec3 this.maxBounds; //!< Mesh maximum bounds in the three local x, y and z directions + boolean this.isEdgesInformationUsed; //!< True if the shape contains the edges of the convex mesh in order to make the collision detection faster + etk::Map > this.edgesAdjacencyList; //!< Adjacency list representing the edges of the mesh + /// Private copy-ructor + ConvexMeshShape( ConvexMeshShape shape); + /// Private assignment operator + ConvexMeshShape operator=( ConvexMeshShape shape); + /// Recompute the bounds of the mesh + void recalculateBounds(); + void setLocalScaling( vec3 scaling) override; + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; + public : + /** + * @brief Constructor to initialize with an array of 3D vertices. + * This method creates an internal copy of the input vertices. + * @param[in] arrayVertices Array with the vertices of the convex mesh + * @param[in] nbVertices Number of vertices in the convex mesh + * @param[in] stride Stride between the beginning of two elements in the vertices array + * @param[in] margin Collision margin (in meters) around the collision shape + */ + ConvexMeshShape( float* arrayVertices, + int nbVertices, + int stride, + float margin = OBJECTMARGIN); + /** + * @brief 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 + */ + ConvexMeshShape(TriangleVertexArray* triangleVertexArray, + boolean isEdgesInformationUsed = true, + float margin = OBJECTMARGIN); + /** + * @brief Constructor. + * If you use this ructor, you will need to set the vertices manually one by one using the addVertex() method. + */ + ConvexMeshShape(float margin = OBJECTMARGIN); + public: + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + /** + * @brief Add a vertex into the convex mesh + * @param vertex Vertex to be added + */ + void addVertex( vec3 vertex); + /** + * @brief Add an edge into 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 into the convex mesh. + * @param[in] v1 Index of the first vertex of the edge to add + * @param[in] v2 Index of the second vertex of the edge to add + */ + void addEdge(int v1, int v2); + /** + * @brief 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 + */ + boolean isEdgesInformationUsed() ; + /** + * @brief Set the variable to know if the edges information is used to speed up the + * collision detection + * @param[in] isEdgesUsed True if you want to use the edges information to speed up the collision detection with the convex mesh shape + */ + void setIsEdgesInformationUsed(boolean isEdgesUsed); + }; +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.cpp new file mode 100644 index 0000000..873a0d4 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.cpp @@ -0,0 +1,33 @@ +/** @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) + */ +#include + +ephysics::ConvexShape::ConvexShape(ephysics::CollisionShapeType type, float margin): + CollisionShape(type), + this.margin(margin) { + +} + +ephysics::ConvexShape::~ConvexShape() { + +} + +vec3 ephysics::ConvexShape::getLocalSupportPointWithMargin( vec3 direction, void** cachedCollisionData) { + // Get the support point without margin + vec3 supportPoint = getLocalSupportPointWithoutMargin(direction, cachedCollisionData); + if (this.margin != 0.0f) { + // Add the margin to the support point + vec3 unitVec(0.0, -1.0, 0.0); + if (direction.length2() > FLTEPSILON * FLTEPSILON) { + unitVec = direction.safeNormalized(); + } + supportPoint += unitVec * this.margin; + } + return supportPoint; +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.hpp new file mode 100644 index 0000000..408134c --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/ConvexShape.hpp @@ -0,0 +1,51 @@ +/** @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) + */ +#pragma once + +#include +namespace ephysics { +/** + * @brief It represents a convex collision shape associated with a + * body that is used during the narrow-phase collision detection. + */ +class ConvexShape: public CollisionShape { + protected: + float this.margin; //!< Margin used for the GJK collision detection algorithm + /// Private copy-ructor + ConvexShape( ConvexShape shape) = delete; + /// Private assignment operator + ConvexShape operator=( ConvexShape shape) = delete; + // Return a local support point in a given direction with the object margin + virtual vec3 getLocalSupportPointWithMargin( vec3 direction, void** cachedCollisionData) ; + /// Return a local support point in a given direction without the object margin + virtual vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) =0; + boolean testPointInside( vec3 worldPoint, ProxyShape* proxyShape) override = 0; + public: + /// Constructor + ConvexShape(CollisionShapeType type, float margin); + /// Destructor + virtual ~ConvexShape(); + public: + /** + * @brief Get the current object margin + * @return The margin (in meters) around the collision shape + */ + float getMargin() { + return this.margin; + } + virtual boolean isConvex() override { + return true; + } + friend class GJKAlgorithm; + friend class EPAAlgorithm; +}; + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.cpp new file mode 100644 index 0000000..e91af83 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.cpp @@ -0,0 +1,238 @@ +/** @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) + */ +#include +#include +#include + +using namespace ephysics; + +CylinderShape::CylinderShape(float radius, + float height, + float margin): + ConvexShape(CYLINDER, margin), this.radius(radius), this.halfHeight(height/float(2.0)) { + assert(radius > 0.0f); + assert(height > 0.0f); +} + +vec3 CylinderShape::getLocalSupportPointWithoutMargin( vec3 direction, + void** cachedCollisionData) { + vec3 supportPoint(0.0, 0.0, 0.0); + float uDotv = direction.y(); + vec3 w(direction.x(), 0.0, direction.z()); + float lengthW = sqrt(direction.x() * direction.x() + direction.z() * direction.z()); + if (lengthW > FLTEPSILON) { + if (uDotv < 0.0) { + supportPoint.setY(-this.halfHeight); + } else { + supportPoint.setY(this.halfHeight); + } + supportPoint += (this.radius / lengthW) * w; + } else { + if (uDotv < 0.0) { + supportPoint.setY(-this.halfHeight); + } else { + supportPoint.setY(this.halfHeight); + } + } + return supportPoint; +} + +boolean CylinderShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + vec3 n = ray.point2 - ray.point1; + float epsilon = float(0.01); + vec3 p(float(0), -this.halfHeight, float(0)); + vec3 q(float(0), this.halfHeight, float(0)); + vec3 d = q - p; + vec3 m = ray.point1 - p; + float t; + float mDotD = m.dot(d); + float nDotD = n.dot(d); + float dDotD = d.dot(d); + // Test if the segment is outside the cylinder + if (mDotD < 0.0f hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj mDotD + nDotD < float(0.0)) { + return false; + } + if (mDotD > dDotD hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj mDotD + nDotD > dDotD) { + return false; + } + float nDotN = n.dot(n); + float mDotN = m.dot(n); + float a = dDotD * nDotN - nDotD * nDotD; + float k = m.dot(m) - this.radius * this.radius; + float c = dDotD * k - mDotD * mDotD; + // If the ray is parallel to the cylinder axis + if (etk::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 intersect an endcap of the cylinder + // If the ray intersects with the "p" endcap of the cylinder + if (mDotD < 0.0f) { + t = -mDotN / nDotN; + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 normalDirection(0, float(-1), 0); + raycastInfo.worldNormal = normalDirection; + return true; + } + // If the ray intersects with the "q" endcap of the cylinder + if (mDotD > dDotD) { + t = (nDotD - mDotN) / nDotN; + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 normalDirection(0, 1.0f, 0); + raycastInfo.worldNormal = normalDirection; + return true; + } + // If the origin is inside the cylinder, we return no hit + return false; + } + float b = dDotD * mDotN - nDotD * mDotD; + 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 intersection along the ray) + float t0 = t = (-b - etk::sqrt(discriminant)) / a; + // If the intersection is outside the cylinder on "p" endcap side + 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 intersection against the "p" endcap (intersection agains whole plane) + t = -mDotD / nDotD; + // Keep the intersection if the it is inside the cylinder radius + if (k + t * (float(2.0) * mDotN + t) > 0.0f) { + return false; + } + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 normalDirection(0, float(-1.0), 0); + raycastInfo.worldNormal = normalDirection; + return true; + } + // If the intersection 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 intersection against the "q" endcap (intersection against whole plane) + t = (dDotD - mDotD) / nDotD; + // Keep the intersection if it is inside the cylinder radius + if (k + dDotD - float(2.0) * mDotD + t * (float(2.0) * (mDotN - nDotD) + t) > 0.0f) { + return false; + } + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 normalDirection(0, 1.0f, 0); + raycastInfo.worldNormal = normalDirection; + return true; + } + t = t0; + // If the intersection 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 + vec3 localHitPoint = ray.point1 + t * n; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = localHitPoint; + vec3 v = localHitPoint - p; + vec3 w = (v.dot(d) / d.length2()) * d; + vec3 normalDirection = (localHitPoint - (p + w)); + raycastInfo.worldNormal = normalDirection; + return true; +} + +float CylinderShape::getRadius() { + return this.radius; +} + +float CylinderShape::getHeight() { + return this.halfHeight + this.halfHeight; +} + +void CylinderShape::setLocalScaling( vec3 scaling) { + this.halfHeight = (this.halfHeight / this.scaling.y()) * scaling.y(); + this.radius = (this.radius / this.scaling.x()) * scaling.x(); + CollisionShape::setLocalScaling(scaling); +} + +sizet CylinderShape::getSizeInBytes() { + return sizeof(CylinderShape); +} + +void CylinderShape::getLocalBounds(vec3 min, vec3 max) { + // Maximum bounds + max.setX(this.radius + this.margin); + max.setY(this.halfHeight + this.margin); + max.setZ(max.x()); + // Minimum bounds + min.setX(-max.x()); + min.setY(-max.y()); + min.setZ(min.x()); +} + +void CylinderShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + float height = float(2.0) * this.halfHeight; + float diag = (1.0f / float(12.0)) * mass * (3 * this.radius * this.radius + height * height); + tensor.setValue(diag, 0.0, 0.0, 0.0, + 0.5f * mass * this.radius * this.radius, 0.0, + 0.0, 0.0, diag); +} + +boolean CylinderShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + return ( (localPoint.x() * localPoint.x() + localPoint.z() * localPoint.z()) < this.radius * this.radius + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() < this.halfHeight + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj localPoint.y() > -this.halfHeight); +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.hpp new file mode 100644 index 0000000..24a6f56 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/CylinderShape.hpp @@ -0,0 +1,66 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + /** + * @brief 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. + */ + class CylinderShape: public ConvexShape { + protected: + float this.radius; //!< Radius of the base + float this.halfHeight; //!< Half height of the cylinder + /// DELETED copy-ructor + CylinderShape( CylinderShape) = delete; + /// DELETED assignment operator + CylinderShape operator=( CylinderShape) = delete; + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; + public: + /** + * @brief 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 + */ + CylinderShape(float radius, float height, float margin = OBJECTMARGIN); + /** + * @breif Get the Shape radius + * @return Radius of the cylinder (in meters) + */ + float getRadius() ; + /** + * @breif Get the Shape height + * @return Height of the cylinder (in meters) + */ + float getHeight() ; + void setLocalScaling( vec3 scaling) override; + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + }; + +} + + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.cpp new file mode 100644 index 0000000..fe24bb5 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.cpp @@ -0,0 +1,246 @@ +/** @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) + */ +#include + +// TODO: REMOVE this... +using namespace ephysics; + +HeightFieldShape::HeightFieldShape(int nbGridColumns, + int nbGridRows, + float minHeight, + float maxHeight, + void* heightFieldData, + HeightDataType dataType, + int upAxis, + float integerHeightScale): + ConcaveShape(HEIGHTFIELD), + this.numberColumns(nbGridColumns), + this.numberRows(nbGridRows), + this.width(nbGridColumns - 1), + this.length(nbGridRows - 1), + this.minHeight(minHeight), + this.maxHeight(maxHeight), + this.upAxis(upAxis), + this.integerHeightScale(integerHeightScale), + this.heightDataType(dataType) { + 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; + 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(vec3(-halfHeight, -this.width * 0.5f, -this.length * float(0.5))); + this.AABB.setMax(vec3(halfHeight, this.width * 0.5f, this.length* float(0.5))); + } else if (this.upAxis == 1) { + this.AABB.setMin(vec3(-this.width * 0.5f, -halfHeight, -this.length * float(0.5))); + this.AABB.setMax(vec3(this.width * 0.5f, halfHeight, this.length * float(0.5))); + } else if (this.upAxis == 2) { + this.AABB.setMin(vec3(-this.width * 0.5f, -this.length * float(0.5), -halfHeight)); + this.AABB.setMax(vec3(this.width * 0.5f, this.length * float(0.5), halfHeight)); + } +} + +void HeightFieldShape::getLocalBounds(vec3 min, vec3 max) { + min = this.AABB.getMin() * this.scaling; + max = this.AABB.getMax() * this.scaling; +} + +void HeightFieldShape::testAllTriangles(TriangleCallback callback, AABB localAABB) { + // Compute the non-scaled AABB + vec3 inverseScaling(1.0f / this.scaling.x(), 1.0f / this.scaling.y(), float(1.0) / this.scaling.z()); + AABB aabb(localAABB.getMin() * inverseScaling, localAABB.getMax() * inverseScaling); + // Compute the integer grid coordinates inside the area we need to test for collision + int minGridCoords[3]; + int maxGridCoords[3]; + 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 = clamp(minGridCoords[1], 0, this.numberColumns - 1); + iMax = clamp(maxGridCoords[1], 0, this.numberColumns - 1); + jMin = clamp(minGridCoords[2], 0, this.numberRows - 1); + jMax = clamp(maxGridCoords[2], 0, this.numberRows - 1); + break; + case 1 : + iMin = clamp(minGridCoords[0], 0, this.numberColumns - 1); + iMax = clamp(maxGridCoords[0], 0, this.numberColumns - 1); + jMin = clamp(minGridCoords[2], 0, this.numberRows - 1); + jMax = clamp(maxGridCoords[2], 0, this.numberRows - 1); + break; + case 2 : + iMin = clamp(minGridCoords[0], 0, this.numberColumns - 1); + iMax = clamp(maxGridCoords[0], 0, this.numberColumns - 1); + jMin = clamp(minGridCoords[1], 0, this.numberRows - 1); + jMax = clamp(maxGridCoords[1], 0, this.numberRows - 1); + break; + } + assert(iMin >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj iMin < this.numberColumns); + assert(iMax >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj iMax < this.numberColumns); + assert(jMin >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj jMin < this.numberRows); + assert(jMax >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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 + vec3 p1 = getVertexAt(i, j); + vec3 p2 = getVertexAt(i, j + 1); + vec3 p3 = getVertexAt(i + 1, j); + vec3 p4 = getVertexAt(i + 1, j + 1); + // Generate the first triangle for the current grid rectangle + vec3 trianglePoints[3] = {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); + } + } +} + +void HeightFieldShape::computeMinMaxGridCoordinates(int* minCoords, int* maxCoords, AABB aabbToCollide) { + // Clamp the min/max coords of the AABB to collide inside the height field AABB + vec3 minPoint = etk::max(aabbToCollide.getMin(), this.AABB.getMin()); + minPoint = etk::min(minPoint, this.AABB.getMax()); + vec3 maxPoint = etk::min(aabbToCollide.getMax(), this.AABB.getMax()); + maxPoint = etk::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] + vec3 translateVec = this.AABB.getExtent() * 0.5f; + minPoint += translateVec; + maxPoint += translateVec; + // Convert the floating min/max coords of the AABB into closest integer + // grid values (note that we use the closest grid coordinate that is out + // of the AABB) + minCoords[0] = computeIntegerGridValue(minPoint.x()) - 1; + minCoords[1] = computeIntegerGridValue(minPoint.y()) - 1; + minCoords[2] = computeIntegerGridValue(minPoint.z()) - 1; + maxCoords[0] = computeIntegerGridValue(maxPoint.x()) + 1; + maxCoords[1] = computeIntegerGridValue(maxPoint.y()) + 1; + maxCoords[2] = computeIntegerGridValue(maxPoint.z()) + 1; +} + +boolean HeightFieldShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + // TODO : Implement raycasting without using an AABB for the ray + // but using a dynamic AABB tree or octree instead + PROFILE("HeightFieldShape::raycast()"); + TriangleOverlapCallback triangleCallback(ray, proxyShape, raycastInfo, *this); + // Compute the AABB for the ray + vec3 rayEnd = ray.point1 + ray.maxFraction * (ray.point2 - ray.point1); + AABB rayAABB(etk::min(ray.point1, rayEnd), etk::max(ray.point1, rayEnd)); + testAllTriangles(triangleCallback, rayAABB); + return triangleCallback.getIsHit(); +} + +vec3 HeightFieldShape::getVertexAt(int xxx, int yyy) { + // Get the height value + float height = getHeightAt(xxx, yyy); + // Height values origin + float heightOrigin = -(this.maxHeight - this.minHeight) * 0.5f - this.minHeight; + vec3 vertex; + switch (this.upAxis) { + case 0: + vertex = vec3(heightOrigin + height, -this.width * 0.5f + xxx, -this.length * float(0.5) + yyy); + break; + case 1: + vertex = vec3(-this.width * 0.5f + xxx, heightOrigin + height, -this.length * float(0.5) + yyy); + break; + case 2: + vertex = vec3(-this.width * 0.5f + xxx, -this.length * float(0.5) + yyy, heightOrigin + height); + break; + default: + assert(false); + } + assert(this.AABB.contains(vertex)); + return vertex * this.scaling; +} + +void TriangleOverlapCallback::testTriangle( vec3* trianglePoints) { + // Create a triangle collision shape + float margin = this.heightFieldShape.getTriangleMargin(); + TriangleShape triangleShape(trianglePoints[0], trianglePoints[1], trianglePoints[2], margin); + triangleShape.setRaycastTestType(this.heightFieldShape.getRaycastTestType()); + // Ray casting test against the collision shape + RaycastInfo raycastInfo; + boolean isTriangleHit = triangleShape.raycast(this.ray, raycastInfo, this.proxyShape); + // If the ray hit the collision shape + if ( isTriangleHit + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj 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; + } +} + +int HeightFieldShape::getNbRows() { + return this.numberRows; +} + +int HeightFieldShape::getNbColumns() { + return this.numberColumns; +} + +HeightFieldShape::HeightDataType HeightFieldShape::getHeightDataType() { + return this.heightDataType; +} + +sizet HeightFieldShape::getSizeInBytes() { + return sizeof(HeightFieldShape); +} + +void HeightFieldShape::setLocalScaling( vec3 scaling) { + CollisionShape::setLocalScaling(scaling); +} + +float HeightFieldShape::getHeightAt(int xxx, int yyy) { + switch(this.heightDataType) { + case HEIGHTFLOATTYPE: + return ((float*)this.heightFieldData)[yyy * this.numberColumns + xxx]; + case HEIGHTDOUBLETYPE: + return ((double*)this.heightFieldData)[yyy * this.numberColumns + xxx]; + case HEIGHTINTTYPE: + return ((int*)this.heightFieldData)[yyy * this.numberColumns + xxx] * this.integerHeightScale; + default: + assert(false); + return 0; + } +} + +int HeightFieldShape::computeIntegerGridValue(float value) { + return (value < 0.0f) ? value - 0.5f : value + 0.5f; +} + +void HeightFieldShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, 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.setValue(mass, 0, 0, + 0, mass, 0, + 0, 0, mass); +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.hpp new file mode 100644 index 0000000..2ce2e77 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/HeightFieldShape.hpp @@ -0,0 +1,135 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + class HeightFieldShape; + /** + * @brief This class is used for testing AABB and triangle overlap for raycasting + */ + class TriangleOverlapCallback : public TriangleCallback { + protected: + Ray this.ray; + ProxyShape* this.proxyShape; + RaycastInfo this.raycastInfo; + boolean this.isHit; + float this.smallestHitFraction; + HeightFieldShape this.heightFieldShape; + public: + TriangleOverlapCallback( Ray ray, + ProxyShape* proxyShape, + RaycastInfo raycastInfo, + HeightFieldShape heightFieldShape): + this.ray(ray), + this.proxyShape(proxyShape), + this.raycastInfo(raycastInfo), + this.heightFieldShape(heightFieldShape) { + this.isHit = false; + this.smallestHitFraction = this.ray.maxFraction; + } + boolean getIsHit() { + return this.isHit; + } + /// Raycast test between a ray and a triangle of the heightfield + virtual void testTriangle( vec3* trianglePoints); + }; + + + /** + * @brief 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 into the shape + * but are shared instead. The height values can be of type integer, 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. + */ + class HeightFieldShape : public ConcaveShape { + public: + /** + * @brief Data type for the height data of the height field + */ + enum HeightDataType { + HEIGHTFLOATTYPE, + HEIGHTDOUBLETYPE, + HEIGHTINTTYPE + }; + protected: + int this.numberColumns; //!< Number of columns in the grid of the height field + int this.numberRows; //!< Number of rows in the grid of the height field + float this.width; //!< Height field width + float this.length; //!< Height field length + float this.minHeight; //!< Minimum height of the height field + float this.maxHeight; //!< Maximum height of the height field + int this.upAxis; //!< Up axis direction (0 => x, 1 => y, 2 => z) + float this.integerHeightScale; //!< Height values scale for height field with integer height values + HeightDataType this.heightDataType; //!< Data type of the height values + void* this.heightFieldData; //!< Array of data with all the height values of the height field + AABB this.AABB; //!< Local AABB of the height field (without scaling) + /// DELETED copy-ructor + HeightFieldShape( HeightFieldShape) = delete; + /// DELETED assignment operator + HeightFieldShape operator=( HeightFieldShape) = delete; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; + /// Insert all the triangles into the dynamic AABB tree + void initBVHTree(); + /// Return the three vertices coordinates (in the array outTriangleVertices) of a triangle + /// given the start vertex index pointer of the triangle. + void getTriangleVerticesWithIndexPointer(int subPart, + int triangleIndex, + vec3* outTriangleVertices) ; + /// Return the vertex (local-coordinates) of the height field at a given (x,y) position + vec3 getVertexAt(int x, int y) ; + /// Return the height of a given (x,y) point in the height field + float getHeightAt(int x, int y) ; + /// Return the closest inside integer grid value of a given floating grid value + int computeIntegerGridValue(float value) ; + /// Compute the min/max grid coords corresponding to the intersection of the AABB of the height field and the AABB to collide + void computeMinMaxGridCoordinates(int* minCoords, int* maxCoords, AABB aabbToCollide) ; + public: + /** + * @brief 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 (int, float, double) + * @param upAxis Integer representing the up axis direction (0 for x, 1 for y and 2 for z) + * @param integerHeightScale Scaling factor used to scale the height values (only when height values type is integer) + */ + HeightFieldShape(int nbGridColumns, + int nbGridRows, + float minHeight, + float maxHeight, + void* heightFieldData, + HeightDataType dataType, + int upAxis = 1, float integerHeightScale = 1.0f); + /// Return the number of rows in the height field + int getNbRows() ; + /// Return the number of columns in the height field + int getNbColumns() ; + /// Return the type of height value in the height field + HeightDataType getHeightDataType() ; + void getLocalBounds(vec3 min, vec3 max) override; + void setLocalScaling( vec3 scaling) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + virtual void testAllTriangles(TriangleCallback callback, AABB localAABB) override; + friend class ConvexTriangleAABBOverlapCallback; + friend class ConcaveMeshRaycastCallback; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.cpp new file mode 100644 index 0000000..cf5b975 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.cpp @@ -0,0 +1,89 @@ +/** @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) + */ +#include +#include +#include + +// TODO: REMOVE this ... +using namespace ephysics; + +SphereShape::SphereShape(float radius): + ConvexShape(SPHERE, radius) { + assert(radius > 0.0f); +} + +void SphereShape::setLocalScaling( vec3 scaling) { + this.margin = (this.margin / this.scaling.x()) * scaling.x(); + CollisionShape::setLocalScaling(scaling); +} + +void SphereShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + float diag = 0.4f * mass * this.margin * this.margin; + tensor.setValue(diag, 0.0f, 0.0f, + 0.0f, diag, 0.0f, + 0.0f, 0.0f, diag); +} + +void SphereShape::computeAABB(AABB aabb, etk::Transform3D transform) { + // Get the local extents in x,y and z direction + vec3 extents(this.margin, this.margin, this.margin); + // Update the AABB with the new minimum and maximum coordinates + aabb.setMin(transform.getPosition() - extents); + aabb.setMax(transform.getPosition() + extents); +} + +void SphereShape::getLocalBounds(vec3 min, vec3 max) { + // Maximum bounds + max.setX(this.margin); + max.setY(this.margin); + max.setZ(this.margin); + // Minimum bounds + min.setX(-this.margin); + min.setY(min.x()); + min.setZ(min.x()); +} + +boolean SphereShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + vec3 m = ray.point1; + float c = m.dot(m) - this.margin * this.margin; + // If the origin of the ray is inside the sphere, we return no intersection + if (c < 0.0f) { + return false; + } + vec3 rayDirection = ray.point2 - ray.point1; + 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 intersection + if (b > 0.0f) { + return false; + } + float raySquareLength = rayDirection.length2(); + // Compute the discriminant of the quadratic equation + float discriminant = b * b - raySquareLength * c; + // If the discriminant is negative or the ray length is very small, there is no intersection + if ( discriminant < 0.0f + || raySquareLength < FLTEPSILON) { + return false; + } + // Compute the solution "t" closest to the origin + float t = -b - etk::sqrt(discriminant); + assert(t >= 0.0f); + // If the hit point is withing the segment ray fraction + if (t < ray.maxFraction * raySquareLength) { + // Compute the intersection information + t /= raySquareLength; + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.hitFraction = t; + raycastInfo.worldPoint = ray.point1 + t * rayDirection; + raycastInfo.worldNormal = raycastInfo.worldPoint; + return true; + } + return false; +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.hpp new file mode 100644 index 0000000..d6d3d7b --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/SphereShape.hpp @@ -0,0 +1,57 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + + /** + * @brief 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. + */ + class SphereShape : public ConvexShape { + protected : + SphereShape( SphereShape shape); + SphereShape operator=( SphereShape shape) = delete; + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override { + return vec3(0.0, 0.0, 0.0); + } + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override { + return (localPoint.length2() < this.margin * this.margin); + } + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override { + return sizeof(SphereShape); + } + public : + /** + * @brief Constructor + * @param[in] radius Radius of the sphere (in meters) + */ + SphereShape(float radius); + /** + * @brief Get the radius of the sphere + * @return Radius of the sphere (in meters) + */ + float getRadius() { + return this.margin; + } + void setLocalScaling( vec3 scaling) override; + void getLocalBounds(vec3 min, vec3 max) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + void computeAABB(AABB aabb, etk::Transform3D transform) override; + }; + +} diff --git a/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.cpp b/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.cpp new file mode 100644 index 0000000..ae734f3 --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.cpp @@ -0,0 +1,163 @@ +/** @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) + */ +#include +#include +#include +#include + +// TODO: REMOVE this... +using namespace ephysics; + +TriangleShape::TriangleShape( vec3 point1, vec3 point2, vec3 point3, float margin) + : ConvexShape(TRIANGLE, margin) { + this.points[0] = point1; + this.points[1] = point2; + this.points[2] = point3; + this.raycastTestType = FRONT; +} + +boolean TriangleShape::raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) { + PROFILE("TriangleShape::raycast()"); + vec3 pq = ray.point2 - ray.point1; + vec3 pa = this.points[0] - ray.point1; + vec3 pb = this.points[1] - ray.point1; + vec3 pc = this.points[2] - ray.point1; + // Test if the line PQ is inside the eges BC, CA and AB. We use the triple + // product for this test. + vec3 m = pq.cross(pc); + float u = pb.dot(m); + if (this.raycastTestType == FRONT) { + if (u < 0.0f) { + return false; + } + } else if (this.raycastTestType == BACK) { + if (u > 0.0f) { + return false; + } + } + float v = -pa.dot(m); + if (this.raycastTestType == FRONT) { + if (v < 0.0f) { + return false; + } + } else if (this.raycastTestType == BACK) { + if (v > 0.0f) { + return false; + } + } else if (this.raycastTestType == FRONTANDBACK) { + if (!sameSign(u, v)) { + return false; + } + } + float w = pa.dot(pq.cross(pb)); + if (this.raycastTestType == FRONT) { + if (w < 0.0f) { + return false; + } + } else if (this.raycastTestType == BACK) { + if (w > 0.0f) { + return false; + } + } else if (this.raycastTestType == FRONTANDBACK) { + if (!sameSign(u, w)) { + return false; + } + } + // If the line PQ is in the triangle plane (case where u=v=w=0) + if ( approxEqual(u, 0) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj approxEqual(v, 0) + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj approxEqual(w, 0)) { + return false; + } + // Compute the barycentric coordinates (u, v, w) to determine the + // intersection point R, R = u * a + v * b + w * c + float denom = 1.0f / (u + v + w); + u *= denom; + v *= denom; + w *= denom; + // Compute the local hit point using the barycentric coordinates + vec3 localHitPoint = u * this.points[0] + v * this.points[1] + w * this.points[2]; + float hitFraction = (localHitPoint - ray.point1).length() / pq.length(); + if ( hitFraction < 0.0f + || hitFraction > ray.maxFraction) { + return false; + } + vec3 localHitNormal = (this.points[1] - this.points[0]).cross(this.points[2] - this.points[0]); + if (localHitNormal.dot(pq) > 0.0f) { + localHitNormal = -localHitNormal; + } + raycastInfo.body = proxyShape->getBody(); + raycastInfo.proxyShape = proxyShape; + raycastInfo.worldPoint = localHitPoint; + raycastInfo.hitFraction = hitFraction; + raycastInfo.worldNormal = localHitNormal; + return true; +} + +sizet TriangleShape::getSizeInBytes() { + return sizeof(TriangleShape); +} + +vec3 TriangleShape::getLocalSupportPointWithoutMargin( vec3 direction, + void** cachedCollisionData) { + vec3 dotProducts(direction.dot(this.points[0]), direction.dot(this.points[1]), direction.dot(this.points[2])); + return this.points[dotProducts.getMaxAxis()]; +} + +void TriangleShape::getLocalBounds(vec3 min, vec3 max) { + vec3 xAxis(this.points[0].x(), this.points[1].x(), this.points[2].x()); + vec3 yAxis(this.points[0].y(), this.points[1].y(), this.points[2].y()); + vec3 zAxis(this.points[0].z(), this.points[1].z(), this.points[2].z()); + min.setValue(xAxis.getMin(), yAxis.getMin(), zAxis.getMin()); + max.setValue(xAxis.getMax(), yAxis.getMax(), zAxis.getMax()); + min -= vec3(this.margin, this.margin, this.margin); + max += vec3(this.margin, this.margin, this.margin); +} + +void TriangleShape::setLocalScaling( vec3 scaling) { + this.points[0] = (this.points[0] / this.scaling) * scaling; + this.points[1] = (this.points[1] / this.scaling) * scaling; + this.points[2] = (this.points[2] / this.scaling) * scaling; + CollisionShape::setLocalScaling(scaling); +} + +void TriangleShape::computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) { + tensor.setZero(); +} + +void TriangleShape::computeAABB(AABB aabb, etk::Transform3D transform) { + vec3 worldPoint1 = transform * this.points[0]; + vec3 worldPoint2 = transform * this.points[1]; + vec3 worldPoint3 = transform * this.points[2]; + vec3 xAxis(worldPoint1.x(), worldPoint2.x(), worldPoint3.x()); + vec3 yAxis(worldPoint1.y(), worldPoint2.y(), worldPoint3.y()); + vec3 zAxis(worldPoint1.z(), worldPoint2.z(), worldPoint3.z()); + aabb.setMin(vec3(xAxis.getMin(), yAxis.getMin(), zAxis.getMin())); + aabb.setMax(vec3(xAxis.getMax(), yAxis.getMax(), zAxis.getMax())); +} + +boolean TriangleShape::testPointInside( vec3 localPoint, ProxyShape* proxyShape) { + return false; +} + +TriangleRaycastSide TriangleShape::getRaycastTestType() { + return this.raycastTestType; +} + + +void TriangleShape::setRaycastTestType(TriangleRaycastSide testType) { + this.raycastTestType = testType; +} + +vec3 TriangleShape::getVertex(int index) { + assert( index >= 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj index < 3); + return this.points[index]; +} + diff --git a/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.hpp b/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.hpp new file mode 100644 index 0000000..867cdde --- /dev/null +++ b/src/org/atriaSoft/ephysics/collision/shapes/TriangleShape.hpp @@ -0,0 +1,72 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * @brief Raycast test side for the triangle + */ + enum TriangleRaycastSide { + FRONT, //!< Raycast against front triangle + BACK, //!< Raycast against back triangle + FRONTANDBACK //!< Raycast against front and back triangle + }; + + /** + * This class represents a triangle collision shape that is centered + * at the origin and defined three points. + */ + class TriangleShape: public ConvexShape { + protected: + vec3 this.points[3]; //!< Three points of the triangle + TriangleRaycastSide this.raycastTestType; //!< Raycast test type for the triangle (front, back, front-back) + /// Private copy-ructor + TriangleShape( TriangleShape shape); + /// Private assignment operator + TriangleShape operator=( TriangleShape shape); + vec3 getLocalSupportPointWithoutMargin( vec3 direction, void** cachedCollisionData) override; + boolean testPointInside( vec3 localPoint, ProxyShape* proxyShape) override; + boolean raycast( Ray ray, RaycastInfo raycastInfo, ProxyShape* proxyShape) override; + sizet getSizeInBytes() override; + public: + /** + * @brief 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 + */ + TriangleShape( vec3 point1, + vec3 point2, + vec3 point3, + float margin = OBJECTMARGIN); + void getLocalBounds(vec3 min, vec3 max) override; + void setLocalScaling( vec3 scaling) override; + void computeLocalInertiaTensor(etk::Matrix3x3 tensor, float mass) override; + void computeAABB(AABB aabb, etk::Transform3D transform) override; + /// Return the raycast test type (front, back, front-back) + TriangleRaycastSide getRaycastTestType() ; + /** + * @brief Set the raycast test type (front, back, front-back) + * @param[in] testType Raycast test type for the triangle (front, back, front-back) + */ + void setRaycastTestType(TriangleRaycastSide testType); + /** + * @brief Return the coordinates of a given vertex of the triangle + * @param[in] index Index (0 to 2) of a vertex of the triangle + */ + vec3 getVertex(int index) ; + friend class ConcaveMeshRaycastCallback; + friend class TriangleOverlapCallback; + }; +} + diff --git a/src/org/atriaSoft/ephysics/configuration.hpp b/src/org/atriaSoft/ephysics/configuration.hpp new file mode 100644 index 0000000..e1c1a15 --- /dev/null +++ b/src/org/atriaSoft/ephysics/configuration.hpp @@ -0,0 +1,99 @@ +/** @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) + */ +#pragma once + +// Libraries +#include +#include + +/// Namespace ephysics +namespace ephysics { + // ------------------- Type definitions ------------------- // + typedef long long; + typedef etk::Pair longpair; + + // ------------------- Enumerations ------------------- // + + /// Position correction technique used in the raint solver (for joints). + /// BAUMGARTEJOINTS : Faster but can be innacurate in some situations. + /// NONLINEARGAUSSSEIDEL : Slower but more precise. This is the option used by default. + enum JointsPositionCorrectionTechnique {BAUMGARTEJOINTS, NONLINEARGAUSSSEIDEL}; + + /// Position correction technique used in the contact solver (for contacts) + /// BAUMGARTECONTACTS : 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). + /// SPLITIMPULSES : A bit slower but the error correction factor is not added to the + /// bodies momentum. This is the option used by default. + enum ContactsPositionCorrectionTechnique {BAUMGARTECONTACTS, SPLITIMPULSES}; + + // ------------------- Constants ------------------- // + /// Pi ant + float PI = float(3.14159265); + + /// 2*Pi ant + float PITIMES2 = float(6.28318530); + + /// Default friction coefficient for a rigid body + float DEFAULTFRICTIONCOEFFICIENT = float(0.3); + + /// Default bounciness factor for a rigid body + float DEFAULTBOUNCINESS = 0.5f; + + /// Default rolling resistance + float DEFAULTROLLINGRESISTANCE = 0.0f; + + /// True if the spleeping technique is enabled + boolean SPLEEPINGENABLED = true; + + /// Object margin for collision detection in meters (for the GJK-EPA Algorithm) + float OBJECTMARGIN = float(0.04); + + /// Distance threshold for two contact points for a valid persistent contact (in meters) + float PERSISTENTCONTACTDISTTHRESHOLD = float(0.03); + + /// Velocity threshold for contact velocity restitution + float RESTITUTIONVELOCITYTHRESHOLD = 1.0f; + + /// Number of iterations when solving the velocity raints of the Sequential Impulse technique + int DEFAULTVELOCITYSOLVERNBITERATIONS = 10; + + /// Number of iterations when solving the position raints of the Sequential Impulse technique + int DEFAULTPOSITIONSOLVERNBITERATIONS = 5; + + /// Time (in seconds) that a body must stay still to be considered sleeping + float DEFAULTTIMEBEFORESLEEP = 1.0f; + + /// A body with a linear velocity smaller than the sleep linear velocity (in m/s) + /// might enter sleeping mode. + float DEFAULTSLEEPLINEARVELOCITY = float(0.02); + + /// A body with angular velocity smaller than the sleep angular velocity (in rad/s) + /// might enter sleeping mode + float DEFAULTSLEEPANGULARVELOCITY = float(3.0 * (PI / 180.0)); + + /// 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 + float DYNAMICTREEAABBGAP = float(0.1); + + /// 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. + float DYNAMICTREEAABBLINGAPMULTIPLIER = float(1.7); + + /// Maximum number of contact manifolds in an overlapping pair that involves two + /// convex collision shapes. + int NBMAXCONTACTMANIFOLDSCONVEXSHAPE = 1; + + /// Maximum number of contact manifolds in an overlapping pair that involves at + /// least one concave collision shape. + int NBMAXCONTACTMANIFOLDSCONCAVESHAPE = 3; + +} diff --git a/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.cpp b/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.cpp new file mode 100644 index 0000000..807a974 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.cpp @@ -0,0 +1,212 @@ +/** @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) + */ +// Libraries +#include +#include + +using namespace ephysics; + +// Static variables definition + float BallAndSocketJoint::BETA = float(0.2); + +// Constructor +BallAndSocketJoint::BallAndSocketJoint( BallAndSocketJointInfo jointInfo) + : Joint(jointInfo), this.impulse(vec3(0, 0, 0)) { + + // Compute the local-space anchor point for each body + this.localAnchorPointBody1 = this.body1->getTransform().getInverse() * jointInfo.this.anchorPointWorldSpace; + this.localAnchorPointBody2 = this.body2->getTransform().getInverse() * jointInfo.this.anchorPointWorldSpace; +} + +// Initialize before solving the raint +void BallAndSocketJoint::initBeforeSolve( ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body1)->second; + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body2)->second; + + // Get the bodies center of mass and orientations + vec3 x1 = this.body1->this.centerOfMassWorld; + vec3 x2 = this.body2->this.centerOfMassWorld; + etk::Quaternion orientationBody1 = this.body1->getTransform().getOrientation(); + etk::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 * this.localAnchorPointBody1; + this.r2World = orientationBody2 * this.localAnchorPointBody2; + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the matrix K=JM^-1J^t (3x3 matrix) + float inverseMassBodies = this.body1->this.massInverse + this.body2->this.massInverse; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + + // Compute the inverse mass matrix K^-1 + this.inverseMassMatrix.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrix = massMatrix.getInverse(); + } + + // Compute the bias "b" of the raint + this.biasVector.setZero(); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + float biasFactor = (BETA / raintSolverData.timeStep); + this.biasVector = biasFactor * (x2 + this.r2World - x1 - this.r1World); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + + // Reset the accumulated impulse + this.impulse.setZero(); + } +} + +// Warm start the raint (apply the previous impulse at the beginning of the step) +void BallAndSocketJoint::warmstart( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Compute the impulse P=J^T * lambda for the body 1 + vec3 linearImpulseBody1 = -this.impulse; + vec3 angularImpulseBody1 = this.impulse.cross(this.r1World); + + // Apply the impulse to the body 1 + v1 += this.body1->this.massInverse * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the body 2 + vec3 angularImpulseBody2 = -this.impulse.cross(this.r2World); + + // Apply the impulse to the body to the body 2 + v2 += this.body2->this.massInverse * this.impulse; + w2 += this.i2 * angularImpulseBody2; +} + +// Solve the velocity raint +void BallAndSocketJoint::solveVelocityConstraint( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Compute J*v + vec3 Jv = v2 + w2.cross(this.r2World) - v1 - w1.cross(this.r1World); + + // Compute the Lagrange multiplier lambda + vec3 deltaLambda = this.inverseMassMatrix * (-Jv - this.biasVector); + this.impulse += deltaLambda; + + // Compute the impulse P=J^T * lambda for the body 1 + vec3 linearImpulseBody1 = -deltaLambda; + vec3 angularImpulseBody1 = deltaLambda.cross(this.r1World); + + // Apply the impulse to the body 1 + v1 += this.body1->this.massInverse * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the body 2 + vec3 angularImpulseBody2 = -deltaLambda.cross(this.r2World); + + // Apply the impulse to the body 2 + v2 += this.body2->this.massInverse * deltaLambda; + w2 += this.i2 * angularImpulseBody2; +} + +// Solve the position raint (for position error correction) +void BallAndSocketJoint::solvePositionConstraint( 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 != NONLINEARGAUSSSEIDEL) return; + + // Get the bodies center of mass and orientations + vec3 x1 = raintSolverData.positions[this.indexBody1]; + vec3 x2 = raintSolverData.positions[this.indexBody2]; + etk::Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + etk::Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.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 * this.localAnchorPointBody1; + this.r2World = q2 * this.localAnchorPointBody2; + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Recompute the inverse mass matrix K=J^TM^-1J of of the 3 translation raints + float inverseMassBodies = inverseMassBody1 + inverseMassBody2; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + this.inverseMassMatrix.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrix = massMatrix.getInverse(); + } + + // Compute the raint error (value of the C(x) function) + vec3 raintError = (x2 + this.r2World - x1 - 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. + vec3 lambda = this.inverseMassMatrix * (-raintError); + + // Compute the impulse of body 1 + vec3 linearImpulseBody1 = -lambda; + vec3 angularImpulseBody1 = lambda.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body center of mass and orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse of body 2 + vec3 angularImpulseBody2 = -lambda.cross(this.r2World); + + // Compute the pseudo velocity of body 2 + vec3 v2 = inverseMassBody2 * lambda; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); +} + diff --git a/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.hpp b/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.hpp new file mode 100644 index 0000000..5f5bd38 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/BallAndSocketJoint.hpp @@ -0,0 +1,68 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * @brief 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. + */ + struct BallAndSocketJointInfo : public JointInfo { + public : + vec3 this.anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + /** + * @brief 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 + */ + BallAndSocketJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace): + JointInfo(rigidBody1, rigidBody2, BALLSOCKETJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace) { + + } + }; + /** + * @brief Represents a ball-and-socket joint that allows arbitrary rotation + * between two bodies. This joint has three degrees of freedom. It can be used to + * create a chain of bodies for instance. + */ + class BallAndSocketJoint : public Joint { + private: + static float BETA; //!< Beta value for the bias factor of position correction + vec3 this.localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + vec3 this.localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + vec3 this.r1World; //!< Vector from center of body 2 to anchor point in world-space + vec3 this.r2World; //!< Vector from center of body 2 to anchor point in world-space + etk::Matrix3x3 this.i1; //!< Inertia tensor of body 1 (in world-space coordinates) + etk::Matrix3x3 this.i2; //!< Inertia tensor of body 2 (in world-space coordinates) + vec3 this.biasVector; //!< Bias vector for the raint + etk::Matrix3x3 this.inverseMassMatrix; //!< Inverse mass matrix K=JM^-1J^-t of the raint + vec3 this.impulse; //!< Accumulated impulse + /// Private copy-ructor + BallAndSocketJoint( BallAndSocketJoint raint); + /// Private assignment operator + BallAndSocketJoint operator=( BallAndSocketJoint raint); + sizet getSizeInBytes() override { + return sizeof(BallAndSocketJoint); + } + void initBeforeSolve( ConstraintSolverData raintSolverData) override; + void warmstart( ConstraintSolverData raintSolverData) override; + void solveVelocityConstraint( ConstraintSolverData raintSolverData) override; + void solvePositionConstraint( ConstraintSolverData raintSolverData) override; + public: + /// Constructor + BallAndSocketJoint( BallAndSocketJointInfo jointInfo); + }; +} diff --git a/src/org/atriaSoft/ephysics/constraint/ContactPoint.cpp b/src/org/atriaSoft/ephysics/constraint/ContactPoint.cpp new file mode 100644 index 0000000..6cf81fc --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/ContactPoint.cpp @@ -0,0 +1,171 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; +using namespace std; + +// Constructor +ContactPoint::ContactPoint( ContactPointInfo contactInfo): + this.body1(contactInfo.shape1->getBody()), + this.body2(contactInfo.shape2->getBody()), + this.normal(contactInfo.normal), + this.penetrationDepth(contactInfo.penetrationDepth), + this.localPointOnBody1(contactInfo.localPoint1), + this.localPointOnBody2(contactInfo.localPoint2), + this.worldPointOnBody1(contactInfo.shape1->getBody()->getTransform() * + contactInfo.shape1->getLocalToBodyTransform() * + contactInfo.localPoint1), + this.worldPointOnBody2(contactInfo.shape2->getBody()->getTransform() * + contactInfo.shape2->getLocalToBodyTransform() * + contactInfo.localPoint2), + this.isRestingContact(false) { + + this.frictionVectors[0] = vec3(0, 0, 0); + this.frictionVectors[1] = vec3(0, 0, 0); + + assert(this.penetrationDepth >= 0.0); + +} + +// Destructor +ContactPoint::~ContactPoint() { + +} + +// Return the reference to the body 1 +CollisionBody* ContactPoint::getBody1() { + return this.body1; +} + +// Return the reference to the body 2 +CollisionBody* ContactPoint::getBody2() { + return this.body2; +} + +// Return the normal vector of the contact +vec3 ContactPoint::getNormal() { + return this.normal; +} + +// Set the penetration depth of the contact +void ContactPoint::setPenetrationDepth(float penetrationDepth) { + this->this.penetrationDepth = penetrationDepth; +} + +// Return the contact point on body 1 +vec3 ContactPoint::getLocalPointOnBody1() { + return this.localPointOnBody1; +} + +// Return the contact point on body 2 +vec3 ContactPoint::getLocalPointOnBody2() { + return this.localPointOnBody2; +} + +// Return the contact world point on body 1 +vec3 ContactPoint::getWorldPointOnBody1() { + return this.worldPointOnBody1; +} + +// Return the contact world point on body 2 +vec3 ContactPoint::getWorldPointOnBody2() { + return this.worldPointOnBody2; +} + +// Return the cached penetration impulse +float ContactPoint::getPenetrationImpulse() { + return this.penetrationImpulse; +} + +// Return the cached first friction impulse +float ContactPoint::getFrictionImpulse1() { + return this.frictionImpulse1; +} + +// Return the cached second friction impulse +float ContactPoint::getFrictionImpulse2() { + return this.frictionImpulse2; +} + +// Return the cached rolling resistance impulse +vec3 ContactPoint::getRollingResistanceImpulse() { + return this.rollingResistanceImpulse; +} + +// Set the cached penetration impulse +void ContactPoint::setPenetrationImpulse(float impulse) { + this.penetrationImpulse = impulse; +} + +// Set the first cached friction impulse +void ContactPoint::setFrictionImpulse1(float impulse) { + this.frictionImpulse1 = impulse; +} + +// Set the second cached friction impulse +void ContactPoint::setFrictionImpulse2(float impulse) { + this.frictionImpulse2 = impulse; +} + +// Set the cached rolling resistance impulse +void ContactPoint::setRollingResistanceImpulse( vec3 impulse) { + this.rollingResistanceImpulse = impulse; +} + +// Set the contact world point on body 1 +void ContactPoint::setWorldPointOnBody1( vec3 worldPoint) { + this.worldPointOnBody1 = worldPoint; +} + +// Set the contact world point on body 2 +void ContactPoint::setWorldPointOnBody2( vec3 worldPoint) { + this.worldPointOnBody2 = worldPoint; +} + +// Return true if the contact is a resting contact +boolean ContactPoint::getIsRestingContact() { + return this.isRestingContact; +} + +// Set the this.isRestingContact variable +void ContactPoint::setIsRestingContact(boolean isRestingContact) { + this.isRestingContact = isRestingContact; +} + +// Get the first friction vector +vec3 ContactPoint::getFrictionVector1() { + return this.frictionVectors[0]; +} + +// Set the first friction vector +void ContactPoint::setFrictionVector1( vec3 frictionVector1) { + this.frictionVectors[0] = frictionVector1; +} + +// Get the second friction vector +vec3 ContactPoint::getFrictionvec2() { + return this.frictionVectors[1]; +} + +// Set the second friction vector +void ContactPoint::setFrictionvec2( vec3 frictionvec2) { + this.frictionVectors[1] = frictionvec2; +} + +// Return the penetration depth of the contact +float ContactPoint::getPenetrationDepth() { + return this.penetrationDepth; +} + +// Return the number of bytes used by the contact point +sizet ContactPoint::getSizeInBytes() { + return sizeof(ContactPoint); +} diff --git a/src/org/atriaSoft/ephysics/constraint/ContactPoint.hpp b/src/org/atriaSoft/ephysics/constraint/ContactPoint.hpp new file mode 100644 index 0000000..2e9d150 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/ContactPoint.hpp @@ -0,0 +1,145 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace ephysics { + + /** + * @brief 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. + */ + struct ContactPointInfo { + public: + ProxyShape* shape1; //!< First proxy shape of the contact + ProxyShape* shape2; //!< Second proxy shape of the contact + CollisionShape* collisionShape1; //!< First collision shape + CollisionShape* collisionShape2; //!< Second collision shape + vec3 normal; //!< Normalized normal vector of the collision contact in world space + float penetrationDepth; //!< Penetration depth of the contact + vec3 localPoint1; //!< Contact point of body 1 in local space of body 1 + vec3 localPoint2; //!< Contact point of body 2 in local space of body 2 + ContactPointInfo(ProxyShape* proxyShape1, + ProxyShape* proxyShape2, + CollisionShape* collShape1, + CollisionShape* collShape2, + vec3 normal, + float penetrationDepth, + vec3 localPoint1, + vec3 localPoint2): + shape1(proxyShape1), + shape2(proxyShape2), + collisionShape1(collShape1), + collisionShape2(collShape2), + normal(normal), + penetrationDepth(penetrationDepth), + localPoint1(localPoint1), + localPoint2(localPoint2) { + + } + ContactPointInfo(): + shape1(null), + shape2(null), + collisionShape1(null), + collisionShape2(null) { + // TODO: add it for etk::Vector + } + }; + + /** + * @brief This class represents a collision contact point between two + * bodies in the physics engine. + */ + class ContactPoint { + private : + CollisionBody* this.body1; //!< First rigid body of the contact + CollisionBody* this.body2; //!< Second rigid body of the contact + vec3 this.normal; //!< Normalized normal vector of the contact (from body1 toward body2) in world space + float this.penetrationDepth; //!< Penetration depth + vec3 this.localPointOnBody1; //!< Contact point on body 1 in local space of body 1 + vec3 this.localPointOnBody2; //!< Contact point on body 2 in local space of body 2 + vec3 this.worldPointOnBody1; //!< Contact point on body 1 in world space + vec3 this.worldPointOnBody2; //!< Contact point on body 2 in world space + boolean this.isRestingContact; //!< True if the contact is a resting contact (exists for more than one time step) + vec3 this.frictionVectors[2]; //!< Two orthogonal vectors that span the tangential friction plane + float this.penetrationImpulse; //!< Cached penetration impulse + float this.frictionImpulse1; //!< Cached first friction impulse + float this.frictionImpulse2; //!< Cached second friction impulse + vec3 this.rollingResistanceImpulse; //!< Cached rolling resistance impulse + /// Private copy-ructor + ContactPoint( ContactPoint contact) = delete; + /// Private assignment operator + ContactPoint operator=( ContactPoint contact) = delete; + public : + /// Constructor + ContactPoint( ContactPointInfo contactInfo); + /// Destructor + ~ContactPoint(); + /// Return the reference to the body 1 + CollisionBody* getBody1() ; + /// Return the reference to the body 2 + CollisionBody* getBody2() ; + /// Return the normal vector of the contact + vec3 getNormal() ; + /// Set the penetration depth of the contact + void setPenetrationDepth(float penetrationDepth); + /// Return the contact local point on body 1 + vec3 getLocalPointOnBody1() ; + /// Return the contact local point on body 2 + vec3 getLocalPointOnBody2() ; + /// Return the contact world point on body 1 + vec3 getWorldPointOnBody1() ; + /// Return the contact world point on body 2 + vec3 getWorldPointOnBody2() ; + /// Return the cached penetration impulse + float getPenetrationImpulse() ; + /// Return the cached first friction impulse + float getFrictionImpulse1() ; + /// Return the cached second friction impulse + float getFrictionImpulse2() ; + /// Return the cached rolling resistance impulse + vec3 getRollingResistanceImpulse() ; + /// Set the cached penetration impulse + void setPenetrationImpulse(float impulse); + /// Set the first cached friction impulse + void setFrictionImpulse1(float impulse); + /// Set the second cached friction impulse + void setFrictionImpulse2(float impulse); + /// Set the cached rolling resistance impulse + void setRollingResistanceImpulse( vec3 impulse); + /// Set the contact world point on body 1 + void setWorldPointOnBody1( vec3 worldPoint); + /// Set the contact world point on body 2 + void setWorldPointOnBody2( vec3 worldPoint); + /// Return true if the contact is a resting contact + boolean getIsRestingContact() ; + /// Set the this.isRestingContact variable + void setIsRestingContact(boolean isRestingContact); + /// Get the first friction vector + vec3 getFrictionVector1() ; + /// Set the first friction vector + void setFrictionVector1( vec3 frictionVector1); + /// Get the second friction vector + vec3 getFrictionvec2() ; + /// Set the second friction vector + void setFrictionvec2( vec3 frictionvec2); + /// Return the penetration depth + float getPenetrationDepth() ; + /// Return the number of bytes used by the contact point + sizet getSizeInBytes() ; + }; + +} diff --git a/src/org/atriaSoft/ephysics/constraint/FixedJoint.cpp b/src/org/atriaSoft/ephysics/constraint/FixedJoint.cpp new file mode 100644 index 0000000..27b33c5 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/FixedJoint.cpp @@ -0,0 +1,311 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +// Static variables definition + float FixedJoint::BETA = float(0.2); + +// Constructor +FixedJoint::FixedJoint( FixedJointInfo jointInfo) + : Joint(jointInfo), this.impulseTranslation(0, 0, 0), this.impulseRotation(0, 0, 0) { + + // Compute the local-space anchor point for each body + etk::Transform3D transform1 = this.body1->getTransform(); + etk::Transform3D transform2 = this.body2->getTransform(); + this.localAnchorPointBody1 = transform1.getInverse() * jointInfo.this.anchorPointWorldSpace; + this.localAnchorPointBody2 = transform2.getInverse() * jointInfo.this.anchorPointWorldSpace; + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation() * + transform1.getOrientation().getInverse(); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); +} + +// Destructor +FixedJoint::~FixedJoint() { + +} + +// Initialize before solving the raint +void FixedJoint::initBeforeSolve( ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body1)->second; + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body2)->second; + + // Get the bodies positions and orientations + vec3 x1 = this.body1->this.centerOfMassWorld; + vec3 x2 = this.body2->this.centerOfMassWorld; + etk::Quaternion orientationBody1 = this.body1->getTransform().getOrientation(); + etk::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 * this.localAnchorPointBody1; + this.r2World = orientationBody2 * this.localAnchorPointBody2; + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + float inverseMassBodies = this.body1->this.massInverse + this.body2->this.massInverse; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + + // Compute the inverse mass matrix K^-1 for the 3 translation raints + this.inverseMassMatrixTranslation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.getInverse(); + } + + // Compute the bias "b" of the raint for the 3 translation raints + float biasFactor = (BETA / raintSolverData.timeStep); + this.biasTranslation.setZero(); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.biasTranslation = biasFactor * (x2 + this.r2World - x1 - this.r1World); + } + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotation = this.i1 + this.i2; + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotation = this.inverseMassMatrixRotation.getInverse(); + } + + // Compute the bias "b" for the 3 rotation raints + this.biasRotation.setZero(); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + etk::Quaternion currentOrientationDifference = orientationBody2 * orientationBody1.getInverse(); + currentOrientationDifference.normalize(); + etk::Quaternion qError = currentOrientationDifference * this.initOrientationDifferenceInv; + this.biasRotation = biasFactor * float(2.0) * qError.getVectorV(); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + + // Reset the accumulated impulses + this.impulseTranslation.setZero(); + this.impulseRotation.setZero(); + } +} + +// Warm start the raint (apply the previous impulse at the beginning of the step) +void FixedJoint::warmstart( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // Compute the impulse P=J^T * lambda for the 3 translation raints for body 1 + vec3 linearImpulseBody1 = -this.impulseTranslation; + vec3 angularImpulseBody1 = this.impulseTranslation.cross(this.r1World); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 1 + angularImpulseBody1 += -this.impulseRotation; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the 3 translation raints for body 2 + vec3 angularImpulseBody2 = -this.impulseTranslation.cross(this.r2World); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 2 + angularImpulseBody2 += this.impulseRotation; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * this.impulseTranslation; + w2 += this.i2 * angularImpulseBody2; +} + +// Solve the velocity raint +void FixedJoint::solveVelocityConstraint( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v for the 3 translation raints + vec3 JvTranslation = v2 + w2.cross(this.r2World) - v1 - w1.cross(this.r1World); + + // Compute the Lagrange multiplier lambda + vec3 deltaLambda = this.inverseMassMatrixTranslation * + (-JvTranslation - this.biasTranslation); + this.impulseTranslation += deltaLambda; + + // Compute the impulse P=J^T * lambda for body 1 + vec3 linearImpulseBody1 = -deltaLambda; + vec3 angularImpulseBody1 = deltaLambda.cross(this.r1World); + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for body 2 + vec3 angularImpulseBody2 = -deltaLambda.cross(this.r2World); + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * deltaLambda; + w2 += this.i2 * angularImpulseBody2; + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 3 rotation raints + vec3 JvRotation = w2 - w1; + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + vec3 deltaLambda2 = this.inverseMassMatrixRotation * (-JvRotation - this.biasRotation); + this.impulseRotation += deltaLambda2; + + // Compute the impulse P=J^T * lambda for the 3 rotation raints for body 1 + angularImpulseBody1 = -deltaLambda2; + + // Apply the impulse to the body 1 + w1 += this.i1 * angularImpulseBody1; + + // Apply the impulse to the body 2 + w2 += this.i2 * deltaLambda2; +} + +// Solve the position raint (for position error correction) +void FixedJoint::solvePositionConstraint( 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 != NONLINEARGAUSSSEIDEL) return; + + // Get the bodies positions and orientations + vec3 x1 = raintSolverData.positions[this.indexBody1]; + vec3 x2 = raintSolverData.positions[this.indexBody2]; + etk::Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + etk::Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.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 * this.localAnchorPointBody1; + this.r2World = q2 * this.localAnchorPointBody2; + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // --------------- Translation Constraints --------------- // + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + float inverseMassBodies = this.body1->this.massInverse + this.body2->this.massInverse; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.getInverse(); + } + + // Compute position error for the 3 translation raints + vec3 errorTranslation = x2 + this.r2World - x1 - this.r1World; + + // Compute the Lagrange multiplier lambda + vec3 lambdaTranslation = this.inverseMassMatrixTranslation * (-errorTranslation); + + // Compute the impulse of body 1 + vec3 linearImpulseBody1 = -lambdaTranslation; + vec3 angularImpulseBody1 = lambdaTranslation.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse of body 2 + vec3 angularImpulseBody2 = -lambdaTranslation.cross(this.r2World); + + // Compute the pseudo velocity of body 2 + vec3 v2 = inverseMassBody2 * lambdaTranslation; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 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 + this.i2; + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotation = this.inverseMassMatrixRotation.getInverse(); + } + + // Compute the position error for the 3 rotation raints + etk::Quaternion currentOrientationDifference = q2 * q1.getInverse(); + currentOrientationDifference.normalize(); + etk::Quaternion qError = currentOrientationDifference * this.initOrientationDifferenceInv; + vec3 errorRotation = float(2.0) * qError.getVectorV(); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + vec3 lambdaRotation = this.inverseMassMatrixRotation * (-errorRotation); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = -lambdaRotation; + + // Compute the pseudo velocity of body 1 + w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the pseudo velocity of body 2 + w2 = this.i2 * lambdaRotation; + + // Update the body position/orientation of body 2 + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); +} + diff --git a/src/org/atriaSoft/ephysics/constraint/FixedJoint.hpp b/src/org/atriaSoft/ephysics/constraint/FixedJoint.hpp new file mode 100644 index 0000000..5e4b399 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/FixedJoint.hpp @@ -0,0 +1,78 @@ +/** @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) + */ +#pragma once + +// Libraries +#include +#include + +namespace ephysics { + + /** + * 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. + */ + struct FixedJointInfo : public JointInfo { + public : + vec3 this.anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + /** + * @breif Contructor + * @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(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace): + JointInfo(rigidBody1, rigidBody2, FIXEDJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace){ + + } + }; + + /** + * @breif It represents a fixed joint that is used to forbid any translation or rotation + * between two bodies. + */ + class FixedJoint : public Joint { + private: + static float BETA; //!< Beta value for the bias factor of position correction + vec3 this.localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + vec3 this.localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + vec3 this.r1World; //!< Vector from center of body 2 to anchor point in world-space + vec3 this.r2World; //!< Vector from center of body 2 to anchor point in world-space + etk::Matrix3x3 this.i1; //!< Inertia tensor of body 1 (in world-space coordinates) + etk::Matrix3x3 this.i2; //!< Inertia tensor of body 2 (in world-space coordinates) + vec3 this.impulseTranslation; //!< Accumulated impulse for the 3 translation raints + vec3 this.impulseRotation; //!< Accumulate impulse for the 3 rotation raints + etk::Matrix3x3 this.inverseMassMatrixTranslation; //!< Inverse mass matrix K=JM^-1J^-t of the 3 translation raints (3x3 matrix) + etk::Matrix3x3 this.inverseMassMatrixRotation; //!< Inverse mass matrix K=JM^-1J^-t of the 3 rotation raints (3x3 matrix) + vec3 this.biasTranslation; //!< Bias vector for the 3 translation raints + vec3 this.biasRotation; //!< Bias vector for the 3 rotation raints + etk::Quaternion this.initOrientationDifferenceInv; //!< Inverse of the initial orientation difference between the two bodies + /// Private copy-ructor + FixedJoint( FixedJoint raint) = delete; + /// Private assignment operator + FixedJoint operator=( FixedJoint raint) = delete; + + sizet getSizeInBytes() override { + return sizeof(FixedJoint); + } + void initBeforeSolve( ConstraintSolverData raintSolverData) override; + void warmstart( ConstraintSolverData raintSolverData) override; + void solveVelocityConstraint( ConstraintSolverData raintSolverData) override; + void solvePositionConstraint( ConstraintSolverData raintSolverData) override; + public: + /// Constructor + FixedJoint( FixedJointInfo jointInfo); + /// Destructor + virtual ~FixedJoint(); + }; +} + diff --git a/src/org/atriaSoft/ephysics/constraint/HingeJoint.cpp b/src/org/atriaSoft/ephysics/constraint/HingeJoint.cpp new file mode 100644 index 0000000..6bd19d8 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/HingeJoint.cpp @@ -0,0 +1,849 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +// Static variables definition + float HingeJoint::BETA = float(0.2); + +// Constructor +HingeJoint::HingeJoint( HingeJointInfo jointInfo) + : Joint(jointInfo), this.impulseTranslation(0, 0, 0), this.impulseRotation(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 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.lowerLimit >= -2.0 * PI); + assert(this.upperLimit >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.upperLimit <= 2.0 * PI); + + // Compute the local-space anchor point for each body + etk::Transform3D transform1 = this.body1->getTransform(); + etk::Transform3D transform2 = this.body2->getTransform(); + this.localAnchorPointBody1 = transform1.getInverse() * jointInfo.this.anchorPointWorldSpace; + this.localAnchorPointBody2 = transform2.getInverse() * jointInfo.this.anchorPointWorldSpace; + + // Compute the local-space hinge axis + this.hingeLocalAxisBody1 = transform1.getOrientation().getInverse() * jointInfo.rotationAxisWorld; + this.hingeLocalAxisBody2 = transform2.getOrientation().getInverse() * jointInfo.rotationAxisWorld; + this.hingeLocalAxisBody1.normalize(); + this.hingeLocalAxisBody2.normalize(); + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation() * + transform1.getOrientation().getInverse(); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); +} + +// Destructor +HingeJoint::~HingeJoint() { + +} + +// Initialize before solving the raint +void HingeJoint::initBeforeSolve( ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the velocity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body1)->second; + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body2)->second; + + // Get the bodies positions and orientations + vec3 x1 = this.body1->this.centerOfMassWorld; + vec3 x2 = this.body2->this.centerOfMassWorld; + etk::Quaternion orientationBody1 = this.body1->getTransform().getOrientation(); + etk::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 * this.localAnchorPointBody1; + this.r2World = orientationBody2 * this.localAnchorPointBody2; + + // Compute the current angle around the hinge axis + float hingeAngle = computeCurrentHingeAngle(orientationBody1, orientationBody2); + + // Check if the limit raints are violated or not + float lowerLimitError = hingeAngle - this.lowerLimit; + float upperLimitError = this.upperLimit - hingeAngle; + boolean oldIsLowerLimitViolated = this.isLowerLimitViolated; + this.isLowerLimitViolated = lowerLimitError <= 0; + if (this.isLowerLimitViolated != oldIsLowerLimitViolated) { + this.impulseLowerLimit = 0.0; + } + boolean oldIsUpperLimitViolated = this.isUpperLimitViolated; + this.isUpperLimitViolated = upperLimitError <= 0; + if (this.isUpperLimitViolated != oldIsUpperLimitViolated) { + this.impulseUpperLimit = 0.0; + } + + // Compute vectors needed in the Jacobian + mA1 = orientationBody1 * this.hingeLocalAxisBody1; + vec3 a2 = orientationBody2 * this.hingeLocalAxisBody2; + mA1.normalize(); + a2.normalize(); + vec3 b2 = a2.getOrthoVector(); + vec3 c2 = a2.cross(b2); + this.b2CrossA1 = b2.cross(mA1); + this.c2CrossA1 = c2.cross(mA1); + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // Compute the inverse mass matrix K=JM^-1J^t for the 3 translation raints (3x3 matrix) + float inverseMassBodies = this.body1->this.massInverse + this.body2->this.massInverse; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.getInverse(); + } + + // Compute the bias "b" of the translation raints + this.bTranslation.setZero(); + float biasFactor = (BETA / raintSolverData.timeStep); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bTranslation = biasFactor * (x2 + this.r2World - x1 - this.r1World); + } + + // Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation raints (2x2 matrix) + vec3 I1B2CrossA1 = this.i1 * this.b2CrossA1; + vec3 I1C2CrossA1 = this.i1 * this.c2CrossA1; + vec3 I2B2CrossA1 = this.i2 * this.b2CrossA1; + vec3 I2C2CrossA1 = this.i2 * this.c2CrossA1; + float el11 = this.b2CrossA1.dot(I1B2CrossA1) + + this.b2CrossA1.dot(I2B2CrossA1); + float el12 = this.b2CrossA1.dot(I1C2CrossA1) + + this.b2CrossA1.dot(I2C2CrossA1); + float el21 = this.c2CrossA1.dot(I1B2CrossA1) + + this.c2CrossA1.dot(I2B2CrossA1); + float el22 = this.c2CrossA1.dot(I1C2CrossA1) + + this.c2CrossA1.dot(I2C2CrossA1); + etk::Matrix2x2 matrixKRotation(el11, el12, el21, el22); + this.inverseMassMatrixRotation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotation = matrixKRotation.getInverse(); + } + + // Compute the bias "b" of the rotation raints + this.bRotation.setZero(); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bRotation = biasFactor * vec2(mA1.dot(b2), mA1.dot(c2)); + } + + // If warm-starting is not enabled + if (!raintSolverData.isWarmStartingActive) { + + // Reset all the accumulated impulses + this.impulseTranslation.setZero(); + this.impulseRotation.setZero(); + this.impulseLowerLimit = 0.0; + this.impulseUpperLimit = 0.0; + this.impulseMotor = 0.0; + } + + // If the motor or limits are enabled + if (this.isMotorEnabled || (this.isLimitEnabled hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (this.isLowerLimitViolated || this.isUpperLimitViolated))) { + + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits and motor (1x1 matrix) + this.inverseMassMatrixLimitMotor = mA1.dot(this.i1 * mA1) + mA1.dot(this.i2 * mA1); + this.inverseMassMatrixLimitMotor = (this.inverseMassMatrixLimitMotor > 0.0) ? + 1.0f / this.inverseMassMatrixLimitMotor : 0.0f; + + if (this.isLimitEnabled) { + + // Compute the bias "b" of the lower limit raint + this.bLowerLimit = 0.0; + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bLowerLimit = biasFactor * lowerLimitError; + } + + // Compute the bias "b" of the upper limit raint + this.bUpperLimit = 0.0; + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bUpperLimit = biasFactor * upperLimitError; + } + } + } +} + +// Warm start the raint (apply the previous impulse at the beginning of the step) +void HingeJoint::warmstart( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // Compute the impulse P=J^T * lambda for the 2 rotation raints + vec3 rotationImpulse = -this.b2CrossA1 * this.impulseRotation.x() - this.c2CrossA1 * this.impulseRotation.y(); + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints + vec3 limitsImpulse = (this.impulseUpperLimit - this.impulseLowerLimit) * mA1; + + // Compute the impulse P=J^T * lambda for the motor raint + vec3 motorImpulse = -this.impulseMotor * mA1; + + // Compute the impulse P=J^T * lambda for the 3 translation raints of body 1 + vec3 linearImpulseBody1 = -this.impulseTranslation; + vec3 angularImpulseBody1 = this.impulseTranslation.cross(this.r1World); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 1 + angularImpulseBody1 += rotationImpulse; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + angularImpulseBody1 += limitsImpulse; + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + angularImpulseBody1 += motorImpulse; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the 3 translation raints of body 2 + vec3 angularImpulseBody2 = -this.impulseTranslation.cross(this.r2World); + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 2 + angularImpulseBody2 += -rotationImpulse; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 2 + angularImpulseBody2 += -limitsImpulse; + + // Compute the impulse P=J^T * lambda for the motor raint of body 2 + angularImpulseBody2 += -motorImpulse; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * this.impulseTranslation; + w2 += this.i2 * angularImpulseBody2; +} + +// Solve the velocity raint +void HingeJoint::solveVelocityConstraint( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v + vec3 JvTranslation = v2 + w2.cross(this.r2World) - v1 - w1.cross(this.r1World); + + // Compute the Lagrange multiplier lambda + vec3 deltaLambdaTranslation = this.inverseMassMatrixTranslation * + (-JvTranslation - this.bTranslation); + this.impulseTranslation += deltaLambdaTranslation; + + // Compute the impulse P=J^T * lambda of body 1 + vec3 linearImpulseBody1 = -deltaLambdaTranslation; + vec3 angularImpulseBody1 = deltaLambdaTranslation.cross(this.r1World); + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda of body 2 + vec3 angularImpulseBody2 = -deltaLambdaTranslation.cross(this.r2World); + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * deltaLambdaTranslation; + w2 += this.i2 * angularImpulseBody2; + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 2 rotation raints + vec2 JvRotation(-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 + vec2 deltaLambdaRotation = this.inverseMassMatrixRotation * (-JvRotation - this.bRotation); + this.impulseRotation += deltaLambdaRotation; + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 1 + angularImpulseBody1 = -this.b2CrossA1 * deltaLambdaRotation.x() - + this.c2CrossA1 * deltaLambdaRotation.y(); + + // Apply the impulse to the body 1 + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the 2 rotation raints of body 2 + angularImpulseBody2 = this.b2CrossA1 * deltaLambdaRotation.x() + + this.c2CrossA1 * deltaLambdaRotation.y(); + + // Apply the impulse to the body 2 + w2 += this.i2 * angularImpulseBody2; + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute J*v for the lower limit raint + float JvLowerLimit = (w2 - w1).dot(mA1); + + // Compute the Lagrange multiplier lambda for the lower limit raint + float deltaLambdaLower = this.inverseMassMatrixLimitMotor * (-JvLowerLimit -this.bLowerLimit); + float lambdaTemp = this.impulseLowerLimit; + this.impulseLowerLimit = etk::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 + vec3 angularImpulseBody1 = -deltaLambdaLower * mA1; + + // Apply the impulse to the body 1 + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + vec3 angularImpulseBody2 = deltaLambdaLower * mA1; + + // Apply the impulse to the body 2 + w2 += this.i2 * angularImpulseBody2; + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute J*v for the upper limit raint + float JvUpperLimit = -(w2 - w1).dot(mA1); + + // Compute the Lagrange multiplier lambda for the upper limit raint + float deltaLambdaUpper = this.inverseMassMatrixLimitMotor * (-JvUpperLimit -this.bUpperLimit); + float lambdaTemp = this.impulseUpperLimit; + this.impulseUpperLimit = etk::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 + vec3 angularImpulseBody1 = deltaLambdaUpper * mA1; + + // Apply the impulse to the body 1 + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + vec3 angularImpulseBody2 = -deltaLambdaUpper * mA1; + + // Apply the impulse to the body 2 + w2 += this.i2 * angularImpulseBody2; + } + } + + // --------------- Motor --------------- // + + // If the motor is enabled + if (this.isMotorEnabled) { + + // Compute J*v for the motor + float JvMotor = mA1.dot(w1 - w2); + + // Compute the Lagrange multiplier lambda for the motor + float maxMotorImpulse = this.maxMotorTorque * raintSolverData.timeStep; + float deltaLambdaMotor = this.inverseMassMatrixLimitMotor * (-JvMotor - this.motorSpeed); + float lambdaTemp = this.impulseMotor; + this.impulseMotor = clamp(this.impulseMotor + deltaLambdaMotor, -maxMotorImpulse, maxMotorImpulse); + deltaLambdaMotor = this.impulseMotor - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the motor of body 1 + vec3 angularImpulseBody1 = -deltaLambdaMotor * mA1; + + // Apply the impulse to the body 1 + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the motor of body 2 + vec3 angularImpulseBody2 = deltaLambdaMotor * mA1; + + // Apply the impulse to the body 2 + w2 += this.i2 * angularImpulseBody2; + } +} + +// Solve the position raint (for position error correction) +void HingeJoint::solvePositionConstraint( 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 != NONLINEARGAUSSSEIDEL) return; + + // Get the bodies positions and orientations + vec3 x1 = raintSolverData.positions[this.indexBody1]; + vec3 x2 = raintSolverData.positions[this.indexBody2]; + etk::Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + etk::Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.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 * this.localAnchorPointBody1; + this.r2World = q2 * this.localAnchorPointBody2; + + // Compute the current angle around the hinge axis + float hingeAngle = computeCurrentHingeAngle(q1, q2); + + // Check if the limit raints are violated or not + float lowerLimitError = hingeAngle - this.lowerLimit; + float upperLimitError = this.upperLimit - hingeAngle; + this.isLowerLimitViolated = lowerLimitError <= 0; + this.isUpperLimitViolated = upperLimitError <= 0; + + // Compute vectors needed in the Jacobian + mA1 = q1 * this.hingeLocalAxisBody1; + vec3 a2 = q2 * this.hingeLocalAxisBody2; + mA1.normalize(); + a2.normalize(); + vec3 b2 = a2.getOrthoVector(); + vec3 c2 = a2.cross(b2); + this.b2CrossA1 = b2.cross(mA1); + this.c2CrossA1 = c2.cross(mA1); + + // Compute the corresponding skew-symmetric matrices + etk::Matrix3x3 skewSymmetricMatrixU1= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r1World); + etk::Matrix3x3 skewSymmetricMatrixU2= etk::Matrix3x3::computeSkewSymmetricMatrixForCrossProduct(this.r2World); + + // --------------- Translation Constraints --------------- // + + // Compute the matrix K=JM^-1J^t (3x3 matrix) for the 3 translation raints + float inverseMassBodies = this.body1->this.massInverse + this.body2->this.massInverse; + etk::Matrix3x3 massMatrix = etk::Matrix3x3(inverseMassBodies, 0, 0, + 0, inverseMassBodies, 0, + 0, 0, inverseMassBodies) + + skewSymmetricMatrixU1 * this.i1 * skewSymmetricMatrixU1.getTranspose() + + skewSymmetricMatrixU2 * this.i2 * skewSymmetricMatrixU2.getTranspose(); + this.inverseMassMatrixTranslation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslation = massMatrix.getInverse(); + } + + // Compute position error for the 3 translation raints + vec3 errorTranslation = x2 + this.r2World - x1 - this.r1World; + + // Compute the Lagrange multiplier lambda + vec3 lambdaTranslation = this.inverseMassMatrixTranslation * (-errorTranslation); + + // Compute the impulse of body 1 + vec3 linearImpulseBody1 = -lambdaTranslation; + vec3 angularImpulseBody1 = lambdaTranslation.cross(this.r1World); + + // Compute the pseudo velocity of body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse of body 2 + vec3 angularImpulseBody2 = -lambdaTranslation.cross(this.r2World); + + // Compute the pseudo velocity of body 2 + vec3 v2 = inverseMassBody2 * lambdaTranslation; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); + + // --------------- Rotation Constraints --------------- // + + // Compute the inverse mass matrix K=JM^-1J^t for the 2 rotation raints (2x2 matrix) + vec3 I1B2CrossA1 = this.i1 * this.b2CrossA1; + vec3 I1C2CrossA1 = this.i1 * this.c2CrossA1; + vec3 I2B2CrossA1 = this.i2 * this.b2CrossA1; + vec3 I2C2CrossA1 = this.i2 * this.c2CrossA1; + float el11 = this.b2CrossA1.dot(I1B2CrossA1) + + this.b2CrossA1.dot(I2B2CrossA1); + float el12 = this.b2CrossA1.dot(I1C2CrossA1) + + this.b2CrossA1.dot(I2C2CrossA1); + float el21 = this.c2CrossA1.dot(I1B2CrossA1) + + this.c2CrossA1.dot(I2B2CrossA1); + float el22 = this.c2CrossA1.dot(I1C2CrossA1) + + this.c2CrossA1.dot(I2C2CrossA1); + etk::Matrix2x2 matrixKRotation(el11, el12, el21, el22); + this.inverseMassMatrixRotation.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotation = matrixKRotation.getInverse(); + } + + // Compute the position error for the 3 rotation raints + vec2 errorRotation = vec2(mA1.dot(b2), mA1.dot(c2)); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + vec2 lambdaRotation = this.inverseMassMatrixRotation * (-errorRotation); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = -this.b2CrossA1 * lambdaRotation.x() - this.c2CrossA1 * lambdaRotation.y(); + + // Compute the pseudo velocity of body 1 + w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse of body 2 + angularImpulseBody2 = this.b2CrossA1 * lambdaRotation.x() + this.c2CrossA1 * lambdaRotation.y(); + + // Compute the pseudo velocity of body 2 + w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + q2 += etk::Quaternion(0, w2) * q2 * 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 = mA1.dot(this.i1 * mA1) + mA1.dot(this.i2 * mA1); + this.inverseMassMatrixLimitMotor = (this.inverseMassMatrixLimitMotor > 0.0) ? + 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 + float lambdaLowerLimit = this.inverseMassMatrixLimitMotor * (-lowerLimitError ); + + // Compute the impulse P=J^T * lambda of body 1 + vec3 angularImpulseBody1 = -lambdaLowerLimit * mA1; + + // Compute the pseudo velocity of body 1 + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse P=J^T * lambda of body 2 + vec3 angularImpulseBody2 = lambdaLowerLimit * mA1; + + // Compute the pseudo velocity of body 2 + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute the Lagrange multiplier lambda for the upper limit raint + float lambdaUpperLimit = this.inverseMassMatrixLimitMotor * (-upperLimitError); + + // Compute the impulse P=J^T * lambda of body 1 + vec3 angularImpulseBody1 = lambdaUpperLimit * mA1; + + // Compute the pseudo velocity of body 1 + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse P=J^T * lambda of body 2 + vec3 angularImpulseBody2 = -lambdaUpperLimit * mA1; + + // Compute the pseudo velocity of body 2 + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); + } + } +} + + +// Enable/Disable the limits of the joint +/** + * @param isLimitEnabled True if you want to enable the limits of the joint and + * false otherwise + */ +void HingeJoint::enableLimit(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 motor of the joint and + * false otherwise + */ +void HingeJoint::enableMotor(boolean isMotorEnabled) { + + this.isMotorEnabled = isMotorEnabled; + this.impulseMotor = 0.0; + + // 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) + */ +void HingeJoint::setMinAngleLimit(float lowerLimit) { + + assert(this.lowerLimit <= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.lowerLimit >= -2.0 * PI); + + if (lowerLimit != this.lowerLimit) { + + this.lowerLimit = lowerLimit; + + // Reset the limits + resetLimits(); + } +} + +// Set the maximum angle limit +/** + * @param upperLimit The maximum limit angle of the joint (in radian) + */ +void HingeJoint::setMaxAngleLimit(float upperLimit) { + + assert(upperLimit >= 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj upperLimit <= 2.0 * PI); + + if (upperLimit != this.upperLimit) { + + this.upperLimit = upperLimit; + + // Reset the limits + resetLimits(); + } +} + +// Reset the limits +void HingeJoint::resetLimits() { + + // Reset the accumulated impulses for the limits + this.impulseLowerLimit = 0.0; + this.impulseUpperLimit = 0.0; + + // Wake up the two bodies of the joint + this.body1->setIsSleeping(false); + this.body2->setIsSleeping(false); +} + +// Set the motor speed +void HingeJoint::setMotorSpeed(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); + } +} + +// Set the maximum motor torque +/** + * @param maxMotorTorque The maximum torque (in Newtons) of the joint motor + */ +void HingeJoint::setMaxMotorTorque(float maxMotorTorque) { + + if (maxMotorTorque != this.maxMotorTorque) { + + assert(this.maxMotorTorque >= 0.0); + this.maxMotorTorque = maxMotorTorque; + + // Wake up the two bodies of the joint + this.body1->setIsSleeping(false); + this.body2->setIsSleeping(false); + } +} + +// Given an angle in radian, this method returns the corresponding angle in the range [-pi; pi] +float HingeJoint::computeNormalizedAngle(float angle) { + + // Convert it into the range [-2*pi; 2*pi] + angle = fmod(angle, PITIMES2); + + // Convert it into the range [-pi; pi] + if (angle < -PI) { + return angle + PITIMES2; + } + else if (angle > PI) { + return angle - PITIMES2; + } + else { + return angle; + } +} + +// 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. +float HingeJoint::computeCorrespondingAngleNearLimits(float inputAngle, float lowerLimitAngle, + float upperLimitAngle) { + if (upperLimitAngle <= lowerLimitAngle) { + return inputAngle; + } + else if (inputAngle > upperLimitAngle) { + float diffToUpperLimit = fabs(computeNormalizedAngle(inputAngle - upperLimitAngle)); + float diffToLowerLimit = fabs(computeNormalizedAngle(inputAngle - lowerLimitAngle)); + return (diffToUpperLimit > diffToLowerLimit) ? (inputAngle - PITIMES2) : inputAngle; + } + else if (inputAngle < lowerLimitAngle) { + float diffToUpperLimit = fabs(computeNormalizedAngle(upperLimitAngle - inputAngle)); + float diffToLowerLimit = fabs(computeNormalizedAngle(lowerLimitAngle - inputAngle)); + return (diffToUpperLimit > diffToLowerLimit) ? inputAngle : (inputAngle + PITIMES2); + } + else { + return inputAngle; + } +} + +// Compute the current angle around the hinge axis +float HingeJoint::computeCurrentHingeAngle( etk::Quaternion orientationBody1, + etk::Quaternion orientationBody2) { + + float hingeAngle; + + // Compute the current orientation difference between the two bodies + etk::Quaternion currentOrientationDiff = orientationBody2 * orientationBody1.getInverse(); + currentOrientationDiff.normalize(); + + // Compute the relative rotation considering the initial orientation difference + etk::Quaternion relativeRotation = currentOrientationDiff * 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). + float cosHalfAngle = relativeRotation.w(); + float sinHalfAngleAbs = relativeRotation.getVectorV().length(); + + // Compute the dot product of the relative rotation axis and the hinge axis + float dotProduct = relativeRotation.getVectorV().dot(mA1); + + // If the relative rotation axis and the hinge axis are pointing the same direction + if (dotProduct >= 0.0f) { + hingeAngle = float(2.0) * etk::atan2(sinHalfAngleAbs, cosHalfAngle); + } + else { + hingeAngle = float(2.0) * etk::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); +} + + +// Return true if the limits of the joint are enabled +/** + * @return True if the limits of the joint are enabled and false otherwise + */ +boolean HingeJoint::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 + */ +boolean HingeJoint::isMotorEnabled() { + return this.isMotorEnabled; +} + +// Return the minimum angle limit +/** + * @return The minimum limit angle of the joint (in radian) + */ +float HingeJoint::getMinAngleLimit() { + return this.lowerLimit; +} + +// Return the maximum angle limit +/** + * @return The maximum limit angle of the joint (in radian) + */ +float HingeJoint::getMaxAngleLimit() { + return this.upperLimit; +} + +// Return the motor speed +/** + * @return The current speed of the joint motor (in radian per second) + */ +float HingeJoint::getMotorSpeed() { + return this.motorSpeed; +} + +// Return the maximum motor torque +/** + * @return The maximum torque of the joint motor (in Newtons) + */ +float HingeJoint::getMaxMotorTorque() { + return this.maxMotorTorque; +} + +// 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 + */ +float HingeJoint::getMotorTorque(float timeStep) { + return this.impulseMotor / timeStep; +} + +// Return the number of bytes used by the joint +sizet HingeJoint::getSizeInBytes() { + return sizeof(HingeJoint); +} + + diff --git a/src/org/atriaSoft/ephysics/constraint/HingeJoint.hpp b/src/org/atriaSoft/ephysics/constraint/HingeJoint.hpp new file mode 100644 index 0000000..e80e2af --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/HingeJoint.hpp @@ -0,0 +1,206 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * @brief It is used to gather the information needed to create a hinge joint. + * This structure will be used to create the actual hinge joint. + */ + struct HingeJointInfo : public JointInfo { + public : + vec3 this.anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + vec3 rotationAxisWorld; //!< Hinge rotation axis (in world-space coordinates) + boolean isLimitEnabled; //!< True if the hinge joint limits are enabled + boolean isMotorEnabled; //!< True if the hinge joint motor is enabled + float minAngleLimit; //!< Minimum allowed rotation angle (in radian) if limits are enabled. The angle must be in the range [-2*pi, 0] + float maxAngleLimit; //!< Maximum allowed rotation angle (in radian) if limits are enabled. The angle must be in the range [0, 2*pi] + float motorSpeed; //!< Motor speed (in radian/second) + float maxMotorTorque; //!< Maximum motor torque (in Newtons * meters) that can be applied to reach to desired motor speed + /** + * @brief Constructor without limits and without motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space coordinates + * @param[in] initRotationAxisWorld The initial rotation axis in world-space coordinates + */ + HingeJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initRotationAxisWorld): + JointInfo(rigidBody1, rigidBody2, HINGEJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + rotationAxisWorld(initRotationAxisWorld), + isLimitEnabled(false), + isMotorEnabled(false), + minAngleLimit(-1), + maxAngleLimit(1), + motorSpeed(0), + maxMotorTorque(0) { + + } + /** + * @brief Constructor with limits but without motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space coordinates + * @param[in] initRotationAxisWorld The intial rotation axis in world-space coordinates + * @param[in] initMinAngleLimit The initial minimum limit angle (in radian) + * @param[in] initMaxAngleLimit The initial maximum limit angle (in radian) + */ + HingeJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initRotationAxisWorld, + float initMinAngleLimit, + float initMaxAngleLimit): + JointInfo(rigidBody1, rigidBody2, HINGEJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + rotationAxisWorld(initRotationAxisWorld), + isLimitEnabled(true), + isMotorEnabled(false), + minAngleLimit(initMinAngleLimit), + maxAngleLimit(initMaxAngleLimit), + motorSpeed(0), + maxMotorTorque(0) { + + } + /** + * @brief Constructor with limits and motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space + * @param[in] initRotationAxisWorld The initial rotation axis in world-space + * @param[in] initMinAngleLimit The initial minimum limit angle (in radian) + * @param[in] initMaxAngleLimit The initial maximum limit angle (in radian) + * @param[in] initMotorSpeed The initial motor speed of the joint (in radian per second) + * @param[in] initMaxMotorTorque The initial maximum motor torque (in Newtons) + */ + HingeJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initRotationAxisWorld, + float initMinAngleLimit, + float initMaxAngleLimit, + float initMotorSpeed, + float initMaxMotorTorque): + JointInfo(rigidBody1, rigidBody2, HINGEJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + rotationAxisWorld(initRotationAxisWorld), + isLimitEnabled(true), + isMotorEnabled(false), + minAngleLimit(initMinAngleLimit), + maxAngleLimit(initMaxAngleLimit), + motorSpeed(initMotorSpeed), + maxMotorTorque(initMaxMotorTorque) { + + } + }; + + /** + * @brief 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. + */ + class HingeJoint : public Joint { + private : + static float BETA; //!< Beta value for the bias factor of position correction + vec3 this.localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + vec3 this.localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + vec3 this.hingeLocalAxisBody1; //!< Hinge rotation axis (in local-space coordinates of body 1) + vec3 this.hingeLocalAxisBody2; //!< Hinge rotation axis (in local-space coordiantes of body 2) + etk::Matrix3x3 this.i1; //!< Inertia tensor of body 1 (in world-space coordinates) + etk::Matrix3x3 this.i2; //!< Inertia tensor of body 2 (in world-space coordinates) + vec3 mA1; //!< Hinge rotation axis (in world-space coordinates) computed from body 1 + vec3 this.r1World; //!< Vector from center of body 2 to anchor point in world-space + vec3 this.r2World; //!< Vector from center of body 2 to anchor point in world-space + vec3 this.b2CrossA1; //!< Cross product of vector b2 and a1 + vec3 this.c2CrossA1; //!< Cross product of vector c2 and a1; + vec3 this.impulseTranslation; //!< Impulse for the 3 translation raints + vec2 this.impulseRotation; //!< Impulse for the 2 rotation raints + float this.impulseLowerLimit; //!< Accumulated impulse for the lower limit raint + float this.impulseUpperLimit; //!< Accumulated impulse for the upper limit raint + float this.impulseMotor; //!< Accumulated impulse for the motor raint; + etk::Matrix3x3 this.inverseMassMatrixTranslation; //!< Inverse mass matrix K=JM^-1J^t for the 3 translation raints + etk::Matrix2x2 this.inverseMassMatrixRotation; //!< Inverse mass matrix K=JM^-1J^t for the 2 rotation raints + float this.inverseMassMatrixLimitMotor; //!< Inverse of mass matrix K=JM^-1J^t for the limits and motor raints (1x1 matrix) + float this.inverseMassMatrixMotor; //!< Inverse of mass matrix K=JM^-1J^t for the motor + vec3 this.bTranslation; //!< Bias vector for the error correction for the translation raints + vec2 this.bRotation; //!< Bias vector for the error correction for the rotation raints + float this.bLowerLimit; //!< Bias of the lower limit raint + float this.bUpperLimit; //!< Bias of the upper limit raint + etk::Quaternion this.initOrientationDifferenceInv; //!< Inverse of the initial orientation difference between the bodies + boolean this.isLimitEnabled; //!< True if the joint limits are enabled + boolean this.isMotorEnabled; //!< True if the motor of the joint in enabled + float this.lowerLimit; //!< Lower limit (minimum allowed rotation angle in radian) + float this.upperLimit; //!< Upper limit (maximum translation distance) + boolean this.isLowerLimitViolated; //!< True if the lower limit is violated + boolean this.isUpperLimitViolated; //!< True if the upper limit is violated + float this.motorSpeed; //!< Motor speed (in rad/s) + float this.maxMotorTorque; //!< Maximum motor torque (in Newtons) that can be applied to reach to desired motor speed + /// Private copy-ructor + HingeJoint( HingeJoint raint); + /// Private assignment operator + HingeJoint operator=( HingeJoint raint); + /// Reset the limits + void resetLimits(); + /// Given an angle in radian, this method returns the corresponding + /// angle in the range [-pi; pi] + float computeNormalizedAngle(float angle) ; + /// 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. + float computeCorrespondingAngleNearLimits(float inputAngle, + float lowerLimitAngle, + float upperLimitAngle) ; + /// Compute the current angle around the hinge axis + float computeCurrentHingeAngle( etk::Quaternion orientationBody1, etk::Quaternion orientationBody2); + sizet getSizeInBytes() override; + void initBeforeSolve( ConstraintSolverData raintSolverData) override; + void warmstart( ConstraintSolverData raintSolverData) override; + void solveVelocityConstraint( ConstraintSolverData raintSolverData) override; + void solvePositionConstraint( ConstraintSolverData raintSolverData) override; + public : + /// Constructor + HingeJoint( HingeJointInfo jointInfo); + /// Destructor + virtual ~HingeJoint(); + /// Return true if the limits or the joint are enabled + boolean isLimitEnabled() ; + /// Return true if the motor of the joint is enabled + boolean isMotorEnabled() ; + /// Enable/Disable the limits of the joint + void enableLimit(boolean isLimitEnabled); + /// Enable/Disable the motor of the joint + void enableMotor(boolean isMotorEnabled); + /// Return the minimum angle limit + float getMinAngleLimit() ; + /// Set the minimum angle limit + void setMinAngleLimit(float lowerLimit); + /// Return the maximum angle limit + float getMaxAngleLimit() ; + /// Set the maximum angle limit + void setMaxAngleLimit(float upperLimit); + /// Return the motor speed + float getMotorSpeed() ; + /// Set the motor speed + void setMotorSpeed(float motorSpeed); + /// Return the maximum motor torque + float getMaxMotorTorque() ; + /// Set the maximum motor torque + void setMaxMotorTorque(float maxMotorTorque); + /// Return the intensity of the current torque applied for the joint motor + float getMotorTorque(float timeStep) ; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/constraint/Joint.cpp b/src/org/atriaSoft/ephysics/constraint/Joint.cpp new file mode 100644 index 0000000..188bcb7 --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/Joint.cpp @@ -0,0 +1,71 @@ +/** @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) + */ +#include + +using namespace ephysics; + +Joint::Joint( 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); +} + +Joint::~Joint() { + +} + +// Return the reference to the body 1 +/** + * @return The first body involved in the joint + */ +RigidBody* Joint::getBody1() { + return this.body1; +} + +// Return the reference to the body 2 +/** + * @return The second body involved in the joint + */ +RigidBody* Joint::getBody2() { + return this.body2; +} + +// Return true if the joint is active +/** + * @return True if the joint is active + */ +boolean Joint::isActive() { + return (this.body1->isActive() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.body2->isActive()); +} + +// Return the type of the joint +/** + * @return The type of the joint + */ +JointType Joint::getType() { + return this.type; +} + +// 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 + */ +boolean Joint::isCollisionEnabled() { + return this.isCollisionEnabled; +} + +// Return true if the joint has already been added into an island +boolean Joint::isAlreadyInIsland() { + return this.isAlreadyInIsland; +} + diff --git a/src/org/atriaSoft/ephysics/constraint/Joint.hpp b/src/org/atriaSoft/ephysics/constraint/Joint.hpp new file mode 100644 index 0000000..4e78c1c --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/Joint.hpp @@ -0,0 +1,124 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + + /// Enumeration for the type of a raint + enum JointType {BALLSOCKETJOINT, SLIDERJOINT, HINGEJOINT, FIXEDJOINT}; + + struct ConstraintSolverData; + class Joint; + + /** + * @brief It represents a single element of a linked list of joints + */ + struct JointListElement { + public: + Joint* joint; //!< Pointer to the actual joint + JointListElement* next; //!< Next element of the list + /** + * @breif Constructor + */ + JointListElement(Joint* initJoint, + JointListElement* initNext): + joint(initJoint), + next(initNext) { + + } + }; + + /** + * @brief It is used to gather the information needed to create a joint. + */ + struct JointInfo { + public : + RigidBody* body1; //!< First rigid body of the joint + RigidBody* body2; //!< Second rigid body of the joint + JointType type; //!< Type of the joint + JointsPositionCorrectionTechnique positionCorrectionTechnique; //!< Position correction technique used for the raint (used for joints). By default, the BAUMGARTE technique is used + boolean isCollisionEnabled; //!< True if the two bodies of the joint are allowed to collide with each other + /// Constructor + JointInfo(JointType raintType): + body1(null), + body2(null), + type(raintType), + positionCorrectionTechnique(NONLINEARGAUSSSEIDEL), + isCollisionEnabled(true) { + + } + /// Constructor + JointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + JointType raintType): + body1(rigidBody1), + body2(rigidBody2), + type(raintType), + positionCorrectionTechnique(NONLINEARGAUSSSEIDEL), + isCollisionEnabled(true) { + + } + /// Destructor + virtual ~JointInfo() = default; + }; + + /** + * @brief It represents a joint between two bodies. + */ + class Joint { + protected : + RigidBody* this.body1; //!< Pointer to the first body of the joint + RigidBody* this.body2; //!< Pointer to the second body of the joint + JointType this.type; //!< Type of the joint + int this.indexBody1; //!< Body 1 index in the velocity array to solve the raint + int this.indexBody2; //!< Body 2 index in the velocity array to solve the raint + JointsPositionCorrectionTechnique this.positionCorrectionTechnique; //!< Position correction technique used for the raint (used for joints) + boolean this.isCollisionEnabled; //!< True if the two bodies of the raint are allowed to collide with each other + boolean this.isAlreadyInIsland; //!< True if the joint has already been added into an island + /// Private copy-ructor + Joint( Joint raint); + /// Private assignment operator + Joint operator=( Joint raint); + /// Return true if the joint has already been added into an island + boolean isAlreadyInIsland() ; + /// Return the number of bytes used by the joint + virtual sizet getSizeInBytes() = 0; + /// Initialize before solving the joint + virtual void initBeforeSolve( ConstraintSolverData raintSolverData) = 0; + /// Warm start the joint (apply the previous impulse at the beginning of the step) + virtual void warmstart( ConstraintSolverData raintSolverData) = 0; + /// Solve the velocity raint + virtual void solveVelocityConstraint( ConstraintSolverData raintSolverData) = 0; + /// Solve the position raint + virtual void solvePositionConstraint( ConstraintSolverData raintSolverData) = 0; + public : + /// Constructor + Joint( JointInfo jointInfo); + /// Destructor + virtual ~Joint(); + /// Return the reference to the body 1 + RigidBody* getBody1() ; + /// Return the reference to the body 2 + RigidBody* getBody2() ; + /// Return true if the raint is active + boolean isActive() ; + /// Return the type of the raint + JointType getType() ; + /// Return true if the collision between the two bodies of the joint is enabled + boolean isCollisionEnabled() ; + friend class DynamicsWorld; + friend class Island; + friend class ConstraintSolver; + }; + +} diff --git a/src/org/atriaSoft/ephysics/constraint/SliderJoint.cpp b/src/org/atriaSoft/ephysics/constraint/SliderJoint.cpp new file mode 100644 index 0000000..3dcb6db --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/SliderJoint.cpp @@ -0,0 +1,840 @@ +/** @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) + */ +#include + +using namespace ephysics; + +// Static variables definition + float SliderJoint::BETA = float(0.2); + +// Constructor +SliderJoint::SliderJoint( SliderJointInfo jointInfo) + : Joint(jointInfo), this.impulseTranslation(0, 0), this.impulseRotation(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.0); + assert(this.lowerLimit <= 0.0); + assert(this.maxMotorForce >= 0.0); + + // Compute the local-space anchor point for each body + etk::Transform3D transform1 = this.body1->getTransform(); + etk::Transform3D transform2 = this.body2->getTransform(); + this.localAnchorPointBody1 = transform1.getInverse() * jointInfo.this.anchorPointWorldSpace; + this.localAnchorPointBody2 = transform2.getInverse() * jointInfo.this.anchorPointWorldSpace; + + // Compute the inverse of the initial orientation difference between the two bodies + this.initOrientationDifferenceInv = transform2.getOrientation() * + transform1.getOrientation().getInverse(); + this.initOrientationDifferenceInv.normalize(); + this.initOrientationDifferenceInv.inverse(); + + // Compute the slider axis in local-space of body 1 + this.sliderAxisBody1 = this.body1->getTransform().getOrientation().getInverse() * + jointInfo.sliderAxisWorldSpace; + this.sliderAxisBody1.normalize(); +} + +// Destructor +SliderJoint::~SliderJoint() { + +} + +// Initialize before solving the raint +void SliderJoint::initBeforeSolve( ConstraintSolverData raintSolverData) { + + // Initialize the bodies index in the veloc ity array + this.indexBody1 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body1)->second; + this.indexBody2 = raintSolverData.mapBodyToConstrainedVelocityIndex.find(this.body2)->second; + + // Get the bodies positions and orientations + vec3 x1 = this.body1->this.centerOfMassWorld; + vec3 x2 = this.body2->this.centerOfMassWorld; + etk::Quaternion orientationBody1 = this.body1->getTransform().getOrientation(); + etk::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 * this.localAnchorPointBody1; + this.R2 = orientationBody2 * this.localAnchorPointBody2; + + // Compute the vector u (difference between anchor points) + vec3 u = x2 + this.R2 - x1 - this.R1; + + // Compute the two orthogonal vectors to the slider axis in world-space + this.sliderAxisWorld = orientationBody1 * 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 + float uDotSliderAxis = u.dot(this.sliderAxisWorld); + float lowerLimitError = uDotSliderAxis - this.lowerLimit; + float upperLimitError = this.upperLimit - uDotSliderAxis; + boolean oldIsLowerLimitViolated = this.isLowerLimitViolated; + this.isLowerLimitViolated = lowerLimitError <= 0; + if (this.isLowerLimitViolated != oldIsLowerLimitViolated) { + this.impulseLowerLimit = 0.0; + } + boolean oldIsUpperLimitViolated = this.isUpperLimitViolated; + this.isUpperLimitViolated = upperLimitError <= 0; + if (this.isUpperLimitViolated != oldIsUpperLimitViolated) { + this.impulseUpperLimit = 0.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); + vec3 r1PlusU = this.R1 + 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) + float sumInverseMass = this.body1->this.massInverse + this.body2->this.massInverse; + vec3 I1R1PlusUCrossN1 = this.i1 * this.R1PlusUCrossN1; + vec3 I1R1PlusUCrossN2 = this.i1 * this.R1PlusUCrossN2; + vec3 I2R2CrossN1 = this.i2 * this.R2CrossN1; + vec3 I2R2CrossN2 = this.i2 * this.R2CrossN2; + float el11 = sumInverseMass + this.R1PlusUCrossN1.dot(I1R1PlusUCrossN1) + + this.R2CrossN1.dot(I2R2CrossN1); + float el12 = this.R1PlusUCrossN1.dot(I1R1PlusUCrossN2) + + this.R2CrossN1.dot(I2R2CrossN2); + float el21 = this.R1PlusUCrossN2.dot(I1R1PlusUCrossN1) + + this.R2CrossN2.dot(I2R2CrossN1); + float el22 = sumInverseMass + this.R1PlusUCrossN2.dot(I1R1PlusUCrossN2) + + this.R2CrossN2.dot(I2R2CrossN2); + etk::Matrix2x2 matrixKTranslation(el11, el12, el21, el22); + this.inverseMassMatrixTranslationConstraint.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslationConstraint = matrixKTranslation.getInverse(); + } + + // Compute the bias "b" of the translation raint + this.bTranslation.setZero(); + float biasFactor = (BETA / raintSolverData.timeStep); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bTranslation.setX(u.dot(this.N1)); + this.bTranslation.setY(u.dot(this.N2)); + this.bTranslation *= biasFactor; + } + + // Compute the inverse of the mass matrix K=JM^-1J^t for the 3 rotation + // contraints (3x3 matrix) + this.inverseMassMatrixRotationConstraint = this.i1 + this.i2; + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotationConstraint = this.inverseMassMatrixRotationConstraint.getInverse(); + } + + // Compute the bias "b" of the rotation raint + this.bRotation.setZero(); + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + etk::Quaternion currentOrientationDifference = orientationBody2 * orientationBody1.getInverse(); + currentOrientationDifference.normalize(); + etk::Quaternion qError = currentOrientationDifference * this.initOrientationDifferenceInv; + this.bRotation = biasFactor * float(2.0) * qError.getVectorV(); + } + + // If the limits are enabled + if (this.isLimitEnabled hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (this.isLowerLimitViolated || this.isUpperLimitViolated)) { + + // Compute the inverse of the mass matrix K=JM^-1J^t for the limits (1x1 matrix) + this.inverseMassMatrixLimit = this.body1->this.massInverse + this.body2->this.massInverse + + this.R1PlusUCrossSliderAxis.dot(this.i1 * this.R1PlusUCrossSliderAxis) + + this.R2CrossSliderAxis.dot(this.i2 * this.R2CrossSliderAxis); + this.inverseMassMatrixLimit = (this.inverseMassMatrixLimit > 0.0) ? + 1.0f / this.inverseMassMatrixLimit : 0.0f; + + // Compute the bias "b" of the lower limit raint + this.bLowerLimit = 0.0; + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + this.bLowerLimit = biasFactor * lowerLimitError; + } + + // Compute the bias "b" of the upper limit raint + this.bUpperLimit = 0.0; + if (this.positionCorrectionTechnique == BAUMGARTEJOINTS) { + 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->this.massInverse + this.body2->this.massInverse; + this.inverseMassMatrixMotor = (this.inverseMassMatrixMotor > 0.0) ? + 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.0; + this.impulseUpperLimit = 0.0; + this.impulseMotor = 0.0; + } +} + +// Warm start the raint (apply the previous impulse at the beginning of the step) +void SliderJoint::warmstart( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + float impulseLimits = this.impulseUpperLimit - this.impulseLowerLimit; + vec3 linearImpulseLimits = impulseLimits * this.sliderAxisWorld; + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + vec3 impulseMotor = this.impulseMotor * this.sliderAxisWorld; + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + vec3 linearImpulseBody1 = -this.N1 * this.impulseTranslation.x() - this.N2 * this.impulseTranslation.y(); + vec3 angularImpulseBody1 = -this.R1PlusUCrossN1 * this.impulseTranslation.x() - + this.R1PlusUCrossN2 * this.impulseTranslation.y(); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 += -this.impulseRotation; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 1 + linearImpulseBody1 += linearImpulseLimits; + angularImpulseBody1 += impulseLimits * this.R1PlusUCrossSliderAxis; + + // Compute the impulse P=J^T * lambda for the motor raint of body 1 + linearImpulseBody1 += impulseMotor; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + vec3 linearImpulseBody2 = this.N1 * this.impulseTranslation.x() + this.N2 * this.impulseTranslation.y(); + vec3 angularImpulseBody2 = this.R2CrossN1 * this.impulseTranslation.x() + + this.R2CrossN2 * this.impulseTranslation.y(); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 2 + angularImpulseBody2 += this.impulseRotation; + + // Compute the impulse P=J^T * lambda for the lower and upper limits raints of body 2 + linearImpulseBody2 += -linearImpulseLimits; + angularImpulseBody2 += -impulseLimits * this.R2CrossSliderAxis; + + // Compute the impulse P=J^T * lambda for the motor raint of body 2 + linearImpulseBody2 += -impulseMotor; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * linearImpulseBody2; + w2 += this.i2 * angularImpulseBody2; +} + +// Solve the velocity raint +void SliderJoint::solveVelocityConstraint( ConstraintSolverData raintSolverData) { + + // Get the velocities + vec3 v1 = raintSolverData.linearVelocities[this.indexBody1]; + vec3 v2 = raintSolverData.linearVelocities[this.indexBody2]; + vec3 w1 = raintSolverData.angularVelocities[this.indexBody1]; + vec3 w2 = raintSolverData.angularVelocities[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.massInverse; + + // --------------- Translation Constraints --------------- // + + // Compute J*v for the 2 translation raints + float el1 = -this.N1.dot(v1) - w1.dot(this.R1PlusUCrossN1) + + this.N1.dot(v2) + w2.dot(this.R2CrossN1); + float el2 = -this.N2.dot(v1) - w1.dot(this.R1PlusUCrossN2) + + this.N2.dot(v2) + w2.dot(this.R2CrossN2); + vec2 JvTranslation(el1, el2); + + // Compute the Lagrange multiplier lambda for the 2 translation raints + vec2 deltaLambda = this.inverseMassMatrixTranslationConstraint * (-JvTranslation -this.bTranslation); + this.impulseTranslation += deltaLambda; + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + vec3 linearImpulseBody1 = -this.N1 * deltaLambda.x() - this.N2 * deltaLambda.y(); + vec3 angularImpulseBody1 = -this.R1PlusUCrossN1 * deltaLambda.x() - + this.R1PlusUCrossN2 * deltaLambda.y(); + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + vec3 linearImpulseBody2 = this.N1 * deltaLambda.x() + this.N2 * deltaLambda.y(); + vec3 angularImpulseBody2 = this.R2CrossN1 * deltaLambda.x() + this.R2CrossN2 * deltaLambda.y(); + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * linearImpulseBody2; + w2 += this.i2 * angularImpulseBody2; + + // --------------- Rotation Constraints --------------- // + + // Compute J*v for the 3 rotation raints + vec3 JvRotation = w2 - w1; + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + vec3 deltaLambda2 = this.inverseMassMatrixRotationConstraint * (-JvRotation - this.bRotation); + this.impulseRotation += deltaLambda2; + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = -deltaLambda2; + + // Apply the impulse to the body to body 1 + w1 += this.i1 * 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 += this.i2 * angularImpulseBody2; + + // --------------- Limits Constraints --------------- // + + if (this.isLimitEnabled) { + + // If the lower limit is violated + if (this.isLowerLimitViolated) { + + // Compute J*v for the lower limit raint + 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); + float lambdaTemp = this.impulseLowerLimit; + this.impulseLowerLimit = etk::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 + vec3 linearImpulseBody1 = -deltaLambdaLower * this.sliderAxisWorld; + vec3 angularImpulseBody1 = -deltaLambdaLower * this.R1PlusUCrossSliderAxis; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + vec3 linearImpulseBody2 = deltaLambdaLower * this.sliderAxisWorld; + vec3 angularImpulseBody2 = deltaLambdaLower * this.R2CrossSliderAxis; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * linearImpulseBody2; + w2 += this.i2 * angularImpulseBody2; + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute J*v for the upper limit raint + 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); + float lambdaTemp = this.impulseUpperLimit; + this.impulseUpperLimit = etk::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 + vec3 linearImpulseBody1 = deltaLambdaUpper * this.sliderAxisWorld; + vec3 angularImpulseBody1 = deltaLambdaUpper * this.R1PlusUCrossSliderAxis; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + w1 += this.i1 * angularImpulseBody1; + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + vec3 linearImpulseBody2 = -deltaLambdaUpper * this.sliderAxisWorld; + vec3 angularImpulseBody2 = -deltaLambdaUpper * this.R2CrossSliderAxis; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * linearImpulseBody2; + w2 += this.i2 * angularImpulseBody2; + } + } + + // --------------- Motor --------------- // + + if (this.isMotorEnabled) { + + // Compute J*v for the motor + float JvMotor = this.sliderAxisWorld.dot(v1) - this.sliderAxisWorld.dot(v2); + + // Compute the Lagrange multiplier lambda for the motor + float maxMotorImpulse = this.maxMotorForce * raintSolverData.timeStep; + float deltaLambdaMotor = this.inverseMassMatrixMotor * (-JvMotor - this.motorSpeed); + float lambdaTemp = this.impulseMotor; + this.impulseMotor = clamp(this.impulseMotor + deltaLambdaMotor, -maxMotorImpulse, maxMotorImpulse); + deltaLambdaMotor = this.impulseMotor - lambdaTemp; + + // Compute the impulse P=J^T * lambda for the motor of body 1 + vec3 linearImpulseBody1 = deltaLambdaMotor * this.sliderAxisWorld; + + // Apply the impulse to the body 1 + v1 += inverseMassBody1 * linearImpulseBody1; + + // Compute the impulse P=J^T * lambda for the motor of body 2 + vec3 linearImpulseBody2 = -deltaLambdaMotor * this.sliderAxisWorld; + + // Apply the impulse to the body 2 + v2 += inverseMassBody2 * linearImpulseBody2; + } +} + +// Solve the position raint (for position error correction) +void SliderJoint::solvePositionConstraint( 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 != NONLINEARGAUSSSEIDEL) return; + + // Get the bodies positions and orientations + vec3 x1 = raintSolverData.positions[this.indexBody1]; + vec3 x2 = raintSolverData.positions[this.indexBody2]; + etk::Quaternion q1 = raintSolverData.orientations[this.indexBody1]; + etk::Quaternion q2 = raintSolverData.orientations[this.indexBody2]; + + // Get the inverse mass and inverse inertia tensors of the bodies + float inverseMassBody1 = this.body1->this.massInverse; + float inverseMassBody2 = this.body2->this.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 * this.localAnchorPointBody1; + this.R2 = q2 * this.localAnchorPointBody2; + + // Compute the vector u (difference between anchor points) + vec3 u = x2 + this.R2 - x1 - this.R1; + + // Compute the two orthogonal vectors to the slider axis in world-space + this.sliderAxisWorld = q1 * 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 + float uDotSliderAxis = u.dot(this.sliderAxisWorld); + float lowerLimitError = uDotSliderAxis - this.lowerLimit; + 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); + vec3 r1PlusU = this.R1 + 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) + float sumInverseMass = this.body1->this.massInverse + this.body2->this.massInverse; + vec3 I1R1PlusUCrossN1 = this.i1 * this.R1PlusUCrossN1; + vec3 I1R1PlusUCrossN2 = this.i1 * this.R1PlusUCrossN2; + vec3 I2R2CrossN1 = this.i2 * this.R2CrossN1; + vec3 I2R2CrossN2 = this.i2 * this.R2CrossN2; + float el11 = sumInverseMass + this.R1PlusUCrossN1.dot(I1R1PlusUCrossN1) + + this.R2CrossN1.dot(I2R2CrossN1); + float el12 = this.R1PlusUCrossN1.dot(I1R1PlusUCrossN2) + + this.R2CrossN1.dot(I2R2CrossN2); + float el21 = this.R1PlusUCrossN2.dot(I1R1PlusUCrossN1) + + this.R2CrossN2.dot(I2R2CrossN1); + float el22 = sumInverseMass + this.R1PlusUCrossN2.dot(I1R1PlusUCrossN2) + + this.R2CrossN2.dot(I2R2CrossN2); + etk::Matrix2x2 matrixKTranslation(el11, el12, el21, el22); + this.inverseMassMatrixTranslationConstraint.setZero(); + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixTranslationConstraint = matrixKTranslation.getInverse(); + } + + // Compute the position error for the 2 translation raints + vec2 translationError(u.dot(this.N1), u.dot(this.N2)); + + // Compute the Lagrange multiplier lambda for the 2 translation raints + vec2 lambdaTranslation = this.inverseMassMatrixTranslationConstraint * (-translationError); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 1 + vec3 linearImpulseBody1 = -this.N1 * lambdaTranslation.x() - this.N2 * lambdaTranslation.y(); + vec3 angularImpulseBody1 = -this.R1PlusUCrossN1 * lambdaTranslation.x() - + this.R1PlusUCrossN2 * lambdaTranslation.y(); + + // Apply the impulse to the body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the 2 translation raints of body 2 + vec3 linearImpulseBody2 = this.N1 * lambdaTranslation.x() + this.N2 * lambdaTranslation.y(); + vec3 angularImpulseBody2 = this.R2CrossN1 * lambdaTranslation.x() + + this.R2CrossN2 * lambdaTranslation.y(); + + // Apply the impulse to the body 2 + vec3 v2 = inverseMassBody2 * linearImpulseBody2; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 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 + this.i2; + if (this.body1->getType() == DYNAMIC || this.body2->getType() == DYNAMIC) { + this.inverseMassMatrixRotationConstraint = this.inverseMassMatrixRotationConstraint.getInverse(); + } + + // Compute the position error for the 3 rotation raints + etk::Quaternion currentOrientationDifference = q2 * q1.getInverse(); + currentOrientationDifference.normalize(); + etk::Quaternion qError = currentOrientationDifference * this.initOrientationDifferenceInv; + vec3 errorRotation = float(2.0) * qError.getVectorV(); + + // Compute the Lagrange multiplier lambda for the 3 rotation raints + vec3 lambdaRotation = this.inverseMassMatrixRotationConstraint * (-errorRotation); + + // Compute the impulse P=J^T * lambda for the 3 rotation raints of body 1 + angularImpulseBody1 = -lambdaRotation; + + // Apply the impulse to the body 1 + w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + q1 += etk::Quaternion(0, w1) * q1 * 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 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + q2 += etk::Quaternion(0, w2) * q2 * 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->this.massInverse + this.body2->this.massInverse + + this.R1PlusUCrossSliderAxis.dot(this.i1 * this.R1PlusUCrossSliderAxis) + + this.R2CrossSliderAxis.dot(this.i2 * this.R2CrossSliderAxis); + this.inverseMassMatrixLimit = (this.inverseMassMatrixLimit > 0.0) ? + 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 + float lambdaLowerLimit = this.inverseMassMatrixLimit * (-lowerLimitError); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 1 + vec3 linearImpulseBody1 = -lambdaLowerLimit * this.sliderAxisWorld; + vec3 angularImpulseBody1 = -lambdaLowerLimit * this.R1PlusUCrossSliderAxis; + + // Apply the impulse to the body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the lower limit raint of body 2 + vec3 linearImpulseBody2 = lambdaLowerLimit * this.sliderAxisWorld; + vec3 angularImpulseBody2 = lambdaLowerLimit * this.R2CrossSliderAxis; + + // Apply the impulse to the body 2 + vec3 v2 = inverseMassBody2 * linearImpulseBody2; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); + } + + // If the upper limit is violated + if (this.isUpperLimitViolated) { + + // Compute the Lagrange multiplier lambda for the upper limit raint + float lambdaUpperLimit = this.inverseMassMatrixLimit * (-upperLimitError); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 1 + vec3 linearImpulseBody1 = lambdaUpperLimit * this.sliderAxisWorld; + vec3 angularImpulseBody1 = lambdaUpperLimit * this.R1PlusUCrossSliderAxis; + + // Apply the impulse to the body 1 + vec3 v1 = inverseMassBody1 * linearImpulseBody1; + vec3 w1 = this.i1 * angularImpulseBody1; + + // Update the body position/orientation of body 1 + x1 += v1; + q1 += etk::Quaternion(0, w1) * q1 * 0.5f; + q1.normalize(); + + // Compute the impulse P=J^T * lambda for the upper limit raint of body 2 + vec3 linearImpulseBody2 = -lambdaUpperLimit * this.sliderAxisWorld; + vec3 angularImpulseBody2 = -lambdaUpperLimit * this.R2CrossSliderAxis; + + // Apply the impulse to the body 2 + vec3 v2 = inverseMassBody2 * linearImpulseBody2; + vec3 w2 = this.i2 * angularImpulseBody2; + + // Update the body position/orientation of body 2 + x2 += v2; + q2 += etk::Quaternion(0, w2) * q2 * 0.5f; + q2.normalize(); + } + } +} + +// Enable/Disable the limits of the joint +/** + * @param isLimitEnabled True if you want to enable the joint limits and false + * otherwise + */ +void SliderJoint::enableLimit(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 + */ +void SliderJoint::enableMotor(boolean isMotorEnabled) { + + this.isMotorEnabled = isMotorEnabled; + this.impulseMotor = 0.0; + + // Wake up the two bodies of the joint + this.body1->setIsSleeping(false); + this.body2->setIsSleeping(false); +} + +// Return the current translation value of the joint +/** + * @return The current translation distance of the joint (in meters) + */ +float SliderJoint::getTranslation() { + + // TODO : Check if we need to compare rigid body position or center of mass here + + // Get the bodies positions and orientations + vec3 x1 = this.body1->getTransform().getPosition(); + vec3 x2 = this.body2->getTransform().getPosition(); + etk::Quaternion q1 = this.body1->getTransform().getOrientation(); + etk::Quaternion q2 = this.body2->getTransform().getOrientation(); + + // Compute the two anchor points in world-space coordinates + vec3 anchorBody1 = x1 + q1 * this.localAnchorPointBody1; + vec3 anchorBody2 = x2 + q2 * this.localAnchorPointBody2; + + // Compute the vector u (difference between anchor points) + vec3 u = anchorBody2 - anchorBody1; + + // Compute the slider axis in world-space + vec3 sliderAxisWorld = q1 * this.sliderAxisBody1; + sliderAxisWorld.normalize(); + + // Compute and return the translation value + return u.dot(sliderAxisWorld); +} + +// Set the minimum translation limit +/** + * @param lowerLimit The minimum translation limit of the joint (in meters) + */ +void SliderJoint::setMinTranslationLimit(float lowerLimit) { + + assert(lowerLimit <= this.upperLimit); + + if (lowerLimit != this.lowerLimit) { + + this.lowerLimit = lowerLimit; + + // Reset the limits + resetLimits(); + } +} + +// Set the maximum translation limit +/** + * @param lowerLimit The maximum translation limit of the joint (in meters) + */ +void SliderJoint::setMaxTranslationLimit(float upperLimit) { + + assert(this.lowerLimit <= upperLimit); + + if (upperLimit != this.upperLimit) { + + this.upperLimit = upperLimit; + + // Reset the limits + resetLimits(); + } +} + +// Reset the limits +void SliderJoint::resetLimits() { + + // Reset the accumulated impulses for the limits + this.impulseLowerLimit = 0.0; + this.impulseUpperLimit = 0.0; + + // Wake up the two bodies of the joint + this.body1->setIsSleeping(false); + this.body2->setIsSleeping(false); +} + +// Set the motor speed +/** + * @param motorSpeed The speed of the joint motor (in meters per second) + */ +void SliderJoint::setMotorSpeed(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); + } +} + +// Set the maximum motor force +/** + * @param maxMotorForce The maximum force of the joint motor (in Newton x meters) + */ +void SliderJoint::setMaxMotorForce(float maxMotorForce) { + + if (maxMotorForce != this.maxMotorForce) { + + assert(this.maxMotorForce >= 0.0); + this.maxMotorForce = maxMotorForce; + + // Wake up the two bodies of the joint + this.body1->setIsSleeping(false); + this.body2->setIsSleeping(false); + } +} + +// Return true if the limits or the joint are enabled +/** + * @return True if the joint limits are enabled + */ +boolean SliderJoint::isLimitEnabled() { + return this.isLimitEnabled; +} + +// Return true if the motor of the joint is enabled +/** + * @return True if the joint motor is enabled + */ +boolean SliderJoint::isMotorEnabled() { + return this.isMotorEnabled; +} + +// Return the minimum translation limit +/** + * @return The minimum translation limit of the joint (in meters) + */ +float SliderJoint::getMinTranslationLimit() { + return this.lowerLimit; +} + +// Return the maximum translation limit +/** + * @return The maximum translation limit of the joint (in meters) + */ +float SliderJoint::getMaxTranslationLimit() { + return this.upperLimit; +} + +// Return the motor speed +/** + * @return The current motor speed of the joint (in meters per second) + */ +float SliderJoint::getMotorSpeed() { + return this.motorSpeed; +} + +// Return the maximum motor force +/** + * @return The maximum force of the joint motor (in Newton x meters) + */ +float SliderJoint::getMaxMotorForce() { + return this.maxMotorForce; +} + +// 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) + */ +float SliderJoint::getMotorForce(float timeStep) { + return this.impulseMotor / timeStep; +} + +// Return the number of bytes used by the joint +sizet SliderJoint::getSizeInBytes() { + return sizeof(SliderJoint); +} + diff --git a/src/org/atriaSoft/ephysics/constraint/SliderJoint.hpp b/src/org/atriaSoft/ephysics/constraint/SliderJoint.hpp new file mode 100644 index 0000000..4bc902d --- /dev/null +++ b/src/org/atriaSoft/ephysics/constraint/SliderJoint.hpp @@ -0,0 +1,203 @@ +/** @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) + */ +#pragma once + +#include +#include + +namespace ephysics { + /** + * 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. + */ + struct SliderJointInfo : public JointInfo { + + public : + vec3 this.anchorPointWorldSpace; //!< Anchor point (in world-space coordinates) + vec3 sliderAxisWorldSpace; //!< Slider axis (in world-space coordinates) + boolean isLimitEnabled; //!< True if the slider limits are enabled + boolean isMotorEnabled; //!< True if the slider motor is enabled + float minTranslationLimit; //!< Mininum allowed translation if limits are enabled + float maxTranslationLimit; //!< Maximum allowed translation if limits are enabled + float motorSpeed; //!< Motor speed + float maxMotorForce; //!< Maximum motor force (in Newtons) that can be applied to reach to desired motor speed + /** + * @brief Constructor without limits and without motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space + * @param[in] initSliderAxisWorldSpace The initial slider axis in world-space + */ + SliderJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initSliderAxisWorldSpace): + JointInfo(rigidBody1, rigidBody2, SLIDERJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + sliderAxisWorldSpace(initSliderAxisWorldSpace), + isLimitEnabled(false), + isMotorEnabled(false), + minTranslationLimit(-1.0), + maxTranslationLimit(1.0), + motorSpeed(0), + maxMotorForce(0) { + + } + /** + * @brief Constructor with limits and no motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space + * @param[in] initSliderAxisWorldSpace The initial slider axis in world-space + * @param[in] initMinTranslationLimit The initial minimum translation limit (in meters) + * @param[in] initMaxTranslationLimit The initial maximum translation limit (in meters) + */ + SliderJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initSliderAxisWorldSpace, + float initMinTranslationLimit, + float initMaxTranslationLimit): + JointInfo(rigidBody1, rigidBody2, SLIDERJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + sliderAxisWorldSpace(initSliderAxisWorldSpace), + isLimitEnabled(true), + isMotorEnabled(false), + minTranslationLimit(initMinTranslationLimit), + maxTranslationLimit(initMaxTranslationLimit), + motorSpeed(0), + maxMotorForce(0) { + + } + /** + * @brief Constructor with limits and motor + * @param[in] rigidBody1 The first body of the joint + * @param[in] rigidBody2 The second body of the joint + * @param[in] initAnchorPointWorldSpace The initial anchor point in world-space + * @param[in] initSliderAxisWorldSpace The initial slider axis in world-space + * @param[in] initMinTranslationLimit The initial minimum translation limit (in meters) + * @param[in] initMaxTranslationLimit The initial maximum translation limit (in meters) + * @param[in] initMotorSpeed The initial speed of the joint motor (in meters per second) + * @param[in] initMaxMotorForce The initial maximum motor force of the joint (in Newtons x meters) + */ + SliderJointInfo(RigidBody* rigidBody1, + RigidBody* rigidBody2, + vec3 initAnchorPointWorldSpace, + vec3 initSliderAxisWorldSpace, + float initMinTranslationLimit, + float initMaxTranslationLimit, + float initMotorSpeed, + float initMaxMotorForce): + JointInfo(rigidBody1, rigidBody2, SLIDERJOINT), + this.anchorPointWorldSpace(initAnchorPointWorldSpace), + sliderAxisWorldSpace(initSliderAxisWorldSpace), + isLimitEnabled(true), + isMotorEnabled(true), + minTranslationLimit(initMinTranslationLimit), + maxTranslationLimit(initMaxTranslationLimit), + motorSpeed(initMotorSpeed), + maxMotorForce(initMaxMotorForce) { + + } + }; + + /** + * @brief This class represents a slider joint. This joint has a one degree of freedom. + * It only allows relative translation of the bodies along a single direction and no + * rotation. + */ + class SliderJoint: public Joint { + private: + static float BETA; //!< Beta value for the position correction bias factor + vec3 this.localAnchorPointBody1; //!< Anchor point of body 1 (in local-space coordinates of body 1) + vec3 this.localAnchorPointBody2; //!< Anchor point of body 2 (in local-space coordinates of body 2) + vec3 this.sliderAxisBody1; //!< Slider axis (in local-space coordinates of body 1) + etk::Matrix3x3 this.i1; //!< Inertia tensor of body 1 (in world-space coordinates) + etk::Matrix3x3 this.i2; //!< Inertia tensor of body 2 (in world-space coordinates) + etk::Quaternion this.initOrientationDifferenceInv; //!< Inverse of the initial orientation difference between the two bodies + vec3 this.N1; //!< First vector orthogonal to the slider axis local-space of body 1 + vec3 this.N2; //!< Second vector orthogonal to the slider axis and this.N1 in local-space of body 1 + vec3 this.R1; //!< Vector r1 in world-space coordinates + vec3 this.R2; //!< Vector r2 in world-space coordinates + vec3 this.R2CrossN1; //!< Cross product of r2 and n1 + vec3 this.R2CrossN2; //!< Cross product of r2 and n2 + vec3 this.R2CrossSliderAxis; //!< Cross product of r2 and the slider axis + vec3 this.R1PlusUCrossN1; //!< Cross product of vector (r1 + u) and n1 + vec3 this.R1PlusUCrossN2; //!< Cross product of vector (r1 + u) and n2 + vec3 this.R1PlusUCrossSliderAxis; //!< Cross product of vector (r1 + u) and the slider axis + vec2 this.bTranslation; //!< Bias of the 2 translation raints + vec3 this.bRotation; //!< Bias of the 3 rotation raints + float this.bLowerLimit; //!< Bias of the lower limit raint + float this.bUpperLimit; //!< Bias of the upper limit raint + etk::Matrix2x2 this.inverseMassMatrixTranslationConstraint; //!< Inverse of mass matrix K=JM^-1J^t for the translation raint (2x2 matrix) + etk::Matrix3x3 this.inverseMassMatrixRotationConstraint; //!< Inverse of mass matrix K=JM^-1J^t for the rotation raint (3x3 matrix) + float this.inverseMassMatrixLimit; //!< Inverse of mass matrix K=JM^-1J^t for the upper and lower limit raints (1x1 matrix) + float this.inverseMassMatrixMotor; //!< Inverse of mass matrix K=JM^-1J^t for the motor + vec2 this.impulseTranslation; //!< Accumulated impulse for the 2 translation raints + vec3 this.impulseRotation; //!< Accumulated impulse for the 3 rotation raints + float this.impulseLowerLimit; //!< Accumulated impulse for the lower limit raint + float this.impulseUpperLimit; //!< Accumulated impulse for the upper limit raint + float this.impulseMotor; //!< Accumulated impulse for the motor + boolean this.isLimitEnabled; //!< True if the slider limits are enabled + boolean this.isMotorEnabled; //!< True if the motor of the joint in enabled + vec3 this.sliderAxisWorld; //!< Slider axis in world-space coordinates + float this.lowerLimit; //!< Lower limit (minimum translation distance) + float this.upperLimit; //!< Upper limit (maximum translation distance) + boolean this.isLowerLimitViolated; //!< True if the lower limit is violated + boolean this.isUpperLimitViolated; //!< True if the upper limit is violated + float this.motorSpeed; //!< Motor speed (in m/s) + float this.maxMotorForce; //!< Maximum motor force (in Newtons) that can be applied to reach to desired motor speed + /// Private copy-ructor + SliderJoint( SliderJoint raint); + /// Private assignment operator + SliderJoint operator=( SliderJoint raint); + /// Reset the limits + void resetLimits(); + sizet getSizeInBytes() override; + void initBeforeSolve( ConstraintSolverData raintSolverData) override; + void warmstart( ConstraintSolverData raintSolverData) override; + void solveVelocityConstraint( ConstraintSolverData raintSolverData) override; + void solvePositionConstraint( ConstraintSolverData raintSolverData) override; + public : + /// Constructor + SliderJoint( SliderJointInfo jointInfo); + /// Destructor + virtual ~SliderJoint(); + /// Return true if the limits or the joint are enabled + boolean isLimitEnabled() ; + /// Return true if the motor of the joint is enabled + boolean isMotorEnabled() ; + /// Enable/Disable the limits of the joint + void enableLimit(boolean isLimitEnabled); + /// Enable/Disable the motor of the joint + void enableMotor(boolean isMotorEnabled); + /// Return the current translation value of the joint + float getTranslation() ; + /// Return the minimum translation limit + float getMinTranslationLimit() ; + /// Set the minimum translation limit + void setMinTranslationLimit(float lowerLimit); + /// Return the maximum translation limit + float getMaxTranslationLimit() ; + /// Set the maximum translation limit + void setMaxTranslationLimit(float upperLimit); + /// Return the motor speed + float getMotorSpeed() ; + /// Set the motor speed + void setMotorSpeed(float motorSpeed); + /// Return the maximum motor force + float getMaxMotorForce() ; + /// Set the maximum motor force + void setMaxMotorForce(float maxMotorForce); + /// Return the intensity of the current force applied for the joint motor + float getMotorForce(float timeStep) ; + }; + + +} diff --git a/src/org/atriaSoft/ephysics/debug.cpp b/src/org/atriaSoft/ephysics/debug.cpp new file mode 100644 index 0000000..d77ade8 --- /dev/null +++ b/src/org/atriaSoft/ephysics/debug.cpp @@ -0,0 +1,12 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2011, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ + +#include + +int ephysic::getLogId() { + static int gval = elog::registerInstance("ephysic"); + return gval; +} diff --git a/src/org/atriaSoft/ephysics/debug.hpp b/src/org/atriaSoft/ephysics/debug.hpp new file mode 100644 index 0000000..a228ed2 --- /dev/null +++ b/src/org/atriaSoft/ephysics/debug.hpp @@ -0,0 +1,37 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2011, Edouard DUPIN, all right reserved + * @license MPL v2.0 (see license file) + */ +#pragma once + +#include + +namespace ephysic { + int getLogId(); +}; +#define EPHYBASE(info,data) ELOGBASE(ephysic::getLogId(),info,data) + +#define Log.print(data) EPHYBASE(-1, data) +#define Log.critical(data) EPHYBASE(1, data) +#define Log.error(data) EPHYBASE(2, data) +#define EPHYWARNING(data) EPHYBASE(3, data) +#define Log.info(data) EPHYBASE(4, data) +#ifdef DEBUG + #define Log.debug(data) EPHYBASE(5, data) + #define Log.verbose(data) EPHYBASE(6, data) + #define EPHYTODO(data) EPHYBASE(4, "TODO : " << data) +#else + #define Log.debug(data) do { } while(false) + #define Log.verbose(data) do { } while(false) + #define EPHYTODO(data) do { } while(false) +#endif + +#define EPHYASSERT(cond,data) \ + do { \ + if (!(cond)) { \ + Log.critical(data); \ + assert(!#cond); \ + } \ + } while (0) + diff --git a/src/org/atriaSoft/ephysics/engine/CollisionWorld.cpp b/src/org/atriaSoft/ephysics/engine/CollisionWorld.cpp new file mode 100644 index 0000000..f20b636 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/CollisionWorld.cpp @@ -0,0 +1,154 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; +using namespace std; + +CollisionWorld::CollisionWorld() : + this.collisionDetection(this), + this.currentBodyID(0), + this.eventListener(null) { + +} + +CollisionWorld::~CollisionWorld() { + while(this.bodies.size() != 0) { + destroyCollisionBody(this.bodies[0]); + } +} + + +CollisionBody* CollisionWorld::createCollisionBody( etk::Transform3D transform) { + // Get the next available body ID + long bodyID = computeNextAvailableBodyID(); + // Largest index cannot be used (it is used for invalid index) + EPHYASSERT(bodyID < UINT64MAX, "index too big"); + // Create the collision body + CollisionBody* collisionBody = ETKNEW(CollisionBody, transform, *this, bodyID); + EPHYASSERT(collisionBody != null, "empty Body collision"); + // Add the collision body to the world + this.bodies.add(collisionBody); + // Return the pointer to the rigid body + return collisionBody; +} + +void CollisionWorld::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.pushBack(collisionBody->getID()); + // Remove the collision body from the list of bodies + this.bodies.erase(this.bodies.find(collisionBody)); + ETKDELETE(CollisionBody, collisionBody); + collisionBody = null; +} + +long CollisionWorld::computeNextAvailableBodyID() { + // Compute the body ID + long bodyID; + if (!this.freeBodiesIDs.empty()) { + bodyID = this.freeBodiesIDs.back(); + this.freeBodiesIDs.popBack(); + } else { + bodyID = this.currentBodyID; + this.currentBodyID++; + } + return bodyID; +} + +void CollisionWorld::resetContactManifoldListsOfBodies() { + // For each rigid body of the world + for (etk::Set::Iterator it = this.bodies.begin(); it != this.bodies.end(); ++it) { + // Reset the contact manifold list of the body + (*it)->resetContactManifoldsList(); + } +} + +boolean CollisionWorld::testAABBOverlap( CollisionBody* body1, 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 + AABB body1AABB = body1->getAABB(); + AABB body2AABB = body2->getAABB(); + // Return true if the two AABBs overlap + return body1AABB.testCollision(body2AABB); +} + +void CollisionWorld::testCollision( ProxyShape* shape, CollisionCallback* callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + etk::Set shapes; + shapes.add(shape->this.broadPhaseID); + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(callback, shapes, emptySet); +} + +void CollisionWorld::testCollision( ProxyShape* shape1, ProxyShape* shape2, CollisionCallback* callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + etk::Set shapes1; + shapes1.add(shape1->this.broadPhaseID); + etk::Set shapes2; + shapes2.add(shape2->this.broadPhaseID); + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(callback, shapes1, shapes2); +} + +void CollisionWorld::testCollision( CollisionBody* body, CollisionCallback* callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + etk::Set shapes1; + // For each shape of the body + for ( ProxyShape* shape = body->getProxyShapesList(); + shape != null; + shape = shape->getNext()) { + shapes1.add(shape->this.broadPhaseID); + } + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(callback, shapes1, emptySet); +} + +void CollisionWorld::testCollision( CollisionBody* body1, CollisionBody* body2, CollisionCallback* callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + // Create the sets of shapes + etk::Set shapes1; + for ( ProxyShape* shape = body1->getProxyShapesList(); + shape != null; + shape = shape->getNext()) { + shapes1.add(shape->this.broadPhaseID); + } + etk::Set shapes2; + for ( ProxyShape* shape = body2->getProxyShapesList(); + shape != null; + shape = shape->getNext()) { + shapes2.add(shape->this.broadPhaseID); + } + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(callback, shapes1, shapes2); +} + +void CollisionWorld::testCollision(CollisionCallback* callback) { + // Reset all the contact manifolds lists of each body + resetContactManifoldListsOfBodies(); + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.testCollisionBetweenShapes(callback, emptySet, emptySet); +} + diff --git a/src/org/atriaSoft/ephysics/engine/CollisionWorld.hpp b/src/org/atriaSoft/ephysics/engine/CollisionWorld.hpp new file mode 100644 index 0000000..04de39c --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/CollisionWorld.hpp @@ -0,0 +1,173 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + class CollisionCallback; + /** + * @brief 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. + */ + class CollisionWorld { + protected : + CollisionDetection this.collisionDetection; //!< Reference to the collision detection + etk::Set this.bodies; //!< All the bodies (rigid and soft) of the world + long this.currentBodyID; //!< Current body ID + etk::Vector this.freeBodiesIDs; //!< List of free ID for rigid bodies + EventListener* this.eventListener; //!< Pointer to an event listener object + /// Private copy-ructor + CollisionWorld( CollisionWorld world); + /// Private assignment operator + CollisionWorld operator=( CollisionWorld world); + /// Return the next available body ID + long computeNextAvailableBodyID(); + /// Reset all the contact manifolds linked list of each body + void resetContactManifoldListsOfBodies(); + public : + /// Constructor + CollisionWorld(); + /// Destructor + virtual ~CollisionWorld(); + /** + * @brief 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 + */ + etk::Set::Iterator getBodiesBeginIterator() { + return this.bodies.begin(); + } + /** + * @brief 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 + */ + etk::Set::Iterator getBodiesEndIterator() { + return this.bodies.end(); + } + /** + * @brief Create a collision body and add it to the world + * @param transform etk::Transform3Dation mapping the local-space of the body to world-space + * @return A pointer to the body that has been created in the world + */ + CollisionBody* createCollisionBody( etk::Transform3D transform); + /** + * @brief Destroy a collision body + * @param collisionBody Pointer to the body to destroy + */ + void destroyCollisionBody(CollisionBody* collisionBody); + /** + * @brief Set the collision dispatch configuration + * This can be used to replace default collision detection algorithms by your + * custom algorithm for instance. + * @param[in] CollisionDispatch Pointer to a collision dispatch object describing + * which collision detection algorithm to use for two given collision shapes + */ + void setCollisionDispatch(CollisionDispatch* collisionDispatch) { + this.collisionDetection.setCollisionDispatch(collisionDispatch); + } + /** + * @brief 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 + */ + void raycast( Ray ray, + RaycastCallback* raycastCallback, + unsigned short raycastWithCategoryMaskBits = 0xFFFF) { + this.collisionDetection.raycast(raycastCallback, ray, raycastWithCategoryMaskBits); + } + /** + * @brief 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 + */ + boolean testAABBOverlap( CollisionBody* body1, + CollisionBody* body2) ; + /** + * @brief 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 + */ + boolean testAABBOverlap( ProxyShape* shape1, + ProxyShape* shape2) { + return this.collisionDetection.testAABBOverlap(shape1, shape2); + } + /** + * @brief 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 + */ + virtual void testCollision( ProxyShape* shape, + CollisionCallback* callback); + /** + * @briefTest 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 + */ + virtual void testCollision( ProxyShape* shape1, + ProxyShape* shape2, + CollisionCallback* callback); + /** + * @brief 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 + */ + virtual void testCollision( CollisionBody* body, + CollisionCallback* callback); + /** + * @brief 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 + */ + virtual void testCollision( CollisionBody* body1, + CollisionBody* body2, + CollisionCallback* callback); + /** + * @brief Test and report collisions between all shapes of the world + * @param callback Pointer to the object with the callback method + */ + virtual void testCollision(CollisionCallback* callback); + friend class CollisionDetection; + friend class CollisionBody; + friend class RigidBody; + friend class ConvexMeshShape; + }; + + /** + * @brief 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. + */ + class CollisionCallback { + public: + /** + * @brief Virtualisation of the destructor. + */ + virtual ~CollisionCallback() = default; + /** + * @brief This method will be called for contact. + * @param[in] contactPointInfo Contact information property. + */ + virtual void notifyContact( ContactPointInfo contactPointInfo)=0; + }; +} diff --git a/src/org/atriaSoft/ephysics/engine/ConstraintSolver.cpp b/src/org/atriaSoft/ephysics/engine/ConstraintSolver.cpp new file mode 100644 index 0000000..913357b --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/ConstraintSolver.cpp @@ -0,0 +1,78 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +ConstraintSolver::ConstraintSolver( etk::Map mapBodyToVelocityIndex): + this.mapBodyToConstrainedVelocityIndex(mapBodyToVelocityIndex), + this.isWarmStartingActive(true), + this.raintSolverData(mapBodyToVelocityIndex) { + +} + +void ConstraintSolver::initializeForIsland(float dt, Island* island) { + PROFILE("ConstraintSolver::initializeForIsland()"); + 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 + Joint** joints = island->getJoints(); + for (int iii=0; iiigetNbJoints(); ++iii) { + // Initialize the raint before solving it + joints[iii]->initBeforeSolve(this.raintSolverData); + // Warm-start the raint if warm-starting is enabled + if (this.isWarmStartingActive) { + joints[iii]->warmstart(this.raintSolverData); + } + } +} + +void ConstraintSolver::solveVelocityConstraints(Island* island) { + PROFILE("ConstraintSolver::solveVelocityConstraints()"); + assert(island != null); + assert(island->getNbJoints() > 0); + // For each joint of the island + Joint** joints = island->getJoints(); + for (int iii=0; iiigetNbJoints(); ++iii) { + joints[iii]->solveVelocityConstraint(this.raintSolverData); + } +} + +void ConstraintSolver::solvePositionConstraints(Island* island) { + PROFILE("ConstraintSolver::solvePositionConstraints()"); + assert(island != null); + assert(island->getNbJoints() > 0); + Joint** joints = island->getJoints(); + for (int iii=0; iii < island->getNbJoints(); ++iii) { + joints[iii]->solvePositionConstraint(this.raintSolverData); + } +} + +void ConstraintSolver::setConstrainedVelocitiesArrays(vec3* rainedLinearVelocities, + vec3* rainedAngularVelocities) { + assert(rainedLinearVelocities != null); + assert(rainedAngularVelocities != null); + this.raintSolverData.linearVelocities = rainedLinearVelocities; + this.raintSolverData.angularVelocities = rainedAngularVelocities; +} + +void ConstraintSolver::setConstrainedPositionsArrays(vec3* rainedPositions, + etk::Quaternion* rainedOrientations) { + assert(rainedPositions != null); + assert(rainedOrientations != null); + this.raintSolverData.positions = rainedPositions; + this.raintSolverData.orientations = rainedOrientations; +} \ No newline at end of file diff --git a/src/org/atriaSoft/ephysics/engine/ConstraintSolver.hpp b/src/org/atriaSoft/ephysics/engine/ConstraintSolver.hpp new file mode 100644 index 0000000..68e9147 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/ConstraintSolver.hpp @@ -0,0 +1,141 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace ephysics { + /** + * This structure contains data from the raint solver that are used to solve + * each joint raint. + */ + struct ConstraintSolverData { + public : + float timeStep; //!< Current time step of the simulation + vec3* linearVelocities; //!< Array with the bodies linear velocities + vec3* angularVelocities; //!< Array with the bodies angular velocities + vec3* positions; //!< Reference to the bodies positions + etk::Quaternion* orientations; //!< Reference to the bodies orientations + etk::Map mapBodyToConstrainedVelocityIndex; //!< Reference to the map that associates rigid body to their index in the rained velocities array + boolean isWarmStartingActive; //!< True if warm starting of the solver is active + /// Constructor + ConstraintSolverData( etk::Map refMapBodyToConstrainedVelocityIndex): + linearVelocities(null), + angularVelocities(null), + positions(null), + orientations(null), + mapBodyToConstrainedVelocityIndex(refMapBodyToConstrainedVelocityIndex) { + + } + }; + + /** + * @brief 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 virtual 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. + */ + class ConstraintSolver { + private : + etk::Map this.mapBodyToConstrainedVelocityIndex; //!< Reference to the map that associates rigid body to their index in the rained velocities array + float this.timeStep; //!< Current time step + boolean this.isWarmStartingActive; //!< True if the warm starting of the solver is active + ConstraintSolverData this.raintSolverData; //!< Constraint solver data used to initialize and solve the raints + public : + /// Constructor + ConstraintSolver( etk::Map mapBodyToVelocityIndex); + /// Initialize the raint solver for a given island + void initializeForIsland(float dt, Island* island); + /// Solve the raints + void solveVelocityConstraints(Island* island); + /// Solve the position raints + void solvePositionConstraints(Island* island); + /// Return true if the Non-Linear-Gauss-Seidel position correction technique is active + boolean getIsNonLinearGaussSeidelPositionCorrectionActive() { + return this.isWarmStartingActive; + } + /// Enable/Disable the Non-Linear-Gauss-Seidel position correction technique. + void setIsNonLinearGaussSeidelPositionCorrectionActive(boolean isActive) { + this.isWarmStartingActive = isActive; + } + /// Set the rained velocities arrays + void setConstrainedVelocitiesArrays(vec3* rainedLinearVelocities, + vec3* rainedAngularVelocities); + /// Set the rained positions/orientations arrays + void setConstrainedPositionsArrays(vec3* rainedPositions, + etk::Quaternion* rainedOrientations); + }; + +} diff --git a/src/org/atriaSoft/ephysics/engine/ContactSolver.cpp b/src/org/atriaSoft/ephysics/engine/ContactSolver.cpp new file mode 100644 index 0000000..533e038 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/ContactSolver.cpp @@ -0,0 +1,712 @@ +/** @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) + */ +#include +#include +#include +#include + +using namespace ephysics; + + float ContactSolver::BETA = float(0.2); + float ContactSolver::BETASPLITIMPULSE = float(0.2); + float ContactSolver::SLOP = float(0.01); + +ContactSolver::ContactSolver( etk::Map mapBodyToVelocityIndex) : + this.splitLinearVelocities(null), + this.splitAngularVelocities(null), + this.linearVelocities(null), + this.angularVelocities(null), + this.mapBodyToConstrainedVelocityIndex(mapBodyToVelocityIndex), + this.isWarmStartingActive(true), + this.isSplitImpulseActive(true), + this.isSolveFrictionAtContactManifoldCenterActive(true) { + +} + +void ContactSolver::initializeForIsland(float dt, Island* island) { + PROFILE("ContactSolver::initializeForIsland()"); + assert(island != null); + assert(island->getNbBodies() > 0); + assert(island->getNbContactManifolds() > 0); + assert(this.splitLinearVelocities != null); + assert(this.splitAngularVelocities != null); + // Set the current time step + this.timeStep = dt; + this.contactConstraints.resize(island->getNbContactManifolds()); + // For each contact manifold of the island + ContactManifold** contactManifolds = island->getContactManifold(); + for (int iii=0; iiigetNbContactPoints() > 0); + // Get the two bodies of the contact + RigidBody* body1 = staticcast(externalManifold->getContactPoint(0)->getBody1()); + RigidBody* body2 = staticcast(externalManifold->getContactPoint(0)->getBody2()); + assert(body1 != null); + assert(body2 != null); + // Get the position of the two bodies + vec3 x1 = body1->this.centerOfMassWorld; + vec3 x2 = body2->this.centerOfMassWorld; + // Initialize the internal contact manifold structure using the external + // contact manifold + internalManifold.indexBody1 = this.mapBodyToConstrainedVelocityIndex.find(body1)->second; + internalManifold.indexBody2 = this.mapBodyToConstrainedVelocityIndex.find(body2)->second; + internalManifold.inverseInertiaTensorBody1 = body1->getInertiaTensorInverseWorld(); + internalManifold.inverseInertiaTensorBody2 = body2->getInertiaTensorInverseWorld(); + internalManifold.massInverseBody1 = body1->this.massInverse; + internalManifold.massInverseBody2 = body2->this.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() == DYNAMIC; + internalManifold.isBody2DynamicType = body2->getType() == DYNAMIC; + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1 = vec3(0.0f,0.0f,0.0f); + internalManifold.frictionPointBody2 = vec3(0.0f,0.0f,0.0f); + } + // For each contact point of the contact manifold + for (int ccc=0; cccgetNbContactPoints(); ++ccc) { + ContactPointSolver contactPoint = internalManifold.contacts[ccc]; + // Get a contact point + ContactPoint* externalContact = externalManifold->getContactPoint(ccc); + // Get the contact point on the two bodies + vec3 p1 = externalContact->getWorldPointOnBody1(); + vec3 p2 = externalContact->getWorldPointOnBody2(); + contactPoint.externalContact = externalContact; + contactPoint.normal = externalContact->getNormal(); + contactPoint.r1 = p1 - x1; + contactPoint.r2 = p2 - x2; + contactPoint.penetrationDepth = externalContact->getPenetrationDepth(); + contactPoint.isRestingContact = externalContact->getIsRestingContact(); + externalContact->setIsRestingContact(true); + contactPoint.oldFrictionVector1 = externalContact->getFrictionVector1(); + contactPoint.oldFrictionvec2 = externalContact->getFrictionvec2(); + contactPoint.penetrationImpulse = 0.0; + contactPoint.friction1Impulse = 0.0; + contactPoint.friction2Impulse = 0.0; + contactPoint.rollingResistanceImpulse = vec3(0.0f,0.0f,0.0f); + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1 += p1; + internalManifold.frictionPointBody2 += p2; + } + } + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + internalManifold.frictionPointBody1 /=staticcast(internalManifold.nbContacts); + internalManifold.frictionPointBody2 /=staticcast(internalManifold.nbContacts); + internalManifold.r1Friction = internalManifold.frictionPointBody1 - x1; + internalManifold.r2Friction = internalManifold.frictionPointBody2 - x2; + internalManifold.oldFrictionVector1 = externalManifold->getFrictionVector1(); + internalManifold.oldFrictionvec2 = externalManifold->getFrictionvec2(); + // 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(); + } else { + // Initialize the accumulated impulses to zero + internalManifold.friction1Impulse = 0.0; + internalManifold.friction2Impulse = 0.0; + internalManifold.frictionTwistImpulse = 0.0; + internalManifold.rollingResistanceImpulse = vec3(0, 0, 0); + } + } + } + // Fill-in all the matrices needed to solve the LCP problem + initializeContactConstraints(); +} + +void ContactSolver::initializeContactConstraints() { + // For each contact raint + for (int c=0; c 0.0) { + contactPoint.inversePenetrationMass = 1.0f / massPenetration; + } + // If we do not solve the friction raints at the center of the contact manifold + if (!this.isSolveFrictionAtContactManifoldCenterActive) { + // Compute the friction vectors + computeFrictionVectors(deltaV, contactPoint); + 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); + // Compute the inverse mass matrix K for the friction + // raints at each contact point + float friction1Mass = manifold.massInverseBody1 + + manifold.massInverseBody2 + + ((I1 * contactPoint.r1CrossT1).cross(contactPoint.r1)).dot(contactPoint.frictionVector1) + + ((I2 * contactPoint.r2CrossT1).cross(contactPoint.r2)).dot(contactPoint.frictionVector1); + float friction2Mass = manifold.massInverseBody1 + + manifold.massInverseBody2 + + ((I1 * contactPoint.r1CrossT2).cross(contactPoint.r1)).dot(contactPoint.frictionvec2) + + ((I2 * contactPoint.r2CrossT2).cross(contactPoint.r2)).dot(contactPoint.frictionvec2); + if (friction1Mass > 0.0) { + contactPoint.inverseFriction1Mass = 1.0f / friction1Mass; + } + if (friction2Mass > 0.0) { + 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.0; + float deltaVDotN = deltaV.dot(contactPoint.normal); + if (deltaVDotN < -RESTITUTIONVELOCITYTHRESHOLD) { + contactPoint.restitutionBias = manifold.restitutionFactor * deltaVDotN; + } + // 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.0; + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + manifold.normal += contactPoint.normal; + } + } + // Compute the inverse K matrix for the rolling resistance raint + manifold.inverseRollingResistance.setZero(); + if (manifold.rollingResistanceFactor > 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (manifold.isBody1DynamicType || manifold.isBody2DynamicType)) { + manifold.inverseRollingResistance = manifold.inverseInertiaTensorBody1 + manifold.inverseInertiaTensorBody2; + manifold.inverseRollingResistance = manifold.inverseRollingResistance.getInverse(); + } + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + manifold.normal.normalize(); + vec3 deltaVFrictionPoint = v2 + w2.cross(manifold.r2Friction) + - v1 - w1.cross(manifold.r1Friction); + // 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); + float friction1Mass = manifold.massInverseBody1 + + manifold.massInverseBody2 + + ((I1 * manifold.r1CrossT1).cross(manifold.r1Friction)).dot(manifold.frictionVector1) + + ((I2 * manifold.r2CrossT1).cross(manifold.r2Friction)).dot(manifold.frictionVector1); + float friction2Mass = manifold.massInverseBody1 + + manifold.massInverseBody2 + + ((I1 * manifold.r1CrossT2).cross(manifold.r1Friction)).dot(manifold.frictionvec2) + + ((I2 * manifold.r2CrossT2).cross(manifold.r2Friction)).dot(manifold.frictionvec2); + float frictionTwistMass = manifold.normal.dot(manifold.inverseInertiaTensorBody1 * manifold.normal) + + manifold.normal.dot(manifold.inverseInertiaTensorBody2 * manifold.normal); + if (friction1Mass > 0.0) { + manifold.inverseFriction1Mass = 1.0f/friction1Mass; + } + if (friction2Mass > 0.0) { + manifold.inverseFriction2Mass = 1.0f/friction2Mass; + } + if (frictionTwistMass > 0.0) { + manifold.inverseTwistFrictionMass = 1.0f / frictionTwistMass; + } + } + } +} + +void ContactSolver::warmStart() { + // Check that warm starting is active + if (!this.isWarmStartingActive) { + return; + } + // For each raint + for (int ccc=0; ccc 0) { + // Compute the impulse P = J^T * lambda + Impulse impulseRollingResistance(vec3(0.0f,0.0f,0.0f), -contactPoint.rollingResistanceImpulse, + vec3(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.0; + contactPoint.friction1Impulse = 0.0; + contactPoint.friction2Impulse = 0.0; + contactPoint.rollingResistanceImpulse = vec3(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 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj atLeastOneRestingContactPoint) { + // Project the old friction impulses (with old friction vectors) into the new friction + // vectors to get the new friction impulses + vec3 oldFrictionImpulse = contactManifold.friction1Impulse * + contactManifold.oldFrictionVector1 + + contactManifold.friction2Impulse * + contactManifold.oldFrictionvec2; + contactManifold.friction1Impulse = oldFrictionImpulse.dot(contactManifold.frictionVector1); + contactManifold.friction2Impulse = oldFrictionImpulse.dot(contactManifold.frictionvec2); + // ------ First friction raint at the center of the contact manifold ------ // + // Compute the impulse P = J^T * lambda + vec3 linearImpulseBody1 = -contactManifold.frictionVector1 * contactManifold.friction1Impulse; + vec3 angularImpulseBody1 = -contactManifold.r1CrossT1 * contactManifold.friction1Impulse; + vec3 linearImpulseBody2 = contactManifold.frictionVector1 * contactManifold.friction1Impulse; + vec3 angularImpulseBody2 = contactManifold.r2CrossT1 * contactManifold.friction1Impulse; + Impulse impulseFriction1(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 * contactManifold.friction2Impulse; + angularImpulseBody1 = -contactManifold.r1CrossT2 * contactManifold.friction2Impulse; + linearImpulseBody2 = contactManifold.frictionvec2 * contactManifold.friction2Impulse; + angularImpulseBody2 = contactManifold.r2CrossT2 * contactManifold.friction2Impulse; + Impulse impulseFriction2(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 = vec3(0.0, 0.0, 0.0); + angularImpulseBody1 = -contactManifold.normal * contactManifold.frictionTwistImpulse; + linearImpulseBody2 = vec3(0.0, 0.0, 0.0); + angularImpulseBody2 = contactManifold.normal * contactManifold.frictionTwistImpulse; + Impulse impulseTwistFriction(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; + angularImpulseBody2 = contactManifold.rollingResistanceImpulse; + Impulse impulseRollingResistance(vec3(0.0f,0.0f,0.0f), angularImpulseBody1, + vec3(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.0; + contactManifold.friction2Impulse = 0.0; + contactManifold.frictionTwistImpulse = 0.0; + contactManifold.rollingResistanceImpulse = vec3(0.0f,0.0f,0.0f); + } + } +} + +void ContactSolver::solve() { + PROFILE("ContactSolver::solve()"); + float deltaLambda; + float lambdaTemp; + // For each contact manifold + for (int ccc=0; ccc SLOP) { + biasPenetrationDepth = -(beta/this.timeStep) * etk::max(0.0f, float(contactPoint.penetrationDepth - SLOP)); + } + float b = biasPenetrationDepth + contactPoint.restitutionBias; + // Compute the Lagrange multiplier lambda + if (this.isSplitImpulseActive) { + deltaLambda = - (Jv + contactPoint.restitutionBias) * contactPoint.inversePenetrationMass; + } else { + deltaLambda = - (Jv + b) * contactPoint.inversePenetrationMass; + } + lambdaTemp = contactPoint.penetrationImpulse; + contactPoint.penetrationImpulse = etk::max(contactPoint.penetrationImpulse + deltaLambda, 0.0f); + deltaLambda = contactPoint.penetrationImpulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + Impulse impulsePenetration = computePenetrationImpulse(deltaLambda, contactPoint); + // Apply the impulse to the bodies of the raint + applyImpulse(impulsePenetration, contactManifold); + suthis.penetrationImpulse += contactPoint.penetrationImpulse; + // If the split impulse position correction is active + if (this.isSplitImpulseActive) { + // Split impulse (position correction) + vec3 v1Split = this.splitLinearVelocities[contactManifold.indexBody1]; + vec3 w1Split = this.splitAngularVelocities[contactManifold.indexBody1]; + vec3 v2Split = this.splitLinearVelocities[contactManifold.indexBody2]; + vec3 w2Split = this.splitAngularVelocities[contactManifold.indexBody2]; + vec3 deltaVSplit = v2Split + w2Split.cross(contactPoint.r2) - v1Split - w1Split.cross(contactPoint.r1); + float JvSplit = deltaVSplit.dot(contactPoint.normal); + float deltaLambdaSplit = - (JvSplit + biasPenetrationDepth) * contactPoint.inversePenetrationMass; + float lambdaTempSplit = contactPoint.penetrationSplitImpulse; + contactPoint.penetrationSplitImpulse = etk::max(contactPoint.penetrationSplitImpulse + deltaLambdaSplit, 0.0f); + deltaLambda = contactPoint.penetrationSplitImpulse - lambdaTempSplit; + // Compute the impulse P=J^T * lambda + Impulse splitImpulsePenetration = computePenetrationImpulse(deltaLambdaSplit, contactPoint); + 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 + w2.cross(contactPoint.r2) - v1 - w1.cross(contactPoint.r1); + Jv = deltaV.dot(contactPoint.frictionVector1); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv; + deltaLambda *= contactPoint.inverseFriction1Mass; + float frictionLimit = contactManifold.frictionCoefficient * contactPoint.penetrationImpulse; + lambdaTemp = contactPoint.friction1Impulse; + contactPoint.friction1Impulse = etk::max(-frictionLimit, + etk::min(contactPoint.friction1Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactPoint.friction1Impulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + Impulse impulseFriction1 = computeFriction1Impulse(deltaLambda, contactPoint); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction1, contactManifold); + // --------- Friction 2 --------- // + // Compute J*v + deltaV = v2 + w2.cross(contactPoint.r2) - v1 - w1.cross(contactPoint.r1); + Jv = deltaV.dot(contactPoint.frictionvec2); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv; + deltaLambda *= contactPoint.inverseFriction2Mass; + frictionLimit = contactManifold.frictionCoefficient * contactPoint.penetrationImpulse; + lambdaTemp = contactPoint.friction2Impulse; + contactPoint.friction2Impulse = etk::max(-frictionLimit, etk::min(contactPoint.friction2Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactPoint.friction2Impulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + Impulse impulseFriction2 = computeFriction2Impulse(deltaLambda, contactPoint); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseFriction2, contactManifold); + // --------- Rolling resistance raint --------- // + if (contactManifold.rollingResistanceFactor > 0) { + // Compute J*v + vec3 JvRolling = w2 - w1; + // Compute the Lagrange multiplier lambda + vec3 deltaLambdaRolling = contactManifold.inverseRollingResistance * (-JvRolling); + float rollingLimit = contactManifold.rollingResistanceFactor * contactPoint.penetrationImpulse; + vec3 lambdaTempRolling = contactPoint.rollingResistanceImpulse; + contactPoint.rollingResistanceImpulse = clamp(contactPoint.rollingResistanceImpulse + deltaLambdaRolling, rollingLimit); + deltaLambdaRolling = contactPoint.rollingResistanceImpulse - lambdaTempRolling; + // Compute the impulse P=J^T * lambda + Impulse impulseRolling(vec3(0.0f,0.0f,0.0f), -deltaLambdaRolling, + vec3(0.0f,0.0f,0.0f), deltaLambdaRolling); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRolling, contactManifold); + } + } + } + // If we solve the friction raints at the center of the contact manifold + if (this.isSolveFrictionAtContactManifoldCenterActive) { + // ------ First friction raint at the center of the contact manifol ------ // + // Compute J*v + vec3 deltaV = v2 + w2.cross(contactManifold.r2Friction) + - v1 - w1.cross(contactManifold.r1Friction); + float Jv = deltaV.dot(contactManifold.frictionVector1); + // Compute the Lagrange multiplier lambda + float deltaLambda = -Jv * contactManifold.inverseFriction1Mass; + float frictionLimit = contactManifold.frictionCoefficient * suthis.penetrationImpulse; + lambdaTemp = contactManifold.friction1Impulse; + contactManifold.friction1Impulse = etk::max(-frictionLimit, etk::min(contactManifold.friction1Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactManifold.friction1Impulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + vec3 linearImpulseBody1 = -contactManifold.frictionVector1 * deltaLambda; + vec3 angularImpulseBody1 = -contactManifold.r1CrossT1 * deltaLambda; + vec3 linearImpulseBody2 = contactManifold.frictionVector1 * deltaLambda; + vec3 angularImpulseBody2 = contactManifold.r2CrossT1 * deltaLambda; + Impulse impulseFriction1(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 manifol ----- // + // Compute J*v + deltaV = v2 + w2.cross(contactManifold.r2Friction) + - v1 - w1.cross(contactManifold.r1Friction); + Jv = deltaV.dot(contactManifold.frictionvec2); + // Compute the Lagrange multiplier lambda + deltaLambda = -Jv * contactManifold.inverseFriction2Mass; + frictionLimit = contactManifold.frictionCoefficient * suthis.penetrationImpulse; + lambdaTemp = contactManifold.friction2Impulse; + contactManifold.friction2Impulse = etk::max(-frictionLimit, etk::min(contactManifold.friction2Impulse + deltaLambda, frictionLimit)); + deltaLambda = contactManifold.friction2Impulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + linearImpulseBody1 = -contactManifold.frictionvec2 * deltaLambda; + angularImpulseBody1 = -contactManifold.r1CrossT2 * deltaLambda; + linearImpulseBody2 = contactManifold.frictionvec2 * deltaLambda; + angularImpulseBody2 = contactManifold.r2CrossT2 * deltaLambda; + Impulse impulseFriction2(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 manifol ------ // + // Compute J*v + deltaV = w2 - w1; + Jv = deltaV.dot(contactManifold.normal); + deltaLambda = -Jv * (contactManifold.inverseTwistFrictionMass); + frictionLimit = contactManifold.frictionCoefficient * suthis.penetrationImpulse; + lambdaTemp = contactManifold.frictionTwistImpulse; + contactManifold.frictionTwistImpulse = etk::max(-frictionLimit, etk::min(contactManifold.frictionTwistImpulse + deltaLambda, frictionLimit)); + deltaLambda = contactManifold.frictionTwistImpulse - lambdaTemp; + // Compute the impulse P=J^T * lambda + linearImpulseBody1 = vec3(0.0, 0.0, 0.0); + angularImpulseBody1 = -contactManifold.normal * deltaLambda; + linearImpulseBody2 = vec3(0.0, 0.0, 0.0);; + angularImpulseBody2 = contactManifold.normal * deltaLambda; + Impulse impulseTwistFriction(linearImpulseBody1, angularImpulseBody1, + linearImpulseBody2, angularImpulseBody2); + // 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 + vec3 JvRolling = w2 - w1; + // Compute the Lagrange multiplier lambda + vec3 deltaLambdaRolling = contactManifold.inverseRollingResistance * (-JvRolling); + float rollingLimit = contactManifold.rollingResistanceFactor * suthis.penetrationImpulse; + vec3 lambdaTempRolling = contactManifold.rollingResistanceImpulse; + contactManifold.rollingResistanceImpulse = clamp(contactManifold.rollingResistanceImpulse + deltaLambdaRolling, + rollingLimit); + deltaLambdaRolling = contactManifold.rollingResistanceImpulse - lambdaTempRolling; + // Compute the impulse P=J^T * lambda + angularImpulseBody1 = -deltaLambdaRolling; + angularImpulseBody2 = deltaLambdaRolling; + Impulse impulseRolling(vec3(0.0f,0.0f,0.0f), + angularImpulseBody1, + vec3(0.0f,0.0f,0.0f), + angularImpulseBody2); + // Apply the impulses to the bodies of the raint + applyImpulse(impulseRolling, contactManifold); + } + } + } +} + +void ContactSolver::storeImpulses() { + // For each contact manifold + for (int ccc=0; cccsetPenetrationImpulse(contactPoint.penetrationImpulse); + contactPoint.externalContact->setFrictionImpulse1(contactPoint.friction1Impulse); + contactPoint.externalContact->setFrictionImpulse2(contactPoint.friction2Impulse); + contactPoint.externalContact->setRollingResistanceImpulse(contactPoint.rollingResistanceImpulse); + contactPoint.externalContact->setFrictionVector1(contactPoint.frictionVector1); + contactPoint.externalContact->setFrictionvec2(contactPoint.frictionvec2); + } + manifold.externalContactManifold->setFrictionImpulse1(manifold.friction1Impulse); + manifold.externalContactManifold->setFrictionImpulse2(manifold.friction2Impulse); + manifold.externalContactManifold->setFrictionTwistImpulse(manifold.frictionTwistImpulse); + manifold.externalContactManifold->setRollingResistanceImpulse(manifold.rollingResistanceImpulse); + manifold.externalContactManifold->setFrictionVector1(manifold.frictionVector1); + manifold.externalContactManifold->setFrictionvec2(manifold.frictionvec2); + } +} + +void ContactSolver::applyImpulse( Impulse impulse, ContactManifoldSolver manifold) { + // Update the velocities of the body 1 by applying the impulse P + this.linearVelocities[manifold.indexBody1] += manifold.massInverseBody1 * impulse.linearImpulseBody1; + this.angularVelocities[manifold.indexBody1] += manifold.inverseInertiaTensorBody1 * impulse.angularImpulseBody1; + // Update the velocities of the body 1 by applying the impulse P + this.linearVelocities[manifold.indexBody2] += manifold.massInverseBody2 * impulse.linearImpulseBody2; + this.angularVelocities[manifold.indexBody2] += manifold.inverseInertiaTensorBody2 * impulse.angularImpulseBody2; +} + +void ContactSolver::applySplitImpulse( Impulse impulse, ContactManifoldSolver manifold) { + // Update the velocities of the body 1 by applying the impulse P + this.splitLinearVelocities[manifold.indexBody1] += manifold.massInverseBody1 * impulse.linearImpulseBody1; + this.splitAngularVelocities[manifold.indexBody1] += manifold.inverseInertiaTensorBody1 * impulse.angularImpulseBody1; + // Update the velocities of the body 1 by applying the impulse P + this.splitLinearVelocities[manifold.indexBody2] += manifold.massInverseBody2 * impulse.linearImpulseBody2; + this.splitAngularVelocities[manifold.indexBody2] += manifold.inverseInertiaTensorBody2 * impulse.angularImpulseBody2; +} + +void ContactSolver::computeFrictionVectors( vec3 deltaVelocity, ContactPointSolver contactPoint) { + assert(contactPoint.normal.length() > 0.0); + // Compute the velocity difference vector in the tangential plane + vec3 normalVelocity = deltaVelocity.dot(contactPoint.normal) * contactPoint.normal; + vec3 tangentVelocity = deltaVelocity - normalVelocity; + // If the velocty difference in the tangential plane is not zero + float lengthTangenVelocity = tangentVelocity.length(); + if (lengthTangenVelocity > FLTEPSILON) { + // Compute the first friction vector in the direction of the tangent + // velocity difference + contactPoint.frictionVector1 = tangentVelocity / lengthTangenVelocity; + } else { + // Get any orthogonal vector to the normal as the first friction vector + contactPoint.frictionVector1 = contactPoint.normal.getOrthoVector(); + } + // 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).safeNormalized(); +} + +void ContactSolver::computeFrictionVectors( vec3 deltaVelocity, ContactManifoldSolver contactManifold) { + assert(contactManifold.normal.length() > 0.0); + // Compute the velocity difference vector in the tangential plane + vec3 normalVelocity = deltaVelocity.dot(contactManifold.normal) * contactManifold.normal; + vec3 tangentVelocity = deltaVelocity - normalVelocity; + // If the velocty difference in the tangential plane is not zero + float lengthTangenVelocity = tangentVelocity.length(); + if (lengthTangenVelocity > FLTEPSILON) { + // Compute the first friction vector in the direction of the tangent + // velocity difference + contactManifold.frictionVector1 = tangentVelocity / lengthTangenVelocity; + } else { + // Get any orthogonal vector to the normal as the first friction vector + contactManifold.frictionVector1 = contactManifold.normal.getOrthoVector(); + } + // 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).safeNormalized(); +} + +void ContactSolver::cleanup() { + this.contactConstraints.clear(); +} + +void ContactSolver::setSplitVelocitiesArrays(vec3* splitLinearVelocities, vec3* splitAngularVelocities) { + assert(splitLinearVelocities != NULL); + assert(splitAngularVelocities != NULL); + this.splitLinearVelocities = splitLinearVelocities; + this.splitAngularVelocities = splitAngularVelocities; +} + +void ContactSolver::setConstrainedVelocitiesArrays(vec3* rainedLinearVelocities, vec3* rainedAngularVelocities) { + assert(rainedLinearVelocities != NULL); + assert(rainedAngularVelocities != NULL); + this.linearVelocities = rainedLinearVelocities; + this.angularVelocities = rainedAngularVelocities; +} + +boolean ContactSolver::isSplitImpulseActive() { + return this.isSplitImpulseActive; +} + +void ContactSolver::setIsSplitImpulseActive(boolean isActive) { + this.isSplitImpulseActive = isActive; +} + +void ContactSolver::setIsSolveFrictionAtContactManifoldCenterActive(boolean isActive) { + this.isSolveFrictionAtContactManifoldCenterActive = isActive; +} + +float ContactSolver::computeMixedRestitutionFactor(RigidBody* body1, RigidBody* body2) { + float restitution1 = body1->getMaterial().getBounciness(); + float restitution2 = body2->getMaterial().getBounciness(); + // Return the largest restitution factor + return (restitution1 > restitution2) ? restitution1 : restitution2; +} + +float ContactSolver::computeMixedFrictionCoefficient(RigidBody* body1, RigidBody* body2) { + // Use the geometric mean to compute the mixed friction coefficient + return sqrt(body1->getMaterial().getFrictionCoefficient() * + body2->getMaterial().getFrictionCoefficient()); +} + +float ContactSolver::computeMixedRollingResistance(RigidBody* body1, RigidBody* body2) { + return 0.5f + * (body1->getMaterial().getRollingResistance() + + body2->getMaterial().getRollingResistance()); +} + + Impulse ContactSolver::computePenetrationImpulse(float deltaLambda, ContactPointSolver contactPoint) { + return Impulse(-contactPoint.normal * deltaLambda, + -contactPoint.r1CrossN * deltaLambda, + contactPoint.normal * deltaLambda, + contactPoint.r2CrossN * deltaLambda); +} + + Impulse ContactSolver::computeFriction1Impulse(float deltaLambda, ContactPointSolver contactPoint) { + return Impulse(-contactPoint.frictionVector1 * deltaLambda, + -contactPoint.r1CrossT1 * deltaLambda, + contactPoint.frictionVector1 * deltaLambda, + contactPoint.r2CrossT1 * deltaLambda); +} + + Impulse ContactSolver::computeFriction2Impulse(float deltaLambda, ContactPointSolver contactPoint) { + return Impulse(-contactPoint.frictionvec2 * deltaLambda, + -contactPoint.r1CrossT2 * deltaLambda, + contactPoint.frictionvec2 * deltaLambda, + contactPoint.r2CrossT2 * deltaLambda); +} diff --git a/src/org/atriaSoft/ephysics/engine/ContactSolver.hpp b/src/org/atriaSoft/ephysics/engine/ContactSolver.hpp new file mode 100644 index 0000000..ed0d624 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/ContactSolver.hpp @@ -0,0 +1,312 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + /** + * @brief 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 Fc that will act on the + * bodies to keep the raint satisfied. Note that from the virtual 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. + */ + class ContactSolver { + private: + /** + * Contact solver internal data structure that to store all the + * information relative to a contact point + */ + struct 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 + vec3 rollingResistanceImpulse; //!< Accumulated rolling resistance impulse + vec3 normal; //!< Normal vector of the contact + vec3 frictionVector1; //!< First friction vector in the tangent plane + vec3 frictionvec2; //!< Second friction vector in the tangent plane + vec3 oldFrictionVector1; //!< Old first friction vector in the tangent plane + vec3 oldFrictionvec2; //!< Old second friction vector in the tangent plane + vec3 r1; //!< Vector from the body 1 center to the contact point + vec3 r2; //!< Vector from the body 2 center to the contact point + vec3 r1CrossT1; //!< Cross product of r1 with 1st friction vector + vec3 r1CrossT2; //!< Cross product of r1 with 2nd friction vector + vec3 r2CrossT1; //!< Cross product of r2 with 1st friction vector + vec3 r2CrossT2; //!< Cross product of r2 with 2nd friction vector + vec3 r1CrossN; //!< Cross product of r1 with the contact normal + vec3 r2CrossN; //!< 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 + }; + /** + * @brief Contact solver internal data structure to store all the information relative to a contact manifold. + */ + struct 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 + etk::Matrix3x3 inverseInertiaTensorBody1; //!< Inverse inertia tensor of body 1 + etk::Matrix3x3 inverseInertiaTensorBody2; //!< Inverse inertia tensor of body 2 + ContactPointSolver contacts[MAXCONTACTPOINTSINMANIFOLD]; //!< 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-// + vec3 normal; //!< Average normal vector of the contact manifold + vec3 frictionPointBody1; //!< Point on body 1 where to apply the friction raints + vec3 frictionPointBody2; //!< Point on body 2 where to apply the friction raints + vec3 r1Friction; //!< R1 vector for the friction raints + vec3 r2Friction; //!< R2 vector for the friction raints + vec3 r1CrossT1; //!< Cross product of r1 with 1st friction vector + vec3 r1CrossT2; //!< Cross product of r1 with 2nd friction vector + vec3 r2CrossT1; //!< Cross product of r2 with 1st friction vector + vec3 r2CrossT2; //!< 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 + etk::Matrix3x3 inverseRollingResistance; //!< Matrix K for the rolling resistance raint + vec3 frictionVector1; //!< First friction direction at contact manifold center + vec3 frictionvec2; //!< Second friction direction at contact manifold center + vec3 oldFrictionVector1; //!< Old 1st friction direction at contact manifold center + vec3 oldFrictionvec2; //!< 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 + vec3 rollingResistanceImpulse; //!< Rolling resistance impulse + }; + static float BETA; //!< Beta value for the penetration depth position correction without split impulses + static float BETASPLITIMPULSE; //!< Beta value for the penetration depth position correction with split impulses + static float SLOP; //!< Slop distance (allowed penetration distance between bodies) + vec3* this.splitLinearVelocities; //!< Split linear velocities for the position contact solver (split impulse) + vec3* this.splitAngularVelocities; //!< Split angular velocities for the position contact solver (split impulse) + float this.timeStep; //!< Current time step + etk::Vector this.contactConstraints; //!< Contact raints + vec3* this.linearVelocities; //!< Array of linear velocities + vec3* this.angularVelocities; //!< Array of angular velocities + etk::Map this.mapBodyToConstrainedVelocityIndex; //!< Reference to the map of rigid body to their index in the rained velocities array + boolean this.isWarmStartingActive; //!< True if the warm starting of the solver is active + boolean this.isSplitImpulseActive; //!< True if the split impulse position correction is active + boolean this.isSolveFrictionAtContactManifoldCenterActive; //!< True if we solve 3 friction raints at the contact manifold center only instead of 2 friction raints at each contact point + /** + * @brief Initialize the contact raints before solving the system + */ + void initializeContactConstraints(); + /** + * @brief Apply an impulse to the two bodies of a raint + * @param[in] impulse Impulse to apply + * @param[in] manifold Constraint to apply the impulse + */ + void applyImpulse( Impulse impulse, ContactManifoldSolver manifold); + /** + * @brief Apply an impulse to the two bodies of a raint + * @param[in] impulse Impulse to apply + * @param[in] manifold Constraint to apply the impulse + */ + void applySplitImpulse( Impulse impulse, ContactManifoldSolver manifold); + /** + * @brief Compute the collision restitution factor from the restitution factor of each body + * @param[in] body1 First body to compute + * @param[in] body2 Second body to compute + * @return Collision restitution factor + */ + float computeMixedRestitutionFactor(RigidBody* body1, RigidBody* body2) ; + /** + * @brief Compute the mixed friction coefficient from the friction coefficient of each body + * @param[in] body1 First body to compute + * @param[in] body2 Second body to compute + * @return Mixed friction coefficient + */ + float computeMixedFrictionCoefficient(RigidBody* body1, RigidBody* body2) ; + /** + * @brief Compute the mixed rolling resistance factor between two bodies + * @param[in] body1 First body to compute + * @param[in] body2 Second body to compute + * @return Mixed rolling resistance + */ + float computeMixedRollingResistance(RigidBody* body1, RigidBody* body2) ; + /** + * @brief 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[in] deltaVelocity Velocity ratio (with the delta time step) + * @param[in,out] contactPoint Contact point property + */ + void computeFrictionVectors( vec3 deltaVelocity, ContactPointSolver contactPoint) ; + /** + * @brief 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[in] deltaVelocity Velocity ratio (with the delta time step) + * @param[in,out] contactPoint Contact point property + */ + void computeFrictionVectors( vec3 deltaVelocity, ContactManifoldSolver contactPoint) ; + /** + * @brief Compute a penetration raint impulse + * @param[in] deltaLambda Ratio to apply at the calculation. + * @param[in,out] contactPoint Contact point property + * @return Impulse of the penetration result + */ + Impulse computePenetrationImpulse(float deltaLambda, ContactPointSolver contactPoint) ; + /** + * @brief Compute the first friction raint impulse + * @param[in] deltaLambda Ratio to apply at the calculation. + * @param[in] contactPoint Contact point property + * @return Impulse of the friction result + */ + Impulse computeFriction1Impulse(float deltaLambda, ContactPointSolver contactPoint) ; + /** + * @brief Compute the second friction raint impulse + * @param[in] deltaLambda Ratio to apply at the calculation. + * @param[in] contactPoint Contact point property + * @return Impulse of the friction result + */ + Impulse computeFriction2Impulse(float deltaLambda, ContactPointSolver contactPoint) ; + public: + /** + * @brief Constructor + * @param[in] mapBodyToVelocityIndex + */ + ContactSolver( etk::Map mapBodyToVelocityIndex); + /** + * @brief Virtualize the destructor + */ + virtual ~ContactSolver() = default; + /** + * @brief Initialize the raint solver for a given island + * @param[in] dt Delta step time + * @param[in] island Island list property + */ + void initializeForIsland(float dt, Island* island); + /** + * @brief Set the split velocities arrays + * @param[in] splitLinearVelocities Split linear velocities Table pointer (not free) + * @param[in] splitAngularVelocities Split angular velocities Table pointer (not free) + */ + void setSplitVelocitiesArrays(vec3* splitLinearVelocities, vec3* splitAngularVelocities); + /** + * @brief Set the rained velocities arrays + * @param[in] rainedLinearVelocities Constrained Linear velocities Table pointer (not free) + * @param[in] rainedAngularVelocities Constrained angular velocities Table pointer (not free) + */ + void setConstrainedVelocitiesArrays(vec3* rainedLinearVelocities, vec3* rainedAngularVelocities); + /** + * @brief 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 + */ + void warmStart(); + /** + * @brief Store the computed impulses to use them to warm start the solver at the next iteration + */ + void storeImpulses(); + /** + * @brief Solve the contacts + */ + void solve(); + /** + * @brief 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 + */ + boolean isSplitImpulseActive() ; + /** + * @brief Activate or Deactivate the split impulses for contacts + * @param[in] isActive status to set. + */ + void setIsSplitImpulseActive(boolean isActive); + /** + * @brief Activate or deactivate the solving of friction raints at the center of + /// the contact manifold instead of solving them at each contact point + * @param[in] isActive Enable or not the center inertie + */ + void setIsSolveFrictionAtContactManifoldCenterActive(boolean isActive); + /** + * @brief Clean up the raint solver + */ + void cleanup(); + }; + +} diff --git a/src/org/atriaSoft/ephysics/engine/DynamicsWorld.cpp b/src/org/atriaSoft/ephysics/engine/DynamicsWorld.cpp new file mode 100644 index 0000000..74c7af5 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/DynamicsWorld.cpp @@ -0,0 +1,789 @@ +/** @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) + */ +#include +#include +#include +#include +#include +#include + +ephysics::DynamicsWorld::DynamicsWorld( vec3 gravity): + CollisionWorld(), + this.contactSolver(this.mapBodyToConstrainedVelocityIndex), + this.raintSolver(this.mapBodyToConstrainedVelocityIndex), + this.nbVelocitySolverIterations(DEFAULTVELOCITYSOLVERNBITERATIONS), + this.nbPositionSolverIterations(DEFAULTPOSITIONSOLVERNBITERATIONS), + this.isSleepingEnabled(SPLEEPINGENABLED), + this.gravity(gravity), + this.isGravityEnabled(true), + this.numberBodiesCapacity(0), + this.sleepLinearVelocity(DEFAULTSLEEPLINEARVELOCITY), + this.sleepAngularVelocity(DEFAULTSLEEPANGULARVELOCITY), + this.timeBeforeSleep(DEFAULTTIMEBEFORESLEEP) { + +} + +ephysics::DynamicsWorld::~DynamicsWorld() { + // Destroy all the joints that have not been removed + etk::Set::Iterator itJoints; + for (itJoints = this.joints.begin(); itJoints != this.joints.end();) { + etk::Set::Iterator itToRemove = itJoints; + ++itJoints; + destroyJoint(*itToRemove); + } + // Destroy all the rigid bodies that have not been removed + etk::Set::Iterator itRigidBodies; + for (itRigidBodies = this.rigidBodies.begin(); itRigidBodies != this.rigidBodies.end();) { + etk::Set::Iterator itToRemove = itRigidBodies; + ++itRigidBodies; + destroyRigidBody(*itToRemove); + } + // Release the memory allocated for the islands + for (auto it: this.islands) { + // Call the island destructor + ETKDELETE(Island, it); + it = null; + } + this.islands.clear(); + // Release the memory allocated for the bodies velocity arrays + if (this.numberBodiesCapacity > 0) { + this.splitLinearVelocities.clear(); + this.splitAngularVelocities.clear(); + this.rainedLinearVelocities.clear(); + this.rainedAngularVelocities.clear(); + this.rainedPositions.clear(); + this.rainedOrientations.clear(); + } + assert(this.joints.size() == 0); + assert(this.rigidBodies.size() == 0); +#ifdef ISPROFILINGACTIVE + // Print the profiling report + etk::Stream tmp; + Profiler::printReport(tmp); + Log.print(tmp.str()); + // Destroy the profiler (release the allocated memory) + Profiler::destroy(); +#endif +} + +void ephysics::DynamicsWorld::update(float timeStep) { + #ifdef ISPROFILINGACTIVE + // Increment the frame counter of the profiler + Profiler::incrementFrameCounter(); + #endif + PROFILE("ephysics::DynamicsWorld::update()"); + 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(); + // Integrate the velocities + integrateRigidBodiesVelocities(); + // Solve the contacts and raints + solveContactsAndConstraints(); + // Integrate the position and orientation of each body + integrateRigidBodiesPositions(); + // Solve the position correction for raints + solvePositionCorrection(); + // Update the state (positions and velocities) of the bodies + updateBodiesState(); + if (this.isSleepingEnabled) { + updateSleepingBodies(); + } + // Notify the event listener about the end of an internal tick + if (this.eventListener != null) { + this.eventListener->endInternalTick(); + } + // Reset the external force and torque applied to the bodies + resetBodiesForceAndTorque(); +} + +void ephysics::DynamicsWorld::integrateRigidBodiesPositions() { + PROFILE("ephysics::DynamicsWorld::integrateRigidBodiesPositions()"); + // For each island of the world + for (int i=0; i < this.islands.size(); i++) { + RigidBody** bodies = this.islands[i]->getBodies(); + // For each body of the island + for (int b=0; b < this.islands[i]->getNbBodies(); b++) { + // Get the rained velocity + int indexArray = this.mapBodyToConstrainedVelocityIndex.find(bodies[b])->second; + vec3 newLinVelocity = this.rainedLinearVelocities[indexArray]; + vec3 newAngVelocity = this.rainedAngularVelocities[indexArray]; + // Add the split impulse velocity from Contact Solver (only used + // to update the position) + if (this.contactSolver.isSplitImpulseActive()) { + newLinVelocity += this.splitLinearVelocities[indexArray]; + newAngVelocity += this.splitAngularVelocities[indexArray]; + } + // Get current position and orientation of the body + vec3 currentPosition = bodies[b]->this.centerOfMassWorld; + etk::Quaternion currentOrientation = bodies[b]->getTransform().getOrientation(); + // Update the new rained position and orientation of the body + this.rainedPositions[indexArray] = currentPosition + newLinVelocity * this.timeStep; + this.rainedOrientations[indexArray] = currentOrientation; + this.rainedOrientations[indexArray] += etk::Quaternion(0, newAngVelocity) + * currentOrientation + * 0.5f + * this.timeStep; + } + } +} + +void ephysics::DynamicsWorld::updateBodiesState() { + PROFILE("ephysics::DynamicsWorld::updateBodiesState()"); + // For each island of the world + for (int islandIndex = 0; islandIndex < this.islands.size(); islandIndex++) { + // For each body of the island + RigidBody** bodies = this.islands[islandIndex]->getBodies(); + for (int b=0; b < this.islands[islandIndex]->getNbBodies(); b++) { + int index = this.mapBodyToConstrainedVelocityIndex.find(bodies[b])->second; + // Update the linear and angular velocity of the body + bodies[b]->this.linearVelocity = this.rainedLinearVelocities[index]; + bodies[b]->this.angularVelocity = this.rainedAngularVelocities[index]; + // Update the position of the center of mass of the body + bodies[b]->this.centerOfMassWorld = this.rainedPositions[index]; + // Update the orientation of the body + bodies[b]->this.transform.setOrientation(this.rainedOrientations[index].safeNormalized()); + // Update the transform of the body (using the new center of mass and new orientation) + bodies[b]->updateTransformWithCenterOfMass(); + // Update the broad-phase state of the body + bodies[b]->updateBroadPhaseState(); + } + } +} + +void ephysics::DynamicsWorld::initVelocityArrays() { + // Allocate memory for the bodies velocity arrays + int nbBodies = this.rigidBodies.size(); + if (this.numberBodiesCapacity != nbBodies hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj nbBodies > 0) { + if (this.numberBodiesCapacity > 0) { + this.splitLinearVelocities.clear(); + this.splitAngularVelocities.clear(); + } + this.numberBodiesCapacity = nbBodies; + this.splitLinearVelocities.clear(); + this.splitAngularVelocities.clear(); + this.rainedLinearVelocities.clear(); + this.rainedAngularVelocities.clear(); + this.rainedPositions.clear(); + this.rainedOrientations.clear(); + this.splitLinearVelocities.resize(this.numberBodiesCapacity, vec3(0,0,0)); + this.splitAngularVelocities.resize(this.numberBodiesCapacity, vec3(0,0,0)); + this.rainedLinearVelocities.resize(this.numberBodiesCapacity, vec3(0,0,0)); + this.rainedAngularVelocities.resize(this.numberBodiesCapacity, vec3(0,0,0)); + this.rainedPositions.resize(this.numberBodiesCapacity, vec3(0,0,0)); + this.rainedOrientations.resize(this.numberBodiesCapacity, etk::Quaternion::identity()); + } + // Reset the velocities arrays + for (int i=0; i::Iterator it; + int indexBody = 0; + for (it = this.rigidBodies.begin(); it != this.rigidBodies.end(); ++it) { + // Add the body into the map + this.mapBodyToConstrainedVelocityIndex.add(*it, indexBody); + indexBody++; + } +} + +void ephysics::DynamicsWorld::integrateRigidBodiesVelocities() { + PROFILE("ephysics::DynamicsWorld::integrateRigidBodiesVelocities()"); + // Initialize the bodies velocity arrays + initVelocityArrays(); + // For each island of the world + for (int i=0; i < this.islands.size(); i++) { + RigidBody** bodies = this.islands[i]->getBodies(); + // For each body of the island + for (int b=0; b < this.islands[i]->getNbBodies(); b++) { + // Insert the body into the map of rained velocities + int indexBody = this.mapBodyToConstrainedVelocityIndex.find(bodies[b])->second; + assert(this.splitLinearVelocities[indexBody] == vec3(0, 0, 0)); + assert(this.splitAngularVelocities[indexBody] == vec3(0, 0, 0)); + // Integrate the external force to get the new velocity of the body + this.rainedLinearVelocities[indexBody] = bodies[b]->getLinearVelocity(); + this.rainedLinearVelocities[indexBody] += bodies[b]->this.massInverse * bodies[b]->this.externalForce * this.timeStep; + this.rainedAngularVelocities[indexBody] = bodies[b]->getAngularVelocity(); + this.rainedAngularVelocities[indexBody] += bodies[b]->getInertiaTensorInverseWorld() * bodies[b]->this.externalTorque * this.timeStep; + // If the gravity has to be applied to this rigid body + if (bodies[b]->isGravityEnabled() hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.isGravityEnabled) { + // Integrate the gravity force + this.rainedLinearVelocities[indexBody] += this.timeStep * bodies[b]->this.massInverse * bodies[b]->getMass() * this.gravity; + } + // Apply the velocity damping + // Damping force : Fc = -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) + float linDampingFactor = bodies[b]->getLinearDamping(); + float angDampingFactor = bodies[b]->getAngularDamping(); + float linearDamping = pow(1.0f - linDampingFactor, this.timeStep); + float angularDamping = pow(1.0f - angDampingFactor, this.timeStep); + this.rainedLinearVelocities[indexBody] *= linearDamping; + this.rainedAngularVelocities[indexBody] *= angularDamping; + indexBody++; + } + } +} + +void ephysics::DynamicsWorld::solveContactsAndConstraints() { + PROFILE("ephysics::DynamicsWorld::solveContactsAndConstraints()"); + // Set the velocities arrays + this.contactSolver.setSplitVelocitiesArrays(this.splitLinearVelocities[0], this.splitAngularVelocities[0]); + this.contactSolver.setConstrainedVelocitiesArrays(this.rainedLinearVelocities[0], + this.rainedAngularVelocities[0]); + this.raintSolver.setConstrainedVelocitiesArrays(this.rainedLinearVelocities[0], + this.rainedAngularVelocities[0]); + this.raintSolver.setConstrainedPositionsArrays(this.rainedPositions[0], + this.rainedOrientations[0]); + // ---------- Solve velocity raints for joints and contacts ---------- // + // For each island of the world + for (int islandIndex = 0; islandIndex < this.islands.size(); islandIndex++) { + // Check if there are contacts and raints to solve + boolean isConstraintsToSolve = this.islands[islandIndex]->getNbJoints() > 0; + boolean isContactsToSolve = this.islands[islandIndex]->getNbContactManifolds() > 0; + if (!isConstraintsToSolve hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj !isContactsToSolve) { + continue; + } + // If there are contacts in the current island + if (isContactsToSolve) { + // Initialize the solver + this.contactSolver.initializeForIsland(this.timeStep, this.islands[islandIndex]); + // Warm start the contact solver + this.contactSolver.warmStart(); + } + // If there are raints + if (isConstraintsToSolve) { + // Initialize the raint solver + this.raintSolver.initializeForIsland(this.timeStep, this.islands[islandIndex]); + } + // For each iteration of the velocity solver + for (int i=0; iremoveAllCollisionShapes(); + // Add the body ID to the list of free IDs + this.freeBodiesIDs.pushBack(rigidBody->getID()); + // Destroy all the joints in which the rigid body to be destroyed is involved + for (ephysics::JointListElement* element = rigidBody->this.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.erase(this.bodies.find(rigidBody)); + this.rigidBodies.erase(this.rigidBodies.find(rigidBody)); + // Call the destructor of the rigid body + ETKDELETE(RigidBody, rigidBody); + rigidBody = null; +} + +ephysics::Joint* ephysics::DynamicsWorld::createJoint( ephysics::JointInfo jointInfo) { + Joint* newJoint = null; + // Allocate memory to create the new joint + switch(jointInfo.type) { + // Ball-and-Socket joint + case BALLSOCKETJOINT: + newJoint = ETKNEW(BallAndSocketJoint, staticcast< ephysics::BallAndSocketJointInfo>(jointInfo)); + break; + // Slider joint + case SLIDERJOINT: + newJoint = ETKNEW(SliderJoint, staticcast< ephysics::SliderJointInfo>(jointInfo)); + break; + // Hinge joint + case HINGEJOINT: + newJoint = ETKNEW(HingeJoint, staticcast< ephysics::HingeJointInfo>(jointInfo)); + break; + // Fixed joint + case FIXEDJOINT: + newJoint = ETKNEW(FixedJoint, staticcast< ephysics::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; +} + +void ephysics::DynamicsWorld::destroyJoint(Joint* joint) { + if (joint == null) { + EPHYWARNING("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.erase(this.joints.find(joint)); + // Remove the joint from the joint list of the bodies involved in the joint + joint->this.body1->removeJointFrothis.jointsList(joint); + joint->this.body2->removeJointFrothis.jointsList(joint); + sizet nbBytes = joint->getSizeInBytes(); + // Call the destructor of the joint + ETKDELETE(Joint, joint); + joint = null; +} + +void ephysics::DynamicsWorld::addJointToBody(ephysics::Joint* joint) { + if (joint == null) { + EPHYWARNING("Request add null joint"); + return; + } + // Add the joint at the beginning of the linked list of joints of the first body + joint->this.body1->this.jointsList = ETKNEW(JointListElement, joint, joint->this.body1->this.jointsList); + // Add the joint at the beginning of the linked list of joints of the second body + joint->this.body2->this.jointsList = ETKNEW(JointListElement, joint, joint->this.body2->this.jointsList); +} + +void ephysics::DynamicsWorld::computeIslands() { + PROFILE("ephysics::DynamicsWorld::computeIslands()"); + int nbBodies = this.rigidBodies.size(); + // Clear all the islands + for (auto it: this.islands) { + ETKDELETE(Island, it); + it = null; + } + // Call the island destructor + this.islands.clear(); + int nbContactManifolds = 0; + // Reset all the isAlreadyInIsland variables of bodies, joints and contact manifolds + for (etk::Set::Iterator it = this.rigidBodies.begin(); it != this.rigidBodies.end(); ++it) { + int nbBodyManifolds = (*it)->resetIsAlreadyInIslandAndCountManifolds(); + nbContactManifolds += nbBodyManifolds; + } + for (etk::Set::Iterator it = this.joints.begin(); it != this.joints.end(); ++it) { + (*it)->this.isAlreadyInIsland = false; + } + // Create a stack (using an array) for the rigid bodies to visit during the Depth First Search + etk::Vector stackBodiesToVisit; + stackBodiesToVisit.resize(nbBodies, null); + // For each rigid body of the world + for (etk::Set::Iterator it = this.rigidBodies.begin(); it != this.rigidBodies.end(); ++it) { + ephysics::RigidBody* body = *it; + // If the body has already been added to an island, we go to the next body + if (body->this.isAlreadyInIsland) { + continue; + } + // If the body is static, we go to the next body + if (body->getType() == 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[stackIndex] = body; + stackIndex++; + body->this.isAlreadyInIsland = true; + // Create the new island + this.islands.pushBack(ETKNEW(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--; + ephysics::RigidBody* bodyToVisit = stackBodiesToVisit[stackIndex]; + assert(bodyToVisit->isActive()); + // Awake the body if it is slepping + bodyToVisit->setIsSleeping(false); + // Add the body into the island + this.islands.back()->addBody(bodyToVisit); + // If the current body is static, we do not want to perform the DFS + // search across that body + if (bodyToVisit->getType() == STATIC) { + continue; + } + // For each contact manifold in which the current body is involded + ephysics::ContactManifoldListElement* contactElement; + for (contactElement = bodyToVisit->this.contactManifoldsList; + contactElement != null; + contactElement = contactElement->next) { + ephysics::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.back()->addContactManifold(contactManifold); + contactManifold->this.isAlreadyInIsland = true; + // Get the other body of the contact manifold + ephysics::RigidBody* body1 = staticcast(contactManifold->getBody1()); + ephysics::RigidBody* body2 = staticcast(contactManifold->getBody2()); + ephysics::RigidBody* otherBody = (body1->getID() == bodyToVisit->getID()) ? body2 : body1; + // Check if the other body has already been added to the island + if (otherBody->this.isAlreadyInIsland) { + continue; + } + // Insert the other body into the stack of bodies to visit + stackBodiesToVisit[stackIndex] = otherBody; + stackIndex++; + otherBody->this.isAlreadyInIsland = true; + } + // For each joint in which the current body is involved + ephysics::JointListElement* jointElement; + for (jointElement = bodyToVisit->this.jointsList; + jointElement != null; + jointElement = jointElement->next) { + ephysics::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.back()->addJoint(joint); + joint->this.isAlreadyInIsland = true; + // Get the other body of the contact manifold + ephysics::RigidBody* body1 = staticcast(joint->getBody1()); + ephysics::RigidBody* body2 = staticcast(joint->getBody2()); + ephysics::RigidBody* otherBody = (body1->getID() == bodyToVisit->getID()) ? body2 : body1; + // Check if the other body has already been added to the island + if (otherBody->this.isAlreadyInIsland) continue; + // Insert the other body into the stack of bodies to visit + stackBodiesToVisit[stackIndex] = otherBody; + stackIndex++; + otherBody->this.isAlreadyInIsland = true; + } + } + this.islands.back()->resetStaticBobyNotInIsland(); + } +} + +void ephysics::DynamicsWorld::updateSleepingBodies() { + PROFILE("ephysics::DynamicsWorld::updateSleepingBodies()"); + float sleepLinearVelocitySquare = this.sleepLinearVelocity * this.sleepLinearVelocity; + float sleepAngularVelocitySquare = this.sleepAngularVelocity * this.sleepAngularVelocity; + // For each island of the world + for (int i=0; igetBodies(); + for (int b=0; b < this.islands[i]->getNbBodies(); b++) { + // Skip static bodies + if (bodies[b]->getType() == STATIC) continue; + // If the body is velocity is large enough to stay awake + if (bodies[b]->getLinearVelocity().length2() > sleepLinearVelocitySquare || + bodies[b]->getAngularVelocity().length2() > sleepAngularVelocitySquare || + !bodies[b]->isAllowedToSleep()) { + // Reset the sleep time of the body + bodies[b]->this.sleepTime = 0.0f; + minSleepTime = 0.0f; + } else { // If the body velocity is bellow the sleeping velocity threshold + // Increase the sleep time + bodies[b]->this.sleepTime += this.timeStep; + if (bodies[b]->this.sleepTime < minSleepTime) { + minSleepTime = bodies[b]->this.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[i]->getNbBodies(); b++) { + bodies[b]->setIsSleeping(true); + } + } + } +} + +void ephysics::DynamicsWorld::enableSleeping(boolean isSleepingEnabled) { + this.isSleepingEnabled = isSleepingEnabled; + if (!this.isSleepingEnabled) { + // For each body of the world + etk::Set::Iterator it; + for (it = this.rigidBodies.begin(); it != this.rigidBodies.end(); ++it) { + // Wake up the rigid body + (*it)->setIsSleeping(false); + } + } +} + +void ephysics::DynamicsWorld::testCollision( ephysics::ProxyShape* shape, ephysics::CollisionCallback* callback) { + // Create the sets of shapes + etk::Set shapes; + shapes.add(shape->this.broadPhaseID); + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(callback, shapes, emptySet); +} + +void ephysics::DynamicsWorld::testCollision( ephysics::ProxyShape* shape1, ephysics::ProxyShape* shape2, ephysics::CollisionCallback* callback) { + // Create the sets of shapes + etk::Set shapes1; + shapes1.add(shape1->this.broadPhaseID); + etk::Set shapes2; + shapes2.add(shape2->this.broadPhaseID); + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(callback, shapes1, shapes2); +} + +void ephysics::DynamicsWorld::testCollision( ephysics::CollisionBody* body, ephysics::CollisionCallback* callback) { + // Create the sets of shapes + etk::Set shapes1; + // For each shape of the body + for ( ProxyShape* shape = body->getProxyShapesList(); + shape != null; + shape = shape->getNext()) { + shapes1.add(shape->this.broadPhaseID); + } + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(callback, shapes1, emptySet); +} + +void ephysics::DynamicsWorld::testCollision( ephysics::CollisionBody* body1, ephysics::CollisionBody* body2, ephysics::CollisionCallback* callback) { + // Create the sets of shapes + etk::Set shapes1; + for ( ProxyShape* shape=body1->getProxyShapesList(); shape != null; + shape = shape->getNext()) { + shapes1.add(shape->this.broadPhaseID); + } + etk::Set shapes2; + for ( ProxyShape* shape=body2->getProxyShapesList(); shape != null; + shape = shape->getNext()) { + shapes2.add(shape->this.broadPhaseID); + } + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(callback, shapes1, shapes2); +} + +void ephysics::DynamicsWorld::testCollision(ephysics::CollisionCallback* callback) { + etk::Set emptySet; + // Perform the collision detection and report contacts + this.collisionDetection.reportCollisionBetweenShapes(callback, emptySet, emptySet); +} + +etk::Vector< ephysics::ContactManifold*> ephysics::DynamicsWorld::getContactsList() { + etk::Vector< ephysics::ContactManifold*> contactManifolds; + // For each currently overlapping pair of bodies + etk::Map::Iterator it; + for (it = this.collisionDetection.this.overlappingPairs.begin(); + it != this.collisionDetection.this.overlappingPairs.end(); + ++it) { + ephysics::OverlappingPair* pair = it->second; + // For each contact manifold of the pair + ephysics::ContactManifoldSet manifoldSet = pair->getContactManifoldSet(); + for (int i=0; i::Iterator it; + for (it = this.rigidBodies.begin(); it != this.rigidBodies.end(); ++it) { + (*it)->this.externalForce.setZero(); + (*it)->this.externalTorque.setZero(); + } +} + +int ephysics::DynamicsWorld::getNbIterationsVelocitySolver() { + return this.nbVelocitySolverIterations; +} + +void ephysics::DynamicsWorld::setNbIterationsVelocitySolver(int nbIterations) { + this.nbVelocitySolverIterations = nbIterations; +} + +int ephysics::DynamicsWorld::getNbIterationsPositionSolver() { + return this.nbPositionSolverIterations; +} + +void ephysics::DynamicsWorld::setNbIterationsPositionSolver(int nbIterations) { + this.nbPositionSolverIterations = nbIterations; +} + +void ephysics::DynamicsWorld::setContactsPositionCorrectionTechnique(ephysics::ContactsPositionCorrectionTechnique technique) { + if (technique == BAUMGARTECONTACTS) { + this.contactSolver.setIsSplitImpulseActive(false); + } else { + this.contactSolver.setIsSplitImpulseActive(true); + } +} + +void ephysics::DynamicsWorld::setJointsPositionCorrectionTechnique(ephysics::JointsPositionCorrectionTechnique technique) { + if (technique == BAUMGARTEJOINTS) { + this.raintSolver.setIsNonLinearGaussSeidelPositionCorrectionActive(false); + } else { + this.raintSolver.setIsNonLinearGaussSeidelPositionCorrectionActive(true); + } +} + +void ephysics::DynamicsWorld::setIsSolveFrictionAtContactManifoldCenterActive(boolean isActive) { + this.contactSolver.setIsSolveFrictionAtContactManifoldCenterActive(isActive); +} + +vec3 ephysics::DynamicsWorld::getGravity() { + return this.gravity; +} + +void ephysics::DynamicsWorld::setGravity(vec3 gravity) { + this.gravity = gravity; +} + +boolean ephysics::DynamicsWorld::isGravityEnabled() { + return this.isGravityEnabled; +} + +void ephysics::DynamicsWorld::setIsGratityEnabled(boolean isGravityEnabled) { + this.isGravityEnabled = isGravityEnabled; +} + +int ephysics::DynamicsWorld::getNbRigidBodies() { + return this.rigidBodies.size(); +} + +int ephysics::DynamicsWorld::getNbJoints() { + return this.joints.size(); +} + +etk::Set::Iterator ephysics::DynamicsWorld::getRigidBodiesBeginIterator() { + return this.rigidBodies.begin(); +} + +etk::Set::Iterator ephysics::DynamicsWorld::getRigidBodiesEndIterator() { + return this.rigidBodies.end(); +} + +boolean ephysics::DynamicsWorld::isSleepingEnabled() { + return this.isSleepingEnabled; +} + +float ephysics::DynamicsWorld::getSleepLinearVelocity() { + return this.sleepLinearVelocity; +} + +void ephysics::DynamicsWorld::setSleepLinearVelocity(float sleepLinearVelocity) { + if(sleepLinearVelocity < 0.0f) { + Log.error("Can not set sleepLinearVelocity=" << sleepLinearVelocity << " < 0 "); + return; + } + this.sleepLinearVelocity = sleepLinearVelocity; +} + +float ephysics::DynamicsWorld::getSleepAngularVelocity() { + return this.sleepAngularVelocity; +} + +void ephysics::DynamicsWorld::setSleepAngularVelocity(float sleepAngularVelocity) { + if(sleepAngularVelocity < 0.0f) { + Log.error("Can not set sleepAngularVelocity=" << sleepAngularVelocity << " < 0 "); + return; + } + this.sleepAngularVelocity = sleepAngularVelocity; +} + +float ephysics::DynamicsWorld::getTimeBeforeSleep() { + return this.timeBeforeSleep; +} + +void ephysics::DynamicsWorld::setTimeBeforeSleep(float timeBeforeSleep) { + if(timeBeforeSleep < 0.0f) { + Log.error("Can not set timeBeforeSleep=" << timeBeforeSleep << " < 0 "); + return; + } + this.timeBeforeSleep = timeBeforeSleep; +} + +void ephysics::DynamicsWorld::setEventListener(ephysics::EventListener* eventListener) { + this.eventListener = eventListener; +} + diff --git a/src/org/atriaSoft/ephysics/engine/DynamicsWorld.hpp b/src/org/atriaSoft/ephysics/engine/DynamicsWorld.hpp new file mode 100644 index 0000000..33f23f5 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/DynamicsWorld.hpp @@ -0,0 +1,297 @@ +/** @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) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace ephysics { + /** + * @brief 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. + */ + class DynamicsWorld : public CollisionWorld { + protected : + ContactSolver this.contactSolver; //!< Contact solver + ConstraintSolver this.raintSolver; //!< Constraint solver + int this.nbVelocitySolverIterations; //!< Number of iterations for the velocity solver of the Sequential Impulses technique + int this.nbPositionSolverIterations; //!< Number of iterations for the position solver of the Sequential Impulses technique + boolean this.isSleepingEnabled; //!< True if the spleeping technique for inactive bodies is enabled + etk::Set this.rigidBodies; //!< All the rigid bodies of the physics world + etk::Set this.joints; //!< All the joints of the world + vec3 this.gravity; //!< Gravity vector of the world + float this.timeStep; //!< Current frame time step (in seconds) + boolean this.isGravityEnabled; //!< True if the gravity force is on + etk::Vector this.rainedLinearVelocities; //!< Array of rained linear velocities (state of the linear velocities after solving the raints) + etk::Vector this.rainedAngularVelocities; //!< Array of rained angular velocities (state of the angular velocities after solving the raints) + etk::Vector this.splitLinearVelocities; //!< Split linear velocities for the position contact solver (split impulse) + etk::Vector this.splitAngularVelocities; //!< Split angular velocities for the position contact solver (split impulse) + etk::Vector this.rainedPositions; //!< Array of rained rigid bodies position (for position error correction) + etk::Vector this.rainedOrientations; //!< Array of rained rigid bodies orientation (for position error correction) + etk::Map this.mapBodyToConstrainedVelocityIndex; //!< Map body to their index in the rained velocities array + etk::Vector this.islands; //!< Array with all the islands of awaken bodies + int this.numberBodiesCapacity; //!< Current allocated capacity for the bodies + float this.sleepLinearVelocity; //!< Sleep linear velocity threshold + float this.sleepAngularVelocity; //!< Sleep angular velocity threshold + float this.timeBeforeSleep; //!< Time (in seconds) before a body is put to sleep if its velocity becomes smaller than the sleep velocity. + /// Private copy-ructor + DynamicsWorld( DynamicsWorld world) = delete; + /// Private assignment operator + DynamicsWorld operator=( DynamicsWorld world) = delete; + /** + * @brief Integrate position and orientation of the rigid bodies. + * The positions and orientations of the bodies are integrated using + * the sympletic Euler time stepping scheme. + */ + void integrateRigidBodiesPositions(); + /** + * @brief Reset the external force and torque applied to the bodies + */ + void resetBodiesForceAndTorque(); + /** + * @brief Initialize the bodies velocities arrays for the next simulation step. + */ + void initVelocityArrays(); + /** + * @brief 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. + */ + void integrateRigidBodiesVelocities(); + /** + * @brief Solve the contacts and raints + */ + void solveContactsAndConstraints(); + /** + * @brief Solve the position error correction of the raints + */ + void solvePositionCorrection(); + /** + * @brief 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. + */ + void computeIslands(); + /** + * @brief Update the postion/orientation of the bodies + */ + void updateBodiesState(); + /** + * @brief Put bodies to sleep if needed. + * For each island, if all the bodies have been almost still for a long enough period of + * time, we put all the bodies of the island to sleep. + */ + void updateSleepingBodies(); + /** + * @brief 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. + */ + void addJointToBody(Joint* joint); + public : + /** + * @brief Constructor + * @param gravity Gravity vector in the world (in meters per second squared) + */ + DynamicsWorld( vec3 gravity); + /** + * @brief Vitualize the Destructor + */ + virtual ~DynamicsWorld(); + /** + * @brief Update the physics simulation + * @param timeStep The amount of time to step the simulation by (in seconds) + */ + void update(float timeStep); + /** + * @brief Get the number of iterations for the velocity raint solver + * @return Number if iteration. + */ + int getNbIterationsVelocitySolver() ; + /** + * @brief Set the number of iterations for the velocity raint solver + * @param[in] nbIterations Number of iterations for the velocity solver + */ + void setNbIterationsVelocitySolver(int nbIterations); + /** + * @brief Get the number of iterations for the position raint solver + */ + int getNbIterationsPositionSolver() ; + /** + * @brief Set the number of iterations for the position raint solver + * @param[in] nbIterations Number of iterations for the position solver + */ + void setNbIterationsPositionSolver(int nbIterations); + /** + * @brief Set the position correction technique used for contacts + * @param[in] technique Technique used for the position correction (Baumgarte or Split Impulses) + */ + void setContactsPositionCorrectionTechnique(ContactsPositionCorrectionTechnique technique); + /** + * @brief Set the position correction technique used for joints + * @param[in] technique Technique used for the joins position correction (Baumgarte or Non Linear Gauss Seidel) + */ + void setJointsPositionCorrectionTechnique(JointsPositionCorrectionTechnique technique); + /** + * @brief Activate or deactivate the solving of friction raints at the center of the contact + * manifold instead of solving them at each contact point + * @param[in] isActive True if you want the friction to be solved at the center of + * the contact manifold and false otherwise + */ + void setIsSolveFrictionAtContactManifoldCenterActive(boolean isActive); + /** + * @brief Create a rigid body into the physics world + * @param[in] transform etk::Transform3Dation from body local-space to world-space + * @return A pointer to the body that has been created in the world + */ + RigidBody* createRigidBody( etk::Transform3D transform); + /** + * @brief Destroy a rigid body and all the joints which it belongs + * @param[in,out] rigidBody Pointer to the body you want to destroy + */ + void destroyRigidBody(RigidBody* rigidBody); + /** + * @brief Create a joint between two bodies in the world and return a pointer to the new joint + * @param[in] jointInfo The information that is necessary to create the joint + * @return A pointer to the joint that has been created in the world + */ + Joint* createJoint( JointInfo jointInfo); + /** + * @brief Destroy a joint + * @param[in,out] joint Pointer to the joint you want to destroy + */ + void destroyJoint(Joint* joint); + /** + * @brief Get the gravity vector of the world + * @return The current gravity vector (in meter per seconds squared) + */ + vec3 getGravity() ; + /** + * @brief Set the gravity vector of the world + * @param[in] gravity The gravity vector (in meter per seconds squared) + */ + void setGravity(vec3 gravity); + /** + * @brief Get if the gravity is enaled + * @return True if the gravity is enabled in the world + */ + boolean isGravityEnabled() ; + /** + * @brief Enable/Disable the gravity + * @param[in] isGravityEnabled True if you want to enable the gravity in the world + * and false otherwise + */ + void setIsGratityEnabled(boolean isGravityEnabled); + /** + * @brief Get the number of rigid bodies in the world + * @return Number of rigid bodies in the world + */ + int getNbRigidBodies() ; + /** + * @brief Get the number of all joints + * @return Number of joints in the world + */ + int getNbJoints() ; + /** + * @brief Get an iterator to the beginning of the bodies of the physics world + * @return Starting iterator of the set of rigid bodies + */ + etk::Set::Iterator getRigidBodiesBeginIterator(); + /** + * @brief Get an iterator to the end of the bodies of the physics world + * @return Ending iterator of the set of rigid bodies + */ + etk::Set::Iterator getRigidBodiesEndIterator(); + /** + * @brief Get if the sleeping technique is enabled + * @return True if the sleeping technique is enabled and false otherwise + */ + boolean isSleepingEnabled() ; + /** + * @brief 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[in] isSleepingEnabled True if you want to enable the sleeping technique and false otherwise + */ + void enableSleeping(boolean isSleepingEnabled); + /** + * @brief Get the sleep linear velocity + * @return The current sleep linear velocity (in meters per second) + */ + float getSleepLinearVelocity() ; + /** + * @brief Set the sleep linear velocity + * @param[in] sleepLinearVelocity The new sleep linear velocity (in meters per second) + */ + void setSleepLinearVelocity(float sleepLinearVelocity); + /** + * @brief Return the current sleep angular velocity + * @return The sleep angular velocity (in radian per second) + */ + float getSleepAngularVelocity() ; + /** + * @brief 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[in] sleepAngularVelocity The sleep angular velocity (in radian per second) + */ + void setSleepAngularVelocity(float sleepAngularVelocity); + /** + * @brief 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) + */ + float getTimeBeforeSleep() ; + /** + * @brief 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) + */ + void setTimeBeforeSleep(float timeBeforeSleep); + /** + * @brief Set an event listener object to receive events callbacks. + * @note If you use null as an argument, the events callbacks will be disabled. + * @param[in] eventListener Pointer to the event listener object that will receive + * event callbacks during the simulation + */ + void setEventListener(EventListener* eventListener); + void testCollision( ProxyShape* shape, + CollisionCallback* callback) override; + virtual void testCollision( ProxyShape* shape1, + ProxyShape* shape2, + CollisionCallback* callback) override; + virtual void testCollision( CollisionBody* body, + CollisionCallback* callback) override; + virtual void testCollision( CollisionBody* body1, + CollisionBody* body2, + CollisionCallback* callback) override; + /// Test and report collisions between all shapes of the world + virtual void testCollision(CollisionCallback* callback) override; + /** + * @brief Get list of all contacts. + * @return The list of all contacts of the world + */ + etk::Vector< ContactManifold*> getContactsList() ; + friend class RigidBody; + }; + + + +} + diff --git a/src/org/atriaSoft/ephysics/engine/EventListener.hpp b/src/org/atriaSoft/ephysics/engine/EventListener.hpp new file mode 100644 index 0000000..ce9b237 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/EventListener.hpp @@ -0,0 +1,56 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + /** + * @brief 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 override the methods you need. Then, you need to register your + * new event listener class to the physics world using the DynamicsWorld::setEventListener() + * method. + */ + class EventListener { + public : + /** + * @brief Generic Constructor + */ + EventListener() {} + /** + * @brief Generic Desstructor take it virtual + */ + virtual ~EventListener() =default; + /** + * @brief Called when a new contact point is found between two bodies that were separated before + * @param contact Information about the contact + */ + virtual void beginContact( ContactPointInfo contact) {}; + /** + * @brief Called when a new contact point is found between two bodies + * @param contact Information about the contact + */ + virtual void newContact( ContactPointInfo contact) {} + /** + * @brief 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. + */ + virtual void beginInternalTick() {} + /** + * @brief 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. + */ + virtual void endInternalTick() {} + }; +} diff --git a/src/org/atriaSoft/ephysics/engine/Impulse.hpp b/src/org/atriaSoft/ephysics/engine/Impulse.hpp new file mode 100644 index 0000000..c31a1aa --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Impulse.hpp @@ -0,0 +1,46 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + /** + * @brief Represents an impulse that we can apply to bodies in the contact or raint solver. + */ + class Impulse { + private: + /// Private assignment operator + Impulse operator=( Impulse impulse); + public: + vec3 linearImpulseBody1; //!< Linear impulse applied to the first body + vec3 angularImpulseBody1; //!< Angular impulse applied to the first body + vec3 linearImpulseBody2; //!< Linear impulse applied to the second body + vec3 angularImpulseBody2; //!< Angular impulse applied to the second body + /// Constructor + Impulse( vec3 initLinearImpulseBody1, + vec3 initAngularImpulseBody1, + vec3 initLinearImpulseBody2, + vec3 initAngularImpulseBody2): + linearImpulseBody1(initLinearImpulseBody1), + angularImpulseBody1(initAngularImpulseBody1), + linearImpulseBody2(initLinearImpulseBody2), + angularImpulseBody2(initAngularImpulseBody2) { + + } + /// Copy-ructor + Impulse( Impulse impulse): + linearImpulseBody1(impulse.linearImpulseBody1), + angularImpulseBody1(impulse.angularImpulseBody1), + linearImpulseBody2(impulse.linearImpulseBody2), + angularImpulseBody2(impulse.angularImpulseBody2) { + + } + }; +} diff --git a/src/org/atriaSoft/ephysics/engine/Island.cpp b/src/org/atriaSoft/ephysics/engine/Island.cpp new file mode 100644 index 0000000..6f8e572 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Island.cpp @@ -0,0 +1,70 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +ephysics::Island::Island(sizet nbMaxBodies, + sizet nbMaxContactManifolds, + sizet nbMaxJoints) { + // Allocate memory for the arrays + this.bodies.reserve(nbMaxBodies); + this.contactManifolds.reserve(nbMaxContactManifolds); + this.joints.reserve(nbMaxJoints); +} + + +void ephysics::Island::addBody(ephysics::RigidBody* body) { + if (body->isSleeping() == true) { + Log.error("Try to add a body that is sleeping ..."); + return; + } + this.bodies.pushBack(body); +} + +void ephysics::Island::addContactManifold(ephysics::ContactManifold* contactManifold) { + this.contactManifolds.pushBack(contactManifold); +} + +void ephysics::Island::addJoint(ephysics::Joint* joint) { + this.joints.pushBack(joint); +} + +sizet ephysics::Island::getNbBodies() { + return this.bodies.size(); +} + +sizet ephysics::Island::getNbContactManifolds() { + return this.contactManifolds.size(); +} + +sizet ephysics::Island::getNbJoints() { + return this.joints.size(); +} + +ephysics::RigidBody** ephysics::Island::getBodies() { + return this.bodies[0]; +} + +ephysics::ContactManifold** ephysics::Island::getContactManifold() { + return this.contactManifolds[0]; +} + +ephysics::Joint** ephysics::Island::getJoints() { + return this.joints[0]; +} + +void ephysics::Island::resetStaticBobyNotInIsland() { + for (auto it: this.bodies) { + if (it->getType() == STATIC) { + it->this.isAlreadyInIsland = false; + } + } +} diff --git a/src/org/atriaSoft/ephysics/engine/Island.hpp b/src/org/atriaSoft/ephysics/engine/Island.hpp new file mode 100644 index 0000000..d44b8ae --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Island.hpp @@ -0,0 +1,83 @@ +/** @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) + */ +#pragma once + +#include +#include +#include + +namespace ephysics { + /** + * @brief An island represent an isolated group of awake bodies that are connected with each other by + * some contraints (contacts or joints). + */ + class Island { + private: + etk::Vector this.bodies; //!< Array with all the bodies of the island + etk::Vector this.contactManifolds; //!< Array with all the contact manifolds between bodies of the island + etk::Vector this.joints; //!< Array with all the joints between bodies of the island + //! Remove assignment operator + Island operator=( Island island) = delete; + //! Remove copy-ructor + Island( Island island) = delete; + public: + /** + * @brief Constructor + */ + Island(sizet nbMaxBodies, sizet nbMaxContactManifolds, sizet nbMaxJoints); + /** + * @brief Destructor + */ + ~Island() = default; + /** + * Add a body. + */ + void addBody(RigidBody* body); + /** + * Add a contact manifold. + */ + void addContactManifold(ContactManifold* contactManifold); + /** + * Add a joint. + */ + void addJoint(Joint* joint); + /** + * @brief Get the number of body + * @return Number of bodies. + */ + sizet getNbBodies() ; + /** + * @ Get the number of contact manifolds + * Return the number of contact manifolds in the island + */ + sizet getNbContactManifolds() ; + /** + * Return the number of joints in the island + */ + sizet getNbJoints() ; + /** + * Return a pointer to the array of bodies + */ + RigidBody** getBodies(); + /** + * Return a pointer to the array of contact manifolds + */ + ContactManifold** getContactManifold(); + /** + * Return a pointer to the array of joints + */ + Joint** getJoints(); + /** + * @brief Reset the isAlreadyIsland variable of the static bodies so that they can also be included in the other islands + */ + void resetStaticBobyNotInIsland(); + }; + + +} diff --git a/src/org/atriaSoft/ephysics/engine/Material.cpp b/src/org/atriaSoft/ephysics/engine/Material.cpp new file mode 100644 index 0000000..68ec4f1 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Material.cpp @@ -0,0 +1,100 @@ +/** @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) + */ +#include + +using namespace ephysics; + +// Constructor +Material::Material() + : this.frictionCoefficient(DEFAULTFRICTIONCOEFFICIENT), + this.rollingResistance(DEFAULTROLLINGRESISTANCE), + this.bounciness(DEFAULTBOUNCINESS) { + +} + +// Copy-ructor +Material::Material( Material material) + : this.frictionCoefficient(material.this.frictionCoefficient), + this.rollingResistance(material.this.rollingResistance), this.bounciness(material.this.bounciness) { + +} + +// Return the bounciness +/** + * @return Bounciness factor (between 0 and 1) where 1 is very bouncy + */ +float Material::getBounciness() { + return this.bounciness; +} + +// 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 + */ +void Material::setBounciness(float bounciness) { + assert(bounciness >= 0.0f hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj bounciness <= 1.0f); + this.bounciness = bounciness; +} + +// Return the friction coefficient +/** + * @return Friction coefficient (positive value) + */ +float Material::getFrictionCoefficient() { + return this.frictionCoefficient; +} + +// 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) + */ +void Material::setFrictionCoefficient(float frictionCoefficient) { + assert(frictionCoefficient >= 0.0f); + this.frictionCoefficient = 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) + */ +float Material::getRollingResistance() { + return this.rollingResistance; +} + +// 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 + */ +void Material::setRollingResistance(float rollingResistance) { + assert(rollingResistance >= 0); + this.rollingResistance = rollingResistance; +} + +// Overloaded assignment operator +Material Material::operator=( Material material) { + + // Check for self-assignment + if (this != material) { + this.frictionCoefficient = material.this.frictionCoefficient; + this.bounciness = material.this.bounciness; + this.rollingResistance = material.this.rollingResistance; + } + + // Return this material + return *this; +} + diff --git a/src/org/atriaSoft/ephysics/engine/Material.hpp b/src/org/atriaSoft/ephysics/engine/Material.hpp new file mode 100644 index 0000000..b297dfe --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Material.hpp @@ -0,0 +1,47 @@ +/** @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) + */ +#pragma once +extern "C" { + #include +} +#include + +namespace ephysics { + /** + * This class contains the material properties of a rigid body that will be use for + * the dynamics simulation like the friction coefficient or the bounciness of the rigid + * body. + */ + class Material { + private : + float this.frictionCoefficient; //!< Friction coefficient (positive value) + float this.rollingResistance; //!< Rolling resistance factor (positive value) + float this.bounciness; //!< Bounciness during collisions (between 0 and 1) where 1 is for a very bouncy body + public : + /// Constructor + Material(); + /// Copy-ructor + Material( Material material); + /// Return the bounciness + float getBounciness() ; + /// Set the bounciness. + void setBounciness(float bounciness); + /// Return the friction coefficient + float getFrictionCoefficient() ; + /// Set the friction coefficient. + void setFrictionCoefficient(float frictionCoefficient); + /// Return the rolling resistance factor + float getRollingResistance() ; + /// Set the rolling resistance factor + void setRollingResistance(float rollingResistance); + /// Overloaded assignment operator + Material operator=( Material material); + }; + +} diff --git a/src/org/atriaSoft/ephysics/engine/OverlappingPair.cpp b/src/org/atriaSoft/ephysics/engine/OverlappingPair.cpp new file mode 100644 index 0000000..7a23007 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/OverlappingPair.cpp @@ -0,0 +1,75 @@ +/** @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) + */ +#include + +using namespace ephysics; + +OverlappingPair::OverlappingPair(ProxyShape* shape1, ProxyShape* shape2, int nbMaxContactManifolds): + this.contactManifoldSet(shape1, shape2, nbMaxContactManifolds), + this.cachedSeparatingAxis(1.0, 1.0, 1.0) { + +} + +ProxyShape* OverlappingPair::getShape1() { + return this.contactManifoldSet.getShape1(); +} + +ProxyShape* OverlappingPair::getShape2() { + return this.contactManifoldSet.getShape2(); +} + +void OverlappingPair::addContact(ContactPoint* contact) { + this.contactManifoldSet.addContactPoint(contact); +} + +void OverlappingPair::update() { + this.contactManifoldSet.update(); +} + +vec3 OverlappingPair::getCachedSeparatingAxis() { + return this.cachedSeparatingAxis; +} + +void OverlappingPair::setCachedSeparatingAxis( vec3 axis) { + this.cachedSeparatingAxis = axis; +} + +int OverlappingPair::getNbContactPoints() { + return this.contactManifoldSet.getTotalNbContactPoints(); +} + + ContactManifoldSet OverlappingPair::getContactManifoldSet() { + return this.contactManifoldSet; +} + +overlappingpairid OverlappingPair::computeID(ProxyShape* shape1, ProxyShape* shape2) { + assert( shape1->this.broadPhaseID >= 0 + hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj shape2->this.broadPhaseID >= 0); + // Construct the pair of body index + overlappingpairid pairID = shape1->this.broadPhaseID < shape2->this.broadPhaseID ? + etk::makePair(shape1->this.broadPhaseID, shape2->this.broadPhaseID) : + etk::makePair(shape2->this.broadPhaseID, shape1->this.broadPhaseID); + assert(pairID.first != pairID.second); + return pairID; +} + +longpair OverlappingPair::computeBodiesIndexPair(CollisionBody* body1, + CollisionBody* body2) { + // Construct the pair of body index + longpair indexPair = body1->getID() < body2->getID() ? + etk::makePair(body1->getID(), body2->getID()) : + etk::makePair(body2->getID(), body1->getID()); + assert(indexPair.first != indexPair.second); + return indexPair; +} + +void OverlappingPair::clearContactPoints() { + this.contactManifoldSet.clear(); +} + diff --git a/src/org/atriaSoft/ephysics/engine/OverlappingPair.hpp b/src/org/atriaSoft/ephysics/engine/OverlappingPair.hpp new file mode 100644 index 0000000..b6545c0 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/OverlappingPair.hpp @@ -0,0 +1,63 @@ +/** @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) + */ +#pragma once +#include +#include +#include + +namespace ephysics { + // Type for the overlapping pair ID + typedef etk::Pair overlappingpairid; + /** + * @brief 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. + */ + class OverlappingPair { + private: + ContactManifoldSet this.contactManifoldSet; //!< Set of persistent contact manifolds + vec3 this.cachedSeparatingAxis; //!< Cached previous separating axis + /// Private copy-ructor + OverlappingPair( OverlappingPair pair); + /// Private assignment operator + OverlappingPair operator=( OverlappingPair pair); + public: + /// Constructor + OverlappingPair(ProxyShape* shape1, + ProxyShape* shape2, + int nbMaxContactManifolds); + /// Return the pointer to first proxy collision shape + ProxyShape* getShape1() ; + /// Return the pointer to second body + ProxyShape* getShape2() ; + /// Add a contact to the contact cache + void addContact(ContactPoint* contact); + /// Update the contact cache + void update(); + /// Return the cached separating axis + vec3 getCachedSeparatingAxis() ; + /// Set the cached separating axis + void setCachedSeparatingAxis( vec3 axis); + /// Return the number of contacts in the cache + int getNbContactPoints() ; + /// Return the a reference to the contact manifold set + ContactManifoldSet getContactManifoldSet(); + /// Clear the contact points of the contact manifold + void clearContactPoints(); + /// Return the pair of bodies index + static overlappingpairid computeID(ProxyShape* shape1, ProxyShape* shape2); + /// Return the pair of bodies index of the pair + static longpair computeBodiesIndexPair(CollisionBody* body1, CollisionBody* body2); + friend class DynamicsWorld; + }; + +} + diff --git a/src/org/atriaSoft/ephysics/engine/Profiler.cpp b/src/org/atriaSoft/ephysics/engine/Profiler.cpp new file mode 100644 index 0000000..3be0bff --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Profiler.cpp @@ -0,0 +1,353 @@ +/** @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) + */ +#ifdef ISPROFILINGACTIVE + +// Libraries +#include +#include + +using namespace ephysics; + +// Initialization of static variables +ProfileNode Profiler::this.rootNode("Root", null); +ProfileNode* Profiler::this.currentNode = Profiler::this.rootNode; +long double Profiler::this.profilingStartTime = Timer::getCurrentSystemTime() * 1000.0; +int Profiler::this.frameCounter = 0; + +// Constructor +ProfileNode::ProfileNode( char* name, ProfileNode* parentNode) + :this.name(name), this.numberTotalCalls(0), this.startTime(0), this.totalTime(0), + this.recursionCounter(0), this.parentNode(parentNode), this.childNode(null), + this.siblingNode(null) { + reset(); +} + +// Destructor +ProfileNode::~ProfileNode() { + ETKDELETE(ProfileNode, this.childNode); + this.childNode = null; + ETKDELETE(ProfileNode, this.siblingNode); + this.siblingNode = null; +} + +// Return a pointer to a sub node with a given name +ProfileNode* ProfileNode::findSubNode( char* name) { + + // Try to find the node among the child nodes + ProfileNode* child = this.childNode; + while (child != null) { + if (child->this.name == name) { + return child; + } + child = child->this.siblingNode; + } + + // The nose has not been found. Therefore, we create it + // and add it to the profiler tree + ProfileNode* newNode = ETKNEW(ProfileNode, name, this); + newNode->this.siblingNode = this.childNode; + this.childNode = newNode; + + return newNode; +} + +// Called when we enter the block of code corresponding to this profile node +void ProfileNode::enterBlockOfCode() { + this.numberTotalCalls++; + + // If the current code is not called recursively + if (this.recursionCounter == 0) { + + // Get the current system time to initialize the starting time of + // the profiling of the current block of code + this.startTime = Timer::getCurrentSystemTime() * 1000.0; + } + + this.recursionCounter++; +} + +// Called when we exit the block of code corresponding to this profile node +boolean ProfileNode::exitBlockOfCode() { + this.recursionCounter--; + + if (this.recursionCounter == 0 hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj this.numberTotalCalls != 0) { + + // Get the current system time + long double currentTime = Timer::getCurrentSystemTime() * 1000.0; + + // Increase the total elasped time in the current block of code + this.totalTime += currentTime - this.startTime; + } + + // Return true if the current code is not recursing + return (this.recursionCounter == 0); +} + +// Reset the profiling of the node +void ProfileNode::reset() { + this.numberTotalCalls = 0; + this.totalTime = 0.0; + + // Reset the child node + if (this.childNode != null) { + this.childNode->reset(); + } + + // Reset the sibling node + if (this.siblingNode != null) { + this.siblingNode->reset(); + } +} + +// Destroy the node +void ProfileNode::destroy() { + ETKDELETE(ProfileNode, this.childNode); + this.childNode = null; + ETKDELETE(ProfileNode, this.siblingNode); + this.siblingNode = null; +} + +// Constructor +ProfileNodeIterator::ProfileNodeIterator(ProfileNode* startingNode) + :this.currentParentNode(startingNode), + this.currentChildNode(this.currentParentNode->getChildNode()){ + +} + +// Enter a given child node +void ProfileNodeIterator::enterChild(int index) { + this.currentChildNode = this.currentParentNode->getChildNode(); + while ((this.currentChildNode != null) hjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkjhjkhjkhjkhkj (index != 0)) { + index--; + this.currentChildNode = this.currentChildNode->getSiblingNode(); + } + + if (this.currentChildNode != null) { + this.currentParentNode = this.currentChildNode; + this.currentChildNode = this.currentParentNode->getChildNode(); + } +} + +// Enter a given parent node +void ProfileNodeIterator::enterParent() { + if (this.currentParentNode->getParentNode() != null) { + this.currentParentNode = this.currentParentNode->getParentNode(); + } + this.currentChildNode = this.currentParentNode->getChildNode(); +} + +// Method called when we want to start profiling a block of code. +void Profiler::startProfilingBlock( char* name) { + + // Look for the node in the tree that corresponds to the block of + // code to profile + if (name != this.currentNode->getName()) { + this.currentNode = mCurrentNode->findSubNode(name); + } + + // Start profile the node + this.currentNode->enterBlockOfCode(); +} + +// Method called at the end of the scope where the +// startProfilingBlock() method has been called. +void Profiler::stopProfilingBlock() { + + // Go to the parent node unless if the current block + // of code is recursing + if (this.currentNode->exitBlockOfCode()) { + this.currentNode = mCurrentNode->getParentNode(); + } +} + +// Reset the timing data of the profiler (but not the profiler tree structure) +void Profiler::reset() { + this.rootNode.reset(); + this.rootNode.enterBlockOfCode(); + this.frameCounter = 0; + this.profilingStartTime = Timer::getCurrentSystemTime() * 1000.0; +} + +// Print the report of the profiler in a given output stream +void Profiler::printReport(etk::Stream stream) { + ProfileNodeIterator* iterator = Profiler::getIterator(); + + // Recursively print the report of each node of the profiler tree + printRecursiveNodeReport(iterator, 0, stream); + + // Destroy the iterator + destroyIterator(iterator); +} + +// Recursively print the report of a given node of the profiler tree +void Profiler::printRecursiveNodeReport(ProfileNodeIterator* iterator, + int spacing, + etk::Stream stream) { + iterator->first(); + + // If we are at the end of a branch in the profiler tree + if (iterator->isEnd()) { + return; + } + + long double parentTime = iterator->isRoot() ? getElapsedTimeSinceStart() : + iterator->getCurrentParentTotalTime(); + long double accumulatedTime = 0.0; + int nbFrames = Profiler::getNbFrames(); + for (int i=0; igetCurrentParentName() << + " (total running time : " << parentTime << " ms) ---\n"; + long double totalTime = 0.0; + + // Recurse over the children of the current node + int nbChildren = 0; + for (int i=0; !iterator->isEnd(); i++, iterator->next()) { + nbChildren++; + long double currentTotalTime = iterator->getCurrentTotalTime(); + accumulatedTime += currentTotalTime; + long double fraction = parentTime > DLBEPSILON ? + (currentTotalTime / parentTime) * 100.0 : 0.0; + for (int j=0; jgetCurrentName() << " : " << + fraction << " % | " << (currentTotalTime / (long double) (nbFrames)) << + " ms/frame (" << iterator->getCurrentNbTotalCalls() << " calls)\n"; + totalTime += currentTotalTime; + } + + if (parentTime < accumulatedTime) { + stream << "Something is wrong !\n"; + } + for (int i=0; i DLBEPSILON ? + ((parentTime - accumulatedTime) / parentTime) * 100.0 : 0.0; + long double difference = parentTime - accumulatedTime; + stream << "| Unaccounted : " << difference << " ms (" << percentage << " %)\n"; + + for (int i=0; ienterChild(i); + printRecursiveNodeReport(iterator, spacing + 3, stream); + iterator->enterParent(); + } +} + +// Return true if we are at the root of the profiler tree +boolean ProfileNodeIterator::isRoot() { + return (this.currentParentNode->getParentNode() == null); +} + +// Return true if we are at the end of a branch of the profiler tree +boolean ProfileNodeIterator::isEnd() { + return (this.currentChildNode == null); +} + +// Return the name of the current node + char* ProfileNodeIterator::getCurrentName() { + return this.currentChildNode->getName(); +} + +// Return the total time of the current node +long double ProfileNodeIterator::getCurrentTotalTime() { + return this.currentChildNode->getTotalTime(); +} + +// Return the total number of calls of the current node +int ProfileNodeIterator::getCurrentNbTotalCalls() { + return this.currentChildNode->getNbTotalCalls(); +} + +// Return the name of the current parent node + char* ProfileNodeIterator::getCurrentParentName() { + return this.currentParentNode->getName(); +} + +// Return the total time of the current parent node +long double ProfileNodeIterator::getCurrentParentTotalTime() { + return this.currentParentNode->getTotalTime(); +} + +// Return the total number of calls of the current parent node +int ProfileNodeIterator::getCurrentParentNbTotalCalls() { + return this.currentParentNode->getNbTotalCalls(); +} + +// Go to the first node +void ProfileNodeIterator::first() { + this.currentChildNode = this.currentParentNode->getChildNode(); +} + +// Go to the next node +void ProfileNodeIterator::next() { + this.currentChildNode = this.currentChildNode->getSiblingNode(); +} + +// Return a pointer to the parent node +ProfileNode* ProfileNode::getParentNode() { + return this.parentNode; +} + +// Return a pointer to a sibling node +ProfileNode* ProfileNode::getSiblingNode() { + return this.siblingNode; +} + +// Return a pointer to a child node +ProfileNode* ProfileNode::getChildNode() { + return this.childNode; +} + +// Return the name of the node + char* ProfileNode::getName() { + return this.name; +} + +// Return the total number of call of the corresponding block of code +int ProfileNode::getNbTotalCalls() { + return this.numberTotalCalls; +} + +// Return the total time spent in the block of code +long double ProfileNode::getTotalTime() { + return this.totalTime; +} + +// Return the number of frames +int Profiler::getNbFrames() { + return this.frameCounter; +} + +// Return the elasped time since the start/reset of the profiling +long double Profiler::getElapsedTimeSinceStart() { + long double currentTime = Timer::getCurrentSystemTime() * 1000.0; + return currentTime - this.profilingStartTime; +} + +// Increment the frame counter +void Profiler::incrementFrameCounter() { + this.frameCounter++; +} + +// Return an iterator over the profiler tree starting at the root +ProfileNodeIterator* Profiler::getIterator() { + return ETKNEW(ProfileNodeIterator(this.rootNode)); +} + +// Destroy a previously allocated iterator +void Profiler::destroyIterator(ProfileNodeIterator* iterator) { + ETKDELETE(ProfileNodeIterator, iterator); +} + +// Destroy the profiler (release the memory) +void Profiler::destroy() { + this.rootNode.destroy(); +} +#endif diff --git a/src/org/atriaSoft/ephysics/engine/Profiler.hpp b/src/org/atriaSoft/ephysics/engine/Profiler.hpp new file mode 100644 index 0000000..0517407 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Profiler.hpp @@ -0,0 +1,155 @@ +/** @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) + */ +#pragma once + +#ifdef ISPROFILINGACTIVE +#include +#include +namespace ephysics { + /** + * @brief It represents a profile sample in the profiler tree. + */ + class ProfileNode { + private : + char* this.name; //!< Name of the node + int this.numberTotalCalls; //!< Total number of calls of this node + long double this.startTime; //!< Starting time of the sampling of corresponding block of code + long double this.totalTime; //!< Total time spent in the block of code + int this.recursionCounter; //!< Recursion counter + ProfileNode* this.parentNode; //!< Pointer to the parent node + ProfileNode* this.childNode; //!< Pointer to a child node + ProfileNode* this.siblingNode; //!< Pointer to a sibling node + public : + /// Constructor + ProfileNode( char* name, ProfileNode* parentNode); + /// Destructor + ~ProfileNode(); + /// Return a pointer to a sub node + ProfileNode* findSubNode( char* name); + /// Return a pointer to the parent node + ProfileNode* getParentNode(); + /// Return a pointer to a sibling node + ProfileNode* getSiblingNode(); + /// Return a pointer to a child node + ProfileNode* getChildNode(); + /// Return the name of the node + char* getName(); + /// Return the total number of call of the corresponding block of code + int getNbTotalCalls() ; + /// Return the total time spent in the block of code + long double getTotalTime() ; + /// Called when we enter the block of code corresponding to this profile node + void enterBlockOfCode(); + /// Called when we exit the block of code corresponding to this profile node + boolean exitBlockOfCode(); + /// Reset the profiling of the node + void reset(); + /// Destroy the node + void destroy(); + }; + /** + * @brief This class allows us to iterator over the profiler tree. + */ + class ProfileNodeIterator { + private : + ProfileNode* this.currentParentNode; //!< Current parent node + ProfileNode* this.currentChildNode; //!< Current child node + public : + /// Constructor + ProfileNodeIterator(ProfileNode* startingNode); + /// Go to the first node + void first(); + /// Go to the next node + void next(); + /// Enter a given child node + void enterChild(int index); + /// Enter a given parent node + void enterParent(); + /// Return true if we are at the root of the profiler tree + boolean isRoot(); + /// Return true if we are at the end of a branch of the profiler tree + boolean isEnd(); + /// Return the name of the current node + char* getCurrentName(); + /// Return the total time of the current node + long double getCurrentTotalTime(); + /// Return the total number of calls of the current node + int getCurrentNbTotalCalls(); + /// Return the name of the current parent node + char* getCurrentParentName(); + /// Return the total time of the current parent node + long double getCurrentParentTotalTime(); + /// Return the total number of calls of the current parent node + int getCurrentParentNbTotalCalls(); + }; + + /** + * @brief This is the main class of the profiler. This profiler is based on "Real-Time Hierarchical + * Profiling" article from "Game Programming Gems 3" by Greg Hjelstrom and Byon Garrabrant. + */ + class Profiler { + private : + static ProfileNode this.rootNode; //!< Root node of the profiler tree + static ProfileNode* this.currentNode; //!< Current node in the current execution + static int this.frameCounter; //!< Frame counter + static long double this.profilingStartTime; //!< Starting profiling time + private: + static void printRecursiveNodeReport(ProfileNodeIterator* iterator, + int spacing, + etk::Stream outputStream); + public : + /// Method called when we want to start profiling a block of code. + static void startProfilingBlock( char *name); + /// Method called at the end of the scope where the + /// startProfilingBlock() method has been called. + static void stopProfilingBlock(); + /// Reset the timing data of the profiler (but not the profiler tree structure) + static void reset(); + /// Return the number of frames + static int getNbFrames(); + /// Return the elasped time since the start/reset of the profiling + static long double getElapsedTimeSinceStart(); + /// Increment the frame counter + static void incrementFrameCounter(); + /// Return an iterator over the profiler tree starting at the root + static ProfileNodeIterator* getIterator(); + /// Print the report of the profiler in a given output stream + static void printReport(etk::Stream outputStream); + /// Destroy a previously allocated iterator + static void destroyIterator(ProfileNodeIterator* iterator); + /// Destroy the profiler (release the memory) + static void destroy(); + }; + + /** + * @brief This class is used to represent a profile sample. It is ructed at the + * beginning of a code block we want to profile and destructed at the end of the + * scope to profile. + */ + class ProfileSample { + public : + /// Constructor + ProfileSample( char* name) { + // Ask the profiler to start profiling a block of code + Profiler::startProfilingBlock(name); + } + /// Destructor + ~ProfileSample() { + // Tell the profiler to stop profiling a block of code + Profiler::stopProfilingBlock(); + } + }; + + // Use this macro to start profile a block of code + #define PROFILE(name) ProfileSample profileSample(name) +} +#else + // Empty macro in case profiling is not active + #define PROFILE(name) +#endif diff --git a/src/org/atriaSoft/ephysics/engine/Timer.cpp b/src/org/atriaSoft/ephysics/engine/Timer.cpp new file mode 100644 index 0000000..4b9fba3 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Timer.cpp @@ -0,0 +1,75 @@ +/** @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) + */ +#include +#include + + +ephysics::Timer::Timer(double timeStep) : this.timeStep(timeStep), this.isRunning(false) { + assert(timeStep > 0.0); +} + +ephysics::Timer::~Timer() { + +} + +long double ephysics::Timer::getCurrentSystemTime() { + return (long double)(echrono::Clock::now().get()) / 1000000000.0; +} + +double ephysics::Timer::getTimeStep() { + return this.timeStep; +} + +void ephysics::Timer::setTimeStep(double timeStep) { + assert(timeStep > 0.0f); + this.timeStep = timeStep; +} + +long double ephysics::Timer::getPhysicsTime() { + return this.lastUpdateTime; +} + +boolean ephysics::Timer::getIsRunning() { + return this.isRunning; +} + +void ephysics::Timer::start() { + if (!this.isRunning) { + // Get the current system time + this.lastUpdateTime = getCurrentSystemTime(); + this.accumulator = 0.0; + this.isRunning = true; + } +} + +void ephysics::Timer::stop() { + this.isRunning = false; +} + +boolean ephysics::Timer::isPossibleToTakeStep() { + return (this.accumulator >= this.timeStep); +} + +void ephysics::Timer::nextStep() { + assert(this.isRunning); + // Update the accumulator value + this.accumulator -= this.timeStep; +} + +float ephysics::Timer::computeInterpolationFactor() { + return (float(this.accumulator / this.timeStep)); +} + +void ephysics::Timer::update() { + long double currentTime = getCurrentSystemTime(); + this.deltaTime = currentTime - this.lastUpdateTime; + this.lastUpdateTime = currentTime; + this.accumulator += this.deltaTime; +} + diff --git a/src/org/atriaSoft/ephysics/engine/Timer.hpp b/src/org/atriaSoft/ephysics/engine/Timer.hpp new file mode 100644 index 0000000..5d92561 --- /dev/null +++ b/src/org/atriaSoft/ephysics/engine/Timer.hpp @@ -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, Edouard DUPIN + * @license MPL v2.0 (see license file) + */ +#include +#include + +namespace ephysics { + /** + * @brief This class will take care of the time in the physics engine. It + * uses functions that depend on the current platform to get the + * current time. + */ + class Timer { + private : + double this.timeStep; //!< Timestep dt of the physics engine (timestep > 0.0) + long double this.lastUpdateTime; //!< Last time the timer has been updated + long double this.deltaTime; //!< Time difference between the two last timer update() calls + double this.accumulator; //!< Used to fix the time step and avoid strange time effects + boolean this.isRunning; //!< True if the timer is running + /// Private copy-ructor + Timer( Timer timer); + /// Private assignment operator + Timer operator=( Timer timer); + public : + /// Constructor + Timer(double timeStep); + /// Destructor + virtual ~Timer(); + /// Return the timestep of the physics engine + double getTimeStep() ; + /// Set the timestep of the physics engine + void setTimeStep(double timeStep); + /// Return the current time of the physics engine + long double getPhysicsTime() ; + /// Start the timer + void start(); + /// Stop the timer + void stop(); + /// Return true if the timer is running + boolean getIsRunning() ; + /// True if it's possible to take a new step + boolean isPossibleToTakeStep() ; + /// Compute the time since the last update() call and add it to the accumulator + void update(); + /// Take a new step => update the timer by adding the timeStep value to the current time + void nextStep(); + /// Compute the interpolation factor + float computeInterpolationFactor(); + /// Return the current time of the system in seconds + static long double getCurrentSystemTime(); + }; + +} diff --git a/src/org/atriaSoft/ephysics/ephysics.hpp b/src/org/atriaSoft/ephysics/ephysics.hpp new file mode 100644 index 0000000..0e3dc8a --- /dev/null +++ b/src/org/atriaSoft/ephysics/ephysics.hpp @@ -0,0 +1,38 @@ +/** @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) + */ +#pragma once + +// Libraries +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + diff --git a/src/org/atriaSoft/ephysics/mathematics/Ray.hpp b/src/org/atriaSoft/ephysics/mathematics/Ray.hpp new file mode 100644 index 0000000..449a150 --- /dev/null +++ b/src/org/atriaSoft/ephysics/mathematics/Ray.hpp @@ -0,0 +1,48 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + /** + * This structure represents a 3D ray represented by two points. + * The ray goes from point1 to point1 + maxFraction * (point2 - point1). + * The points are specified in world-space coordinates. + */ + struct Ray { + public: + vec3 point1; //! 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) + */ +#pragma once + +// Libraries +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + diff --git a/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.cpp b/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.cpp new file mode 100644 index 0000000..04244f2 --- /dev/null +++ b/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.cpp @@ -0,0 +1,41 @@ +/** @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) + */ +#include +#include + +using namespace ephysics; + +/// 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. +void ephysics::computeBarycentricCoordinatesInTriangle( vec3 a, vec3 b, vec3 c, + vec3 p, float u, float v, float w) { + vec3 v0 = b - a; + vec3 v1 = c - a; + vec3 v2 = p - a; + + float d00 = v0.dot(v0); + float d01 = v0.dot(v1); + float d11 = v1.dot(v1); + float d20 = v2.dot(v0); + float d21 = v2.dot(v1); + + float denom = d00 * d11 - d01 * d01; + v = (d11 * d20 - d01 * d21) / denom; + w = (d00 * d21 - d01 * d20) / denom; + u = 1.0f - v - w; +} + +// Clamp a vector such that it is no longer than a given maximum length +vec3 ephysics::clamp( vec3 vector, float maxLength) { + if (vector.length2() > maxLength * maxLength) { + return vector.safeNormalized() * maxLength; + } + return vector; +} diff --git a/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.hpp b/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.hpp new file mode 100644 index 0000000..854d850 --- /dev/null +++ b/src/org/atriaSoft/ephysics/mathematics/mathematics_functions.hpp @@ -0,0 +1,59 @@ +/** @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) + */ +#include +#include + +/// ReactPhysics3D namespace +namespace ephysics { + +// ---------- Mathematics functions ---------- // + +/// 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] +inline boolean approxEqual(float a, float b, float epsilon = FLTEPSILON) { + return (etk::abs(a - b) < epsilon); +} + +/// Function that returns the result of the "value" clamped by +/// two others values "lowerLimit" and "upperLimit" +inline int clamp(int value, int lowerLimit, int upperLimit) { + assert(lowerLimit <= upperLimit); + return etk::min(etk::max(value, lowerLimit), upperLimit); +} + +/// Function that returns the result of the "value" clamped by +/// two others values "lowerLimit" and "upperLimit" +inline float clamp(float value, float lowerLimit, float upperLimit) { + assert(lowerLimit <= upperLimit); + return etk::min(etk::max(value, lowerLimit), upperLimit); +} + +/// Return the minimum value among three values +inline float min3(float a, float b, float c) { + return etk::min(etk::min(a, b), c); +} + +/// Return the maximum value among three values +inline float max3(float a, float b, float c) { + return etk::max(etk::max(a, b), c); +} + +/// Return true if two values have the same sign +inline boolean sameSign(float a, float b) { + return a * b >= 0.0f; +} + +/// Clamp a vector such that it is no longer than a given maximum length +vec3 clamp( vec3 vector, float maxLength); + +/// Compute the barycentric coordinates u, v, w of a point p inside the triangle (a, b, c) +void computeBarycentricCoordinatesInTriangle( vec3 a, vec3 b, vec3 c, + vec3 p, float u, float v, float w); + +} diff --git a/src/org/atriaSoft/ephysics/memory/Stack.hpp b/src/org/atriaSoft/ephysics/memory/Stack.hpp new file mode 100644 index 0000000..d66b788 --- /dev/null +++ b/src/org/atriaSoft/ephysics/memory/Stack.hpp @@ -0,0 +1,85 @@ +/** @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) + */ +#pragma once + +#include + +namespace ephysics { + +/** + * This class represents a simple generic stack with an initial capacity. If the number + * of elements exceeds the capacity, the heap will be used to allocated more memory. + */ +template +class Stack { + private: + /// Initial array that contains the elements of the stack + T mInitArray[capacity]; + /// Pointer to the first element of the stack + T* mElements; + /// Number of elements in the stack + int mNbElements; + /// Number of allocated elements in the stack + int mNbAllocatedElements; + public: + /// Constructor + Stack() : mElements(mInitArray), mNbElements(0), mNbAllocatedElements(capacity) { + + } + /// Destructor + ~Stack() { + // If elements have been allocated on the heap + if (mInitArray != mElements) { + // Release the memory allocated on the heap + free(mElements); + } + } + /// Push an element into the stack + void push( T element); + /// Pop an element from the stack (remove it from the stack and return it) + T pop(); + /// Return the number of elments in the stack + int getNbElements() ; +}; + +// Push an element into the stack +template +inline void Stack::push( T element) { + + // If we need to allocate more elements + if (mNbElements == mNbAllocatedElements) { + T* oldElements = mElements; + mNbAllocatedElements *= 2; + mElements = (T*) malloc(mNbAllocatedElements * sizeof(T)); + assert(mElements); + memcpy(mElements, oldElements, mNbElements * sizeof(T)); + if (oldElements != mInitArray) { + free(oldElements); + } + } + + mElements[mNbElements] = element; + mNbElements++; +} + +// Pop an element from the stack (remove it from the stack and return it) +template +inline T Stack::pop() { + assert(mNbElements > 0); + mNbElements--; + return mElements[mNbElements]; +} + +// Return the number of elments in the stack +template +inline int Stack::getNbElements() { + return mNbElements; +} + +} diff --git a/src/org/atriaSoft/etk/math/Matrix3f.java b/src/org/atriaSoft/etk/math/Matrix3f.java index aba82bd..bd40c8c 100644 --- a/src/org/atriaSoft/etk/math/Matrix3f.java +++ b/src/org/atriaSoft/etk/math/Matrix3f.java @@ -128,6 +128,9 @@ public class Matrix3f { } return new Vector3f(this.mat[6], this.mat[7], this.mat[8]); } + public float get(int iii) { + return this.mat[iii]; + } /** * @brief get a transpose matrix of this one. * @return the transpose matrix @@ -405,4 +408,29 @@ public class Matrix3f { point.x * this.mat[3] + point.y * this.mat[4] + point.z * this.mat[5], point.x * this.mat[6] + point.y * this.mat[7] + point.z * this.mat[8]); } + /** + * @brief Create a matrix 3D with a simple rotation + * @param[in] normal vector aroud witch apply the rotation + * @param[in] angleRad Radian angle to set at the matrix + * @return New matrix of the transformation requested + */ + public static Matrix3f createMatrixRotate(Vector3f normal, float angleRad) { + Matrix3f tmp = new Matrix3f(); + float cosVal = (float)Math.cos(angleRad); + float sinVal = (float)Math.sin(angleRad); + float invVal = 1.0f-cosVal; + // set rotation : + tmp.mat[0] = normal.x * normal.x * invVal + cosVal; + tmp.mat[1] = normal.x * normal.y * invVal - normal.z * sinVal; + tmp.mat[2] = normal.x * normal.z * invVal + normal.y * sinVal; + + tmp.mat[3] = normal.y * normal.x * invVal + normal.z * sinVal; + tmp.mat[4] = normal.y * normal.y * invVal + cosVal; + tmp.mat[5] = normal.y * normal.z * invVal - normal.x * sinVal; + + tmp.mat[6] = normal.z * normal.x * invVal - normal.y * sinVal; + tmp.mat[7] = normal.z * normal.y * invVal + normal.x * sinVal; + tmp.mat[8] = normal.z * normal.z * invVal + cosVal; + return tmp; + } } diff --git a/src/org/atriaSoft/etk/math/Vector3f.java b/src/org/atriaSoft/etk/math/Vector3f.java index 270f8f2..f8852dc 100644 --- a/src/org/atriaSoft/etk/math/Vector3f.java +++ b/src/org/atriaSoft/etk/math/Vector3f.java @@ -70,6 +70,14 @@ public class Vector3f { this.z -= obj.z; return this; } + /** + * @brief Subtract a vector from this one + * @param[in] obj The vector to subtract + * @return A new vector with the data + */ + public Vector3f less_new(Vector3f obj) { + return new Vector3f(this.x - obj.x, this.y - obj.y, this.z - obj.z); + } /** * @brief Scale the vector * @param[in] val Scale factor @@ -83,6 +91,7 @@ public class Vector3f { /** * @brief Scale the vector * @param[in] val Scale factor + * @return A new vector with the data */ public Vector3f multiply_new(float val) { return new Vector3f(this.x * val, this.y * val, this.z * val); @@ -512,4 +521,30 @@ public class Vector3f { public String toString() { return "Vector3f(" + this.x + "," + this.y + "," + this.z + ")"; } + + + + + + /** + * @brief Get the length square between the 2 vectors + * @param[in] start First vector + * @param[in] stop second vector + * @return Length value + */ + public static float length2(Vector3f start, Vector3f stop) { + float x = stop.x - start.x; + float y = stop.y - start.y; + float z = stop.z - start.z; + return x * x + y * y + z * z; + } + /** + * @brief Get the length between the 2 vectors + * @param[in] start First vector + * @param[in] stop second vector + * @return Length value + */ + public float length(Vector3f start, Vector3f stop) { + return (float) Math.sqrt(length2(start, stop)); + } } diff --git a/src/org/atriaSoft/gameEngine/components/ComponentPhysics.java b/src/org/atriaSoft/gameEngine/components/ComponentPhysics.java index 9b6ef31..d99b6fd 100644 --- a/src/org/atriaSoft/gameEngine/components/ComponentPhysics.java +++ b/src/org/atriaSoft/gameEngine/components/ComponentPhysics.java @@ -11,16 +11,22 @@ import org.atriaSoft.gale.resource.ResourceColored3DObject; import org.atriaSoft.gale.test.sample2.Log; import org.atriaSoft.gameEngine.Component; import org.atriaSoft.gameEngine.engines.EngineGravity; +import org.atriaSoft.gameEngine.physics.PhysicBox; import org.atriaSoft.gameEngine.physics.PhysicCollisionAABB; +import org.atriaSoft.gameEngine.physics.PhysicMapVoxel; import org.atriaSoft.gameEngine.physics.PhysicShape; +import org.atriaSoft.gameEngine.physics.PhysicSphere; +import org.atriaSoft.gameEngine.physics.ToolCollisionOBBWithOBB; import entities.Entity; public class ComponentPhysics extends Component { private PhysicCollisionAABB aabb; private List aabbIntersection = new ArrayList(); + private List narrowIntersection = new ArrayList(); private List shapes = new ArrayList(); private ComponentPosition position; + private boolean staticObject = false; private boolean manageGravity = false; public static float globalMaxSpeed = Float.MAX_VALUE; private float maxSpeed = globalMaxSpeed; @@ -68,6 +74,86 @@ public class ComponentPhysics extends Component { return aabb; } + public void updateForNarrowCollision() { + if (aabbIntersection.size() == 0) { + return; + } + if (position == null) { + Log.info("No position in Entity "); + return; + } + for (PhysicShape shape : shapes) { + shape.updateForNarrowCollision(position.getTransform()); + } + narrowIntersection.clear(); + } + public boolean checkNarrowCollision() { + if (this.staticObject == true) { + return false; + } + for (ComponentPhysics elem : aabbIntersection) { + boolean collide = false; + for (PhysicShape shapeCurrent : shapes) { + if (true == elem.checkCollide(shapeCurrent)) { + collide = true; + break; + } + } + if (collide == true) { + narrowIntersection.add(elem); + elem.narrowIntersection.add(this); + } + } + return false; + } + + private boolean checkCollide(PhysicShape shapeCurrent) { + if (shapeCurrent instanceof PhysicBox) { + PhysicBox shape111 = (PhysicBox)shapeCurrent; + for (PhysicShape shape : shapes) { + if (shape instanceof PhysicBox) { + PhysicBox shape222 = (PhysicBox)shape; + if (ToolCollisionOBBWithOBB.testCollide(shape111, shape222) == true) { + return true; + } + } else if (shape instanceof PhysicSphere) { + + } else if (shape instanceof PhysicMapVoxel) { + + } else { + Log.error("Not manage collision model... " + shape); + } + } + } else if (shapeCurrent instanceof PhysicSphere) { + for (PhysicShape shape : shapes) { + if (shape instanceof PhysicBox) { + + } else if (shape instanceof PhysicSphere) { + + } else if (shape instanceof PhysicMapVoxel) { + + } else { + Log.error("Not manage collision model... " + shape); + } + } + } else if (shapeCurrent instanceof PhysicMapVoxel) { + for (PhysicShape shape : shapes) { + if (shape instanceof PhysicBox) { + + } else if (shape instanceof PhysicSphere) { + + } else if (shape instanceof PhysicMapVoxel) { + + } else { + Log.error("Not manage collision model... " + shape); + } + } + } else { + Log.error("Not manage collision model... " + shapeCurrent); + } + return false; + } + public void applyForces(float timeStep, EngineGravity gravity) { // get the gravity at the specific position... Vector3f gravityForce; @@ -98,8 +184,12 @@ public class ComponentPhysics extends Component { Color displayColor; if (this.aabbIntersection.size() == 0) { displayColor = new Color(1,1,1,1); - } else { - displayColor = new Color(1,0,0,1); + } else { + if (this.narrowIntersection.size() == 0) { + displayColor = new Color(1,1,0,1); + } else { + displayColor = new Color(1,0,0,1); + } } if (aabb != null) { debugDrawProperty.drawCubeLine(aabb.getMin(), aabb.getMax(), displayColor, Matrix4f.identity(), true, true); @@ -142,4 +232,15 @@ public class ComponentPhysics extends Component { public void addIntersection(ComponentPhysics component) { this.aabbIntersection.add(component); } + public List getAabbIntersection() { + return aabbIntersection; + } + + public boolean isStaticObject() { + return staticObject; + } + + public void setStaticObject(boolean staticObject) { + this.staticObject = staticObject; + } } diff --git a/src/org/atriaSoft/gameEngine/engines/EnginePhysics.java b/src/org/atriaSoft/gameEngine/engines/EnginePhysics.java index a51d9b5..6aa7275 100644 --- a/src/org/atriaSoft/gameEngine/engines/EnginePhysics.java +++ b/src/org/atriaSoft/gameEngine/engines/EnginePhysics.java @@ -1,5 +1,6 @@ package org.atriaSoft.gameEngine.engines; +import java.util.List; import java.util.Vector; import org.atriaSoft.gale.resource.ResourceColored3DObject; @@ -50,7 +51,7 @@ public class EnginePhysics extends Engine { //applyForces(TIME_STEP); updateAABB(TIME_STEP); updateCollisionsAABB(TIME_STEP); - updateCollisions(TIME_STEP); + updateCollisionsNarrowPhase(TIME_STEP); GenerateResultCollisionsForces(TIME_STEP); // Decrease the accumulated time accumulator -= TIME_STEP; @@ -86,7 +87,15 @@ public class EnginePhysics extends Engine { } } } - private void updateCollisions(float timeStep) { + private void updateCollisionsNarrowPhase(float timeStep) { + // clear all object intersection + for (ComponentPhysics it: components) { + it.updateForNarrowCollision(); + } + for (int iii=0; iii< components.size(); iii++) { + ComponentPhysics current = components.get(iii); + boolean collide = current.checkNarrowCollision(); + } } private void GenerateResultCollisionsForces(float timeStep) { diff --git a/src/org/atriaSoft/gameEngine/geometry/AABB.java b/src/org/atriaSoft/gameEngine/geometry/AABB.java new file mode 100644 index 0000000..f2c0e84 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/AABB.java @@ -0,0 +1,33 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Matrix3f; +import org.atriaSoft.etk.math.Vector3f; + +public class AABB { + public Vector3f position; + public Vector3f size; // HALF SIZE! + + public AABB(Vector3f position, Vector3f size) { + this.position = position; + this.size = size; + } + public AABB() { + this.position = new Vector3f(); + this.size = new Vector3f(1.0f, 1.0f, 1.0f); + } + public void setMinMax(Vector3f min, Vector3f max) { + this.position = min.add_new(max).multiply(0.5f); + this.size = max.less_new(min).multiply(0.5f); + } + + public Vector3f getMin() { + return new Vector3f(position.x-size.x, position.y-size.y, position.z-size.z); + } + public Vector3f getMax() { + return new Vector3f(position.x+size.x, position.y+size.y, position.z+size.z); + } + @Override + public String toString() { + return "AABB [position=" + position + ", size=" + size + "]"; + } +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Geometry3D.java b/src/org/atriaSoft/gameEngine/geometry/Geometry3D.java new file mode 100644 index 0000000..6e0915b --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Geometry3D.java @@ -0,0 +1,125 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Geometry3D { + public static boolean CMP(float x, float y) { + return Math.abs(x-y) <= 0.000001f; + } + + public static boolean pointInSphere(Vector3f point, Sphere sphere) { + return Vector3f.length2(point, sphere.position) < sphere.radius * sphere.radius; + } + + public static boolean pointInPlane(Vector3f point, Plane plane) { + // This should probably use an epsilon! + //return Dot(point, plane.normal) - plane.distance == 0.0f; + return CMP(point.dot(plane.normal) - plane.distance, 0.0f); + } + + public static boolean pointInAABB(Vector3f point, AABB aabb) { + Vector3f min = aabb.getMin(); + Vector3f max = aabb.getMax(); + if (point.x < min.x || point.y < min.y || point.z < min.z) { + return false; + } + if (point.x > max.x || point.y > max.y || point.z > max.z) { + return false; + } + return true; + } + + public static boolean pointInOBB(Vector3f point, OBB obb) { + Vector3f dir = point.less_new(obb.position); + { + int iii = 0; + Vector3f axis = obb.orientation.getRow(iii*3); + float distance = Vector3f.length2(dir, axis); + float squareDistance = obb.size.x*obb.size.x; + if (distance > squareDistance + || distance < -squareDistance) { + return false; + } + } + { + int iii = 1; + Vector3f axis = obb.orientation.getRow(iii*3); + float distance = Vector3f.length2(dir, axis); + float squareDistance = obb.size.y*obb.size.y; + if (distance > squareDistance + || distance < -squareDistance) { + return false; + } + } + { + int iii = 2; + Vector3f axis = obb.orientation.getRow(iii*3); + float distance = Vector3f.length2(dir, axis); + float squareDistance = obb.size.z*obb.size.z; + if (distance > squareDistance + || distance < -squareDistance) { + return false; + } + } + + return true; + } + // select the point under the oriented triangle + public static boolean pointUnderTriangle(Vector3f p, Triangle t) { + // Move the triangle so that the point is + // now at the origin of the triangle + Vector3f a = t.p1.less_new(p); + Vector3f b = t.p2.less_new(p); + Vector3f c = t.p3.less_new(p); + + // The point should be moved too, so they are both + // relative, but because we don't use p in the + // equation anymore, we don't need it! + // p -= p; // This would just equal the zero vector! + + Vector3f normPBC = b.cross(c); // Normal of PBC (u) + Vector3f normPCA = c.cross(a); // Normal of PCA (v) + Vector3f normPAB = a.cross(b); // Normal of PAB (w) + + // Test to see if the normals are facing + // the same direction, return false if not + float val = normPBC.dot(normPCA); + if (val < 0.0f) { + return false; + } + val = normPBC.dot(normPAB); + if (val < 0.0f) { + return false; + } + + // All normals facing the same way, return true + return true; + } + public static boolean pointInTriangle(Vector3f p, Triangle t) { + // Move the triangle so that the point is + // now at the origin of the triangle + Vector3f a = t.p1.less_new(p); + Vector3f b = t.p2.less_new(p); + Vector3f c = t.p3.less_new(p); + // The point should be moved too, so they are both + // relative, but because we don't use p in the + // equation anymore, we don't need it! + // p -= p; // This would just equal the zero vector! + Vector3f normPBC = b.cross(c); // Normal of PBC (u) + Vector3f normPCA = c.cross(a); // Normal of PCA (v) + Vector3f normPAB = a.cross(b); // Normal of PAB (w) + // Test to see if the normals are facing + // the same direction, return false if not + float val = normPBC.dot(normPCA); + if (CMP(val, 0.0f) == false) { + return false; + } + val = normPBC.dot(normPAB); + if (CMP(val, 0.0f) == false) { + return false; + } + // All normals facing the same way, return true + return true; + } + +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Line.java b/src/org/atriaSoft/gameEngine/geometry/Line.java new file mode 100644 index 0000000..24b50f9 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Line.java @@ -0,0 +1,29 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Line { + public Vector3f start; + public Vector3f end; + + public Line(Vector3f start, Vector3f end) { + this.start = start; + this.end = end; + } + public Line() { + this.start = new Vector3f(); + this.end = new Vector3f(); + } + @Override + public String toString() { + return "Line [start=" + start + ", end=" + end + "]"; + } + + public float length2() { + return this.start.less_new(this.end).length2(); + } + public float length() { + return this.start.less_new(this.end).length(); + } + +} diff --git a/src/org/atriaSoft/gameEngine/geometry/OBB.java b/src/org/atriaSoft/gameEngine/geometry/OBB.java new file mode 100644 index 0000000..cb3ae31 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/OBB.java @@ -0,0 +1,30 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Matrix3f; +import org.atriaSoft.etk.math.Vector3f; + +public class OBB { + public Vector3f position; + public Vector3f size; // HALF SIZE! + public Matrix3f orientation; + + public OBB(Vector3f position, Vector3f size, Matrix3f orientation) { + this.position = position; + this.size = size; + this.orientation = orientation; + } + public OBB(Vector3f position, Vector3f size) { + this.position = position; + this.size = size; + this.orientation = Matrix3f.identity(); + } + public OBB() { + this.position = new Vector3f(); + this.size = new Vector3f(1.0f, 1.0f, 1.0f); + this.orientation = Matrix3f.identity(); + } + @Override + public String toString() { + return "OBB [position=" + position + ", size=" + size + ", orientation=" + orientation + "]"; + } +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Plane.java b/src/org/atriaSoft/gameEngine/geometry/Plane.java new file mode 100644 index 0000000..48ab4f2 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Plane.java @@ -0,0 +1,21 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Plane { + public Vector3f normal; + public float distance; + + public Plane(Vector3f normal, float distance) { + this.normal = normal; + this.distance = distance; + } + public Plane() { + this.normal = new Vector3f(1.0f, 0.0f, 0.0f); + this.distance = 0; + } + @Override + public String toString() { + return "Plane [normal=" + normal + ", distance=" + distance + "]"; + } +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Ray.java b/src/org/atriaSoft/gameEngine/geometry/Ray.java new file mode 100644 index 0000000..5c01934 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Ray.java @@ -0,0 +1,29 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Ray { + public Vector3f origin; + public Vector3f direction; + + public Ray() { + this.origin = new Vector3f(); + this.direction = new Vector3f(0.0f, 0.0f, 1.0f); + } + public Ray(Vector3f origin, Vector3f direction) { + this.origin = origin; + this.direction = direction; + } + public static Ray createFromPoint(Vector3f origin, Vector3f destination) { + Ray out = new Ray(origin, destination.less_new(origin)); + out.normalizeDirection(); + return out; + } + public void normalizeDirection() { + direction.safeNormalize(); + } + @Override + public String toString() { + return "Ray [origin=" + origin + ", direction=" + direction + "]"; + } +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Sphere.java b/src/org/atriaSoft/gameEngine/geometry/Sphere.java new file mode 100644 index 0000000..3038aec --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Sphere.java @@ -0,0 +1,21 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Sphere { + public Vector3f position; + public float radius; + + public Sphere(Vector3f position, float radius) { + this.position = position; + this.radius = radius; + } + public Sphere() { + this.position = new Vector3f(); + this.radius = 1.0f; + } + @Override + public String toString() { + return "Sphere [position=" + position + ", radius=" + radius + "]"; + } +} diff --git a/src/org/atriaSoft/gameEngine/geometry/Triangle.java b/src/org/atriaSoft/gameEngine/geometry/Triangle.java new file mode 100644 index 0000000..ae4b9f0 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/geometry/Triangle.java @@ -0,0 +1,24 @@ +package org.atriaSoft.gameEngine.geometry; + +import org.atriaSoft.etk.math.Vector3f; + +public class Triangle { + public Vector3f p1; + public Vector3f p2; + public Vector3f p3; + public Triangle(Vector3f p1, Vector3f p2, Vector3f p3) { + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + public Triangle() { + this.p1 = new Vector3f(); + this.p2 = new Vector3f(); + this.p3 = new Vector3f(); + } + @Override + public String toString() { + return "Triangle [p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + "]"; + } + +} diff --git a/src/org/atriaSoft/gameEngine/map/MapVoxel.java b/src/org/atriaSoft/gameEngine/map/MapVoxel.java index 2094dea..b2e8126 100644 --- a/src/org/atriaSoft/gameEngine/map/MapVoxel.java +++ b/src/org/atriaSoft/gameEngine/map/MapVoxel.java @@ -77,6 +77,7 @@ public class MapVoxel extends EngineMap { ComponentPhysics physics = new ComponentPhysics(false); PhysicMapVoxel box = new PhysicMapVoxel(tmpVoxelChunk); physics.addShape(box); + physics.setStaticObject(true); tmpEntity.addComponent(physics); this.env.addEntity(tmpEntity); diff --git a/src/org/atriaSoft/gameEngine/physics/PhysicBox.java b/src/org/atriaSoft/gameEngine/physics/PhysicBox.java index 8edfe03..25b929f 100644 --- a/src/org/atriaSoft/gameEngine/physics/PhysicBox.java +++ b/src/org/atriaSoft/gameEngine/physics/PhysicBox.java @@ -29,6 +29,20 @@ public class PhysicBox extends PhysicShape { aabb.update(transform.multiply(this.transform.multiply(new Vector3f(-this.size.x*0.5f,-this.size.y*0.5f,-this.size.z*0.5f)))); aabb.update(transform.multiply(this.transform.multiply(new Vector3f(this.size.x*0.5f,-this.size.y*0.5f,-this.size.z*0.5f)))); } + // only needed for the narrow phase calculation ... + public Vector3f narrowPhaseGlobalPos; + public Vector3f narrowPhaseAxisX = new Vector3f(1,0,0); + public Vector3f narrowPhaseAxisY = new Vector3f(1,0,0); + public Vector3f narrowPhaseAxisZ = new Vector3f(1,0,0); + public Vector3f narrowPhaseHalfSize; + @Override + public void updateForNarrowCollision(Transform3D transform) { + this.narrowPhaseGlobalPos = transform.multiply(this.transform.multiply(new Vector3f(0,0,0))); + this.narrowPhaseAxisX = transform.multiply(this.transform.multiply(new Vector3f(1,0,0))).less(this.narrowPhaseGlobalPos); + this.narrowPhaseAxisY = transform.multiply(this.transform.multiply(new Vector3f(0,1,0))).less(this.narrowPhaseGlobalPos); + this.narrowPhaseAxisZ = transform.multiply(this.transform.multiply(new Vector3f(0,0,1))).less(this.narrowPhaseGlobalPos); + this.narrowPhaseHalfSize = this.size.multiply_new(0.5f); + } @Override public void renderDebug(Transform3D transform, ResourceColored3DObject debugDrawProperty) { debugDrawProperty.drawSquare(this.size.multiply_new(0.5f), this.transform.getOpenGLMatrix().multiply_new(transform.getOpenGLMatrix()), new Color(0,1,0,0.25f)); diff --git a/src/org/atriaSoft/gameEngine/physics/PhysicMapVoxel.java b/src/org/atriaSoft/gameEngine/physics/PhysicMapVoxel.java index eaa4ac4..eb3d6df 100644 --- a/src/org/atriaSoft/gameEngine/physics/PhysicMapVoxel.java +++ b/src/org/atriaSoft/gameEngine/physics/PhysicMapVoxel.java @@ -23,6 +23,16 @@ public class PhysicMapVoxel extends PhysicShape { this.chunk.getPosition().y + VoxelChunk.VOXEL_CHUNK_SIZE, this.chunk.getPosition().z + VoxelChunk.VOXEL_CHUNK_SIZE)); } + // only needed for the narrow phase calculation ... + private Vector3f narrowPhaseGlobalPos; + private Vector3f narrowPhaseAxisX = new Vector3f(1,0,0); + private Vector3f narrowPhaseAxisY = new Vector3f(1,0,0); + private Vector3f narrowPhaseAxisZ = new Vector3f(1,0,0); + private Vector3f narrowPhaseHalfSize = new Vector3f(0.5f,0.5f,0.5f); + @Override + public void updateForNarrowCollision(Transform3D transform) { + this.narrowPhaseGlobalPos = transform.multiply(this.transform.multiply(new Vector3f(0,0,0))); + } @Override public void renderDebug(Transform3D transform, ResourceColored3DObject debugDrawProperty) { diff --git a/src/org/atriaSoft/gameEngine/physics/PhysicShape.java b/src/org/atriaSoft/gameEngine/physics/PhysicShape.java index 12d0028..6d8aa4c 100644 --- a/src/org/atriaSoft/gameEngine/physics/PhysicShape.java +++ b/src/org/atriaSoft/gameEngine/physics/PhysicShape.java @@ -54,6 +54,7 @@ public abstract class PhysicShape { return type; } public abstract void updateAABB(Transform3D transform, PhysicCollisionAABB aabb); + public abstract void updateForNarrowCollision(Transform3D transform); public abstract void renderDebug(Transform3D transform, ResourceColored3DObject debugDrawProperty); } diff --git a/src/org/atriaSoft/gameEngine/physics/PhysicSphere.java b/src/org/atriaSoft/gameEngine/physics/PhysicSphere.java index 24973ab..ad0391a 100644 --- a/src/org/atriaSoft/gameEngine/physics/PhysicSphere.java +++ b/src/org/atriaSoft/gameEngine/physics/PhysicSphere.java @@ -25,6 +25,10 @@ public class PhysicSphere extends PhysicShape { aabb.update(transform.multiply(this.transform.getPosition()).add_new(new Vector3f(0,-this.size,0))); aabb.update(transform.multiply(this.transform.getPosition()).add_new(new Vector3f(0,0,this.size))); aabb.update(transform.multiply(this.transform.getPosition()).add_new(new Vector3f(0,0,-this.size))); + } + @Override + public void updateForNarrowCollision(Transform3D transform) { + } @Override public void renderDebug(Transform3D transform, ResourceColored3DObject debugDrawProperty) { diff --git a/src/org/atriaSoft/gameEngine/physics/ToolCollisionOBBWithOBB.java b/src/org/atriaSoft/gameEngine/physics/ToolCollisionOBBWithOBB.java new file mode 100644 index 0000000..23c7c84 --- /dev/null +++ b/src/org/atriaSoft/gameEngine/physics/ToolCollisionOBBWithOBB.java @@ -0,0 +1,127 @@ +package org.atriaSoft.gameEngine.physics; + +import org.atriaSoft.etk.math.Vector3f; +import org.atriaSoft.gale.test.sample2.Log; + +// set the relevant elements of our oriented bounding box +class OBB +{ + public Vector3f Pos, AxisX, AxisY, AxisZ, Half_size; + public OBB() { + Pos = new Vector3f(); + AxisX = new Vector3f(); + AxisY = new Vector3f(); + AxisZ = new Vector3f(); + Half_size = new Vector3f(); + } +}; + +public class ToolCollisionOBBWithOBB { + // check if there's a separating plane in between the selected axes + private static boolean getSeparatingPlane(Vector3f RPos, Vector3f Plane, OBB box1, OBB box2) { + return (Math.abs(RPos.dot(Plane)) > + (Math.abs(box1.AxisX.multiply_new(box1.Half_size.x).dot(Plane)) + + Math.abs(box1.AxisY.multiply_new(box1.Half_size.y).dot(Plane)) + + Math.abs(box1.AxisZ.multiply_new(box1.Half_size.z).dot(Plane)) + + Math.abs(box2.AxisX.multiply_new(box2.Half_size.x).dot(Plane)) + + Math.abs(box2.AxisY.multiply_new(box2.Half_size.y).dot(Plane)) + + Math.abs(box2.AxisZ.multiply_new(box2.Half_size.z).dot(Plane)) + ) + ); + } + + // test for separating planes in all 15 axes + private static boolean getCollision(OBB box1, OBB box2) { + Vector3f RPos = box2.Pos.less_new(box1.Pos); + boolean ret = + getSeparatingPlane(RPos, box1.AxisX, box1, box2) || + getSeparatingPlane(RPos, box1.AxisY, box1, box2) || + getSeparatingPlane(RPos, box1.AxisZ, box1, box2) || + getSeparatingPlane(RPos, box2.AxisX, box1, box2) || + getSeparatingPlane(RPos, box2.AxisY, box1, box2) || + getSeparatingPlane(RPos, box2.AxisZ, box1, box2) || + getSeparatingPlane(RPos, box1.AxisX.cross(box2.AxisX), box1, box2) || + getSeparatingPlane(RPos, box1.AxisX.cross(box2.AxisY), box1, box2) || + getSeparatingPlane(RPos, box1.AxisX.cross(box2.AxisZ), box1, box2) || + getSeparatingPlane(RPos, box1.AxisY.cross(box2.AxisX), box1, box2) || + getSeparatingPlane(RPos, box1.AxisY.cross(box2.AxisY), box1, box2) || + getSeparatingPlane(RPos, box1.AxisY.cross(box2.AxisZ), box1, box2) || + getSeparatingPlane(RPos, box1.AxisZ.cross(box2.AxisX), box1, box2) || + getSeparatingPlane(RPos, box1.AxisZ.cross(box2.AxisY), box1, box2) || + getSeparatingPlane(RPos, box1.AxisZ.cross(box2.AxisZ), box1, box2); + return ! ret; + } + + // a quick test to see the code working + public static void main(String[] args) { + // create two obbs + OBB A = new OBB(); + OBB B = new OBB();; + + // set the first obb's properties + A.Pos = new Vector3f(0.0f, 0.0f, 0.0f); // set its center position + + // set the half size + A.Half_size = new Vector3f(10.0f, 1.0f, 1.0f); + + // set the axes orientation + A.AxisX = new Vector3f(1.0f, 0.0f, 0.0f); + A.AxisY = new Vector3f(0.0f, 1.0f, 0.0f); + A.AxisZ = new Vector3f(0.0f, 0.0f, 1.0f); + + // set the second obb's properties + B.Pos = new Vector3f(20.0f, 0.0f, 0.0f); // set its center position + + // set the half size + B.Half_size = new Vector3f(10.0f, 1.0f, 1.0f); + + // set the axes orientation + B.AxisX = new Vector3f(1.0f, 0.0f, 0.0f); + B.AxisY = new Vector3f(0.0f, 1.0f, 0.0f); + B.AxisZ = new Vector3f(0.0f, 0.0f, 1.0f); + + // run the code and get the result as a message + if (getCollision(A, B) == true) { + Log.info("Collision!!!"); + } else { + Log.info("NO Collision!!!"); + } + } + + + // check if there's a separating plane in between the selected axes + private static boolean getSeparatingPlane222(Vector3f RPos, Vector3f Plane, PhysicBox box1, PhysicBox box2) { + return (Math.abs(RPos.dot(Plane)) > + (Math.abs(box1.narrowPhaseAxisX.multiply_new(box1.narrowPhaseHalfSize.x).dot(Plane)) + + Math.abs(box1.narrowPhaseAxisY.multiply_new(box1.narrowPhaseHalfSize.y).dot(Plane)) + + Math.abs(box1.narrowPhaseAxisZ.multiply_new(box1.narrowPhaseHalfSize.z).dot(Plane)) + + Math.abs(box2.narrowPhaseAxisX.multiply_new(box2.narrowPhaseHalfSize.x).dot(Plane)) + + Math.abs(box2.narrowPhaseAxisY.multiply_new(box2.narrowPhaseHalfSize.y).dot(Plane)) + + Math.abs(box2.narrowPhaseAxisZ.multiply_new(box2.narrowPhaseHalfSize.z).dot(Plane)) + ) + ); + } + + public static boolean testCollide(PhysicBox box1, PhysicBox box2) { + + Vector3f RPos = box2.narrowPhaseGlobalPos.less_new(box1.narrowPhaseGlobalPos); + boolean ret = + getSeparatingPlane222(RPos, box1.narrowPhaseAxisX, box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisY, box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisZ, box1, box2) || + getSeparatingPlane222(RPos, box2.narrowPhaseAxisX, box1, box2) || + getSeparatingPlane222(RPos, box2.narrowPhaseAxisY, box1, box2) || + getSeparatingPlane222(RPos, box2.narrowPhaseAxisZ, box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisX.cross(box2.narrowPhaseAxisX), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisX.cross(box2.narrowPhaseAxisY), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisX.cross(box2.narrowPhaseAxisZ), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisY.cross(box2.narrowPhaseAxisX), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisY.cross(box2.narrowPhaseAxisY), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisY.cross(box2.narrowPhaseAxisZ), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisZ.cross(box2.narrowPhaseAxisX), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisZ.cross(box2.narrowPhaseAxisY), box1, box2) || + getSeparatingPlane222(RPos, box1.narrowPhaseAxisZ.cross(box2.narrowPhaseAxisZ), box1, box2); + return ! ret; + } + +} diff --git a/src/org/atriaSoft/gameEngine/sample/LoxelEngine/LoxelApplication.java b/src/org/atriaSoft/gameEngine/sample/LoxelEngine/LoxelApplication.java index 22949bf..7e7369d 100644 --- a/src/org/atriaSoft/gameEngine/sample/LoxelEngine/LoxelApplication.java +++ b/src/org/atriaSoft/gameEngine/sample/LoxelEngine/LoxelApplication.java @@ -79,7 +79,7 @@ public class LoxelApplication extends Application { sun.addComponent(new ComponentPosition(new Transform3D(new Vector3f(1000,1000,1000)))); sun.addComponent(new ComponentLightSun(new Light(new Vector3f(0.4f,0.4f,0.4f), new Vector3f(0,0,0), new Vector3f(0.8f,0,0)))); env.addEntity(sun); - + // add a cube to show where in the light ... Entity localLight = new Entity(this.env); lightPosition = new ComponentPosition(new Transform3D(new Vector3f(-10,-10,17))); @@ -91,6 +91,24 @@ public class LoxelApplication extends Application { new Uri("DATA", "basic.vert"), new Uri("DATA", "basic.frag"))); env.addEntity(localLight); + + // add a cube to test collision ... + Entity localBox = new Entity(this.env); + localBox.addComponent(new ComponentPosition(new Transform3D(new Vector3f(-2,-2,14)))); + localBox.addComponent(new ComponentStaticMesh(new Uri("RES", "cube-one.obj"))); + localBox.addComponent(new ComponentTexture(new Uri("DATA", "blocks/clay.png"))); + localBox.addComponent(new ComponentLight(new Light(new Vector3f(0,1,0), new Vector3f(0,0,0), new Vector3f(0.8f,0.03f,0.002f)))); + localBox.addComponent(new ComponentRenderTexturedStaticMesh( + new Uri("DATA", "basic.vert"), + new Uri("DATA", "basic.frag"))); + ComponentPhysics physics2 = new ComponentPhysics(true); + PhysicBox box2 = new PhysicBox(); + box2.setSize(new Vector3f(1,1,1)); + box2.setOrigin(new Vector3f(0,0,0)); + box2.setMass(1); + physics2.addShape(box2); + localBox.addComponent(physics2); + env.addEntity(localBox); Entity gird = new Entity(this.env); gird.addComponent(new ComponentPosition(new Transform3D(new Vector3f(0,0,13)))); diff --git a/srcTest/test/atriaSoft/etk/math/testTransformation3D.java b/srcTest/test/atriaSoft/etk/math/testTransformation3D.java new file mode 100644 index 0000000..69dd4d0 --- /dev/null +++ b/srcTest/test/atriaSoft/etk/math/testTransformation3D.java @@ -0,0 +1,77 @@ +package test.atriaSoft.etk.math; + +import org.atriaSoft.gameEngine.geometry.AABB; +import org.atriaSoft.gameEngine.geometry.OBB; +import org.atriaSoft.gameEngine.geometry.Geometry3D; +import org.atriaSoft.gameEngine.geometry.Plane; +import org.atriaSoft.gameEngine.geometry.Sphere; +import org.atriaSoft.gameEngine.geometry.Triangle; +import org.atriaSoft.etk.math.Matrix3f; +import org.atriaSoft.etk.math.Vector3f; +import org.junit.jupiter.api.*; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class testTransformation3D{ + + @Test + void testPointInLine() { + Sphere shape = new Sphere(new Vector3f(4,4,4), 2); + assertFalse(Geometry3D.pointInSphere(new Vector3f(0,0,0), shape)); + assertFalse(Geometry3D.pointInSphere(new Vector3f(6,6,6), shape)); + assertTrue(Geometry3D.pointInSphere(new Vector3f(3,3,3), shape)); + assertTrue(Geometry3D.pointInSphere(new Vector3f(4,4,4), shape)); + assertTrue(Geometry3D.pointInSphere(new Vector3f(4,4,2.0001f), shape)); + assertTrue(Geometry3D.pointInSphere(new Vector3f(4,2.0001f,4), shape)); + assertTrue(Geometry3D.pointInSphere(new Vector3f(2.0001f,4,4), shape)); + } + @Test + void testPointInAABB() { + AABB shape = new AABB(new Vector3f(4,4,4), new Vector3f(1,2,3)); + assertFalse(Geometry3D.pointInAABB(new Vector3f(0,0,0), shape)); + assertFalse(Geometry3D.pointInAABB(new Vector3f(6,6,6), shape)); + assertTrue(Geometry3D.pointInAABB(new Vector3f(3,3,3), shape)); + assertTrue(Geometry3D.pointInAABB(new Vector3f(4,4,4), shape)); + assertTrue(Geometry3D.pointInAABB(new Vector3f(4,4,1.0001f), shape)); + assertTrue(Geometry3D.pointInAABB(new Vector3f(4,2.0001f,4), shape)); + assertTrue(Geometry3D.pointInAABB(new Vector3f(3.0001f,4,4), shape)); + } + @Test + void testPointInOBB() { + Matrix3f orientation = Matrix3f.identity(); + orientation.multiply(Matrix3f.createMatrixRotate(new Vector3f(0,0,1), (float)Math.toRadians(45))); + OBB shape = new OBB(new Vector3f(4,4,4), new Vector3f(1,2,3), orientation); + assertFalse(Geometry3D.pointInOBB(new Vector3f(0,0,0), shape)); + assertFalse(Geometry3D.pointInOBB(new Vector3f(6,6,6), shape)); + assertTrue(Geometry3D.pointInOBB(new Vector3f(3,3,3), shape)); + assertTrue(Geometry3D.pointInOBB(new Vector3f(4,4,4), shape)); + assertTrue(Geometry3D.pointInOBB(new Vector3f(4,4,1.0001f), shape)); + assertTrue(Geometry3D.pointInOBB(new Vector3f(4,2.0001f,4), shape)); + assertTrue(Geometry3D.pointInOBB(new Vector3f(3.0001f,4,4), shape)); + } + @Test + void testPointInPlane() { + Plane shape = new Plane((new Vector3f(4,4,4)).normalize(), (float)Math.sqrt(1*1+1*1)); + assertFalse(Geometry3D.pointInPlane(new Vector3f(0,0,0), shape)); + assertFalse(Geometry3D.pointInPlane(new Vector3f(6,6,6), shape)); + assertTrue(Geometry3D.pointInPlane(new Vector3f(3,3,3), shape)); + assertTrue(Geometry3D.pointInPlane(new Vector3f(4,4,4), shape)); + assertTrue(Geometry3D.pointInPlane(new Vector3f(4,4,1.0001f), shape)); + assertTrue(Geometry3D.pointInPlane(new Vector3f(4,2.0001f,4), shape)); + assertTrue(Geometry3D.pointInPlane(new Vector3f(3.0001f,4,4), shape)); + } + @Test + void testPointInTriangle() { + Triangle shape = new Triangle(new Vector3f(1,0,0), new Vector3f(0,1,0), new Vector3f(0,0,1)); +// assertTrue(Geometry3D.pointInTriangle(new Vector3f(1,0,0), shape)); +// assertTrue(Geometry3D.pointInTriangle(new Vector3f(0,1,0), shape)); +// assertTrue(Geometry3D.pointInTriangle(new Vector3f(0,0,1), shape)); +// assertFalse(Geometry3D.pointInTriangle(new Vector3f(5252,25252521,41458), shape)); +// assertFalse(Geometry3D.pointInTriangle(new Vector3f(1,1,1), shape)); + assertFalse(Geometry3D.pointInTriangle(new Vector3f(0.1f,0.1f,0.1f), shape)); + assertFalse(Geometry3D.pointInTriangle(new Vector3f(0,0,0), shape)); + } +}