1 /* 2 Copyright (c) 2013-2020 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 module dagon.ext.physics.rigidbody; 30 31 import std.math; 32 33 import dlib.core.ownership; 34 import dlib.core.memory; 35 import dlib.container.array; 36 import dlib.math.vector; 37 import dlib.math.matrix; 38 import dlib.math.quaternion; 39 import dlib.math.transformation; 40 import dlib.math.utils; 41 42 import dagon.ext.physics.world; 43 import dagon.ext.physics.shape; 44 import dagon.ext.physics.contact; 45 46 interface CollisionDispatcher 47 { 48 void onNewContact(RigidBody rb, Contact c); 49 } 50 51 class RigidBody: Owner 52 { 53 Vector3f position; 54 Quaternionf orientation; 55 56 Vector3f linearVelocity; 57 Vector3f angularVelocity; 58 59 Vector3f pseudoLinearVelocity; 60 Vector3f pseudoAngularVelocity; 61 62 Vector3f linearAcceleration; 63 Vector3f angularAcceleration; 64 65 float mass; 66 float invMass; 67 68 Matrix3x3f inertiaTensor; 69 Matrix3x3f invInertiaTensor; //TODO: rename to invInertiaWorld 70 71 Vector3f centerOfMass; 72 73 Vector3f forceAccumulator; 74 Vector3f torqueAccumulator; 75 76 float bounce; 77 float friction; 78 79 bool active = true; 80 bool useGravity = true; 81 bool enableRotation = true; 82 83 Array!ShapeComponent shapes; 84 85 bool dynamic; 86 87 float damping = 0.5f; 88 float stopThreshold = 0.15f; //0.15f 89 float stopThresholdPV = 0.0f; //0.01f 90 float stopThresholdAngular = 0.2f; 91 92 bool useOwnGravity = false; 93 Vector3f gravity = Vector3f(0, 0, 0); 94 95 Array!CollisionDispatcher collisionDispatchers; 96 97 void contactEvent(Contact c) 98 { 99 foreach(d; collisionDispatchers.data) 100 { 101 d.onNewContact(this, c); 102 } 103 } 104 105 bool raycastable = true; 106 uint numContacts = 0; 107 bool useFriction = true; 108 109 float maxSpeed = float.max; 110 111 uint layer = 0; 112 113 this(PhysicsWorld world) 114 { 115 super(world); 116 117 position = Vector3f(0.0f, 0.0f, 0.0f); 118 orientation = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f); 119 120 linearVelocity = Vector3f(0.0f, 0.0f, 0.0f); 121 angularVelocity = Vector3f(0.0f, 0.0f, 0.0f); 122 123 pseudoLinearVelocity = Vector3f(0.0f, 0.0f, 0.0f); 124 pseudoAngularVelocity = Vector3f(0.0f, 0.0f, 0.0f); 125 126 linearAcceleration = Vector3f(0.0f, 0.0f, 0.0f); 127 angularAcceleration = Vector3f(0.0f, 0.0f, 0.0f); 128 129 mass = 1.0f; 130 invMass = 1.0f; 131 132 inertiaTensor = matrixf( 133 mass, 0, 0, 134 0, mass, 0, 135 0, 0, mass 136 ); 137 invInertiaTensor = matrixf( 138 0, 0, 0, 139 0, 0, 0, 140 0, 0, 0 141 ); 142 143 centerOfMass = Vector3f(0.0f, 0.0f, 0.0f); 144 145 forceAccumulator = Vector3f(0.0f, 0.0f, 0.0f); 146 torqueAccumulator = Vector3f(0.0f, 0.0f, 0.0f); 147 148 bounce = 0.0f; 149 friction = 0.5f; 150 151 dynamic = true; 152 } 153 154 Matrix4x4f transformation() 155 { 156 Matrix4x4f t; 157 t = translationMatrix(position); 158 t *= orientation.toMatrix4x4(); 159 return t; 160 } 161 162 void addShapeComponent(ShapeComponent shape) 163 { 164 shape.transformation = transformation() * translationMatrix(shape.centroid); 165 shapes.append(shape); 166 167 if (!dynamic) 168 return; 169 170 centerOfMass = Vector3f(0, 0, 0); 171 float m = 0.0f; 172 173 foreach (sh; shapes.data) 174 { 175 m += sh.mass; 176 centerOfMass += sh.mass * sh.centroid; 177 } 178 179 float invm = 1.0f / m; 180 centerOfMass *= invm; 181 182 mass += shape.mass; 183 invMass = 1.0f / mass; 184 185 // Compute inertia tensor using Huygens-Steiner theorem 186 inertiaTensor = Matrix3x3f.zero; 187 foreach (sh; shapes.data) 188 { 189 Vector3f r = centerOfMass - sh.centroid; 190 inertiaTensor += 191 sh.geometry.inertiaTensor(sh.mass) + 192 (Matrix3x3f.identity * dot(r, r) - tensorProduct(r, r)) * sh.mass; 193 } 194 195 invInertiaTensor = inertiaTensor.inverse; 196 } 197 198 void integrateForces(float dt) 199 { 200 if (!active || !dynamic) 201 return; 202 203 linearAcceleration = forceAccumulator * invMass; 204 if (enableRotation) 205 angularAcceleration = torqueAccumulator * invInertiaTensor; 206 207 linearVelocity += linearAcceleration * dt; 208 if (enableRotation) 209 angularVelocity += angularAcceleration * dt; 210 } 211 212 void integrateVelocities(float dt) 213 { 214 if (!active || !dynamic) 215 return; 216 217 float d = clamp(1.0f - dt * damping, 0.0f, 1.0f); 218 linearVelocity *= d; 219 angularVelocity *= d; 220 221 float speed = linearVelocity.length; 222 223 if (speed > maxSpeed) 224 linearVelocity = linearVelocity.normalized * maxSpeed; 225 226 if (speed > stopThreshold /* || numContacts < 3 */) 227 { 228 position += linearVelocity * dt; 229 } 230 231 if (enableRotation) 232 if (angularVelocity.length > stopThresholdAngular /* || numContacts < 3 */) //stopThreshold 233 { 234 orientation += 0.5f * Quaternionf(angularVelocity, 0.0f) * orientation * dt; 235 orientation.normalize(); 236 } 237 } 238 239 void integratePseudoVelocities(float dt) 240 { 241 if (!active || !dynamic) 242 return; 243 244 float d = clamp(1.0f - dt * damping, 0.0f, 1.0f); 245 246 pseudoLinearVelocity *= d; 247 pseudoAngularVelocity *= d; 248 249 if (pseudoLinearVelocity.length > stopThresholdPV) 250 { 251 position += pseudoLinearVelocity; 252 } 253 254 if (enableRotation) 255 if (pseudoAngularVelocity.length > stopThresholdPV) 256 { 257 orientation += 0.5f * Quaternionf(pseudoAngularVelocity, 0.0f) * orientation; 258 orientation.normalize(); 259 } 260 261 pseudoLinearVelocity = Vector3f(0.0f, 0.0f, 0.0f); 262 pseudoAngularVelocity = Vector3f(0.0f, 0.0f, 0.0f); 263 } 264 265 @property Vector3f worldCenterOfMass() 266 { 267 return position + orientation.rotate(centerOfMass); 268 } 269 270 void updateInertia() 271 { 272 if (active && dynamic) 273 { 274 auto rot = orientation.toMatrix3x3; 275 //invInertiaTensor = (rot * inertiaTensor * rot.transposed).inverse; 276 invInertiaTensor = (rot * inertiaTensor.inverse * rot.transposed); 277 } 278 } 279 280 void updateShapeComponents() 281 { 282 foreach (sh; shapes.data) 283 { 284 sh.transformation = transformation() * translationMatrix(sh.centroid); 285 } 286 } 287 288 void resetForces() 289 { 290 forceAccumulator = Vector3f(0, 0, 0); 291 torqueAccumulator = Vector3f(0, 0, 0); 292 } 293 294 void applyForce(Vector3f force) 295 { 296 if (!active || !dynamic) 297 return; 298 299 forceAccumulator += force; 300 } 301 302 void applyTorque(Vector3f torque) 303 { 304 if (!active || !dynamic) 305 return; 306 307 torqueAccumulator += torque; 308 } 309 310 void applyImpulse(Vector3f impulse, Vector3f point) 311 { 312 if (!active || !dynamic) 313 return; 314 315 linearVelocity += impulse * invMass; 316 Vector3f angularImpulse = cross(point - worldCenterOfMass, impulse); 317 angularVelocity += angularImpulse * invInertiaTensor; 318 } 319 320 void applyForceAtPos(Vector3f force, Vector3f pos) 321 { 322 if (!active || !dynamic) 323 return; 324 325 forceAccumulator += force; 326 torqueAccumulator += cross(pos - worldCenterOfMass, force); 327 } 328 329 void applyPseudoImpulse(Vector3f impulse, Vector3f point) 330 { 331 if (!active || !dynamic) 332 return; 333 334 pseudoLinearVelocity += impulse * invMass; 335 Vector3f angularImpulse = cross(point - worldCenterOfMass, impulse); 336 pseudoAngularVelocity += angularImpulse * invInertiaTensor; 337 } 338 339 @property float linearKineticEnergy() 340 { 341 if (!active || !dynamic) 342 return float.infinity; 343 344 // 0.5 * m * v^2 345 float v = linearVelocity.length; 346 return 0.5f * mass * v * v; 347 } 348 349 @property float angularKineticEnergy() 350 { 351 if (!active || !dynamic) 352 return float.infinity; 353 354 // 0.5 * w * I * w 355 Vector3f w = angularVelocity; 356 return 0.5f * dot(w * inertiaTensor, w); 357 } 358 359 ~this() 360 { 361 shapes.free(); 362 collisionDispatchers.free(); 363 } 364 }