1 module unecht.core.components.physics.system; 2 3 import derelict.ode.ode; 4 import derelict.util.system; 5 6 import unecht.core.hideFlags; 7 import unecht.core.component; 8 import unecht.core.defaultInspector; 9 import unecht.core.components.physics.material; 10 11 import gl3n.linalg; 12 13 version(UEProfiling)import unecht.core.profiler; 14 15 /// 16 @UEDefaultInspector!UEPhysicsSystem 17 final class UEPhysicsSystem : UEComponent { 18 19 mixin(UERegisterObject!()); 20 21 static dWorldID world; 22 static dSpaceID space; 23 24 private static bool initialised; 25 private static dJointGroupID contactgroup; 26 27 /// 28 static void setGravity(vec3 _v) 29 { 30 dWorldSetGravity(world, _v.x,_v.y,_v.z); 31 } 32 33 /// 34 override void onCreate() { 35 36 sceneNode.hideFlags.set(HideFlags.hideInHirarchie); 37 38 if(!initialised) 39 { 40 DerelictODE.load(); 41 dInitODE (); 42 43 // Create a new, empty world and assign its ID number to World. Most applications will only need one world. 44 world = dWorldCreate(); 45 46 // Create a new collision space and assign its ID number to Space, passing 0 instead of an existing dSpaceID. 47 // There are three different types of collision spaces we could create here depending on the number of objects 48 // in the world but dSimpleSpaceCreate is fine for a small number of objects. If there were more objects we 49 // would be using dHashSpaceCreate or dQuadTreeSpaceCreate (look these up in the ODE docs) 50 space = dSimpleSpaceCreate(null); 51 52 // Create a joint group object and assign its ID number to contactgroup. dJointGroupCreate used to have a 53 // max_size parameter but it is no longer used so we just pass 0 as its argument. 54 contactgroup = dJointGroupCreate(0); 55 56 // Now we set the gravity vector for our world by passing World as the first argument to dWorldSetGravity. 57 // Earth's gravity vector would be (0, -9.81, 0) assuming that +Y is up. I found that a lighter gravity looked 58 // more realistic in this case. 59 dWorldSetGravity(world, 0, -1, 0); 60 61 // These next two functions control how much error correcting and constraint force mixing occurs in the world. 62 // Don't worry about these for now as they are set to the default values and we could happily delete them from 63 // this example. Different values, however, can drastically change the behaviour of the objects colliding, so 64 // I suggest you look up the full info on them in the ODE docs. 65 dWorldSetERP(world, 0.2); 66 67 dWorldSetCFM(world, 1e-5); 68 69 // This function sets the velocity that interpenetrating objects will separate at. The default value is infinity. 70 dWorldSetContactMaxCorrectingVel(world, 0.9); 71 72 // This function sets the depth of the surface layer around the world objects. Contacts are allowed to sink into 73 // each other up to this depth. Setting it to a small value reduces the amount of jittering between contacting 74 // objects, the default value is 0. 75 dWorldSetContactSurfaceLayer(world, 0.001); 76 77 // To save some CPU time we set the auto disable flag to 1. This means that objects that have come to rest (based 78 // on their current linear and angular velocity) will no longer participate in the simulation, unless acted upon 79 // by a moving object. If you do not want to use this feature then set the flag to 0. You can also manually enable 80 // or disable objects using dBodyEnable and dBodyDisable, see the docs for more info on this. 81 dWorldSetAutoDisableFlag(world, 1); 82 83 initialised = true; 84 } 85 } 86 87 /// 88 override void onDestroy() { 89 if(initialised) 90 { 91 if(world) 92 dWorldDestroy(world); 93 94 if(contactgroup) 95 dJointGroupDestroy(contactgroup); 96 97 world = null; 98 contactgroup = null; 99 100 initialised = false; 101 102 dCloseODE(); 103 } 104 } 105 106 /// 107 override void onUpdate() { 108 version(UEProfiling) 109 auto profZone = Zone(profiler, "physics update"); 110 111 _collisionsThisFrame = 0; 112 113 // Remove all temporary collision joints 114 dJointGroupEmpty(contactgroup); 115 116 { 117 version(UEProfiling) 118 auto profZone2 = Zone(profiler, "physics collide"); 119 /++ 120 + dSpaceCollide determines which pairs of geoms in the space we pass to it may potentially intersect. 121 + We must also pass the address of a callback function that we will provide. 122 + The callback function is responsible for determining which of the potential intersections 123 + are actual collisions before adding the collision joints to our joint group called contactgroup, 124 + this gives us the chance to set the behaviour of these joints before adding them to the group. 125 + The second parameter is a pointer to any data that we may want to pass to our callback routine. 126 + We will cover the details of the nearCallback routine in the next section. 127 +/ 128 dSpaceCollide(space, null, &nearCallback); 129 } 130 131 { 132 version(UEProfiling) 133 auto profZone2 = Zone(profiler, "physics step"); 134 /+ 135 + Now we advance the simulation by calling dWorldQuickStep. 136 + This is a faster version of dWorldStep but it is also 137 + slightly less accurate. As well as the World object ID we also pass a step size value. 138 + In each step the simulation is updated by a certain number of smaller steps or iterations. 139 + The default number of iterations is 20 but you can change this by calling 140 + dWorldSetQuickStepNumIterations. 141 +/ 142 dWorldQuickStep(world, 0.05); 143 } 144 145 propagateCollisions(); 146 } 147 148 /// 149 void propagateCollisions() 150 { 151 version(UEProfiling) 152 auto profZone = Zone(profiler, "physics propagate"); 153 154 foreach(col; _collisions[0.._collisionsThisFrame]) 155 { 156 if(col.c1) 157 col.c1.entity.broadcast!"onCollision"(col.c2); 158 if(col.c2) 159 col.c2.entity.broadcast!"onCollision"(col.c1); 160 } 161 } 162 163 /// 164 private struct Collision 165 { 166 UEComponent c1,c2; 167 } 168 169 private static Collision[1024] _collisions; 170 private static uint _collisionsThisFrame; 171 172 /// 173 private extern(C) @nogc nothrow static void nearCallback(void *data, dGeomID o1, dGeomID o2) 174 { 175 // Get the dynamics body for each geom 176 dBodyID b1 = dGeomGetBody(o1); 177 dBodyID b2 = dGeomGetBody(o2); 178 179 void* gData1 = dGeomGetData(o1); 180 void* gData2 = dGeomGetData(o2); 181 182 UEComponent comp1 = cast(UEComponent)gData1; 183 UEComponent comp2 = cast(UEComponent)gData2; 184 185 UEPhysicsMaterialInfo m1,m2; 186 187 if(comp1) 188 if(auto mat = comp1.entity.getComponent!UEPhysicsMaterial) 189 m1 = mat.materialInfo; 190 191 if(comp2) 192 if(auto mat = comp2.entity.getComponent!UEPhysicsMaterial) 193 m2 = mat.materialInfo; 194 195 dSurfaceParameters surfaceParams; 196 with(surfaceParams){ 197 mode = 0; 198 199 if(m1.isBouncy || m2.isBouncy) 200 mode = dContactBounce; 201 202 mu = (m1.friction + m2.friction) / 2.0f; 203 204 if(m1.friction >= dInfinity || m2.friction >= dInfinity) 205 mu = dInfinity; 206 207 //mu2 = 0; 208 //rho = 0 209 //rho2 = 0; 210 //rhoN = 0; 211 bounce = (m1.bouncyness + m2.bouncyness) / 2.0f; 212 bounce_vel = 0.001; 213 //soft_erp = 0; 214 //soft_cfm = 0; 215 //motion1 = 0; 216 //motion2 = 0; 217 //motionN = 0; 218 //dReal slip1, slip2; 219 } 220 221 // Create an array of dContact objects to hold the contact joints 222 static immutable CONTACT_COUNT = 32; 223 dContact[CONTACT_COUNT] contacts; 224 foreach(ref c; contacts) 225 c.surface = surfaceParams; 226 227 // Here we do the actual collision test by calling dCollide. It returns the number of actual contact points or zero 228 // if there were none. As well as the geom IDs, max number of contacts we also pass the address of a dContactGeom 229 // as the fourth parameter. dContactGeom is a substructure of a dContact object so we simply pass the address of 230 // the first dContactGeom from our array of dContact objects and then pass the offset to the next dContactGeom 231 // as the fifth paramater, which is the size of a dContact structure. That made sense didn't it? 232 if (int numc = dCollide(o1, o2, CONTACT_COUNT, &contacts[0].geom, dContact.sizeof)) 233 { 234 // To add each contact point found to our joint group we call dJointCreateContact which is just one of the many 235 // different joint types available. 236 foreach (i; 0..numc) 237 { 238 // dJointCreateContact needs to know which world and joint group to work with as well as the dContact 239 // object itself. It returns a new dJointID which we then use with dJointAttach to finally create the 240 // temporary contact joint between the two geom bodies. 241 dJointID c = dJointCreateContact(UEPhysicsSystem.world, contactgroup, &contacts[i]); 242 243 dJointAttach(c, b1, b2); 244 } 245 246 auto component1 = cast(UEComponent)gData1; 247 auto component2 = cast(UEComponent)gData2; 248 249 if(_collisionsThisFrame<_collisions.length) 250 _collisions[_collisionsThisFrame++] = Collision(component1,component2); 251 else 252 assert(false); 253 } 254 } 255 }