babylon.oimoJSPlugin.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. module BABYLON {
  2. declare var OIMO;
  3. export class OimoJSPlugin implements IPhysicsEnginePlugin {
  4. private _world;
  5. private _registeredMeshes = [];
  6. public name = "oimo";
  7. private _gravity: Vector3;
  8. private _checkWithEpsilon(value: number): number {
  9. return value < PhysicsEngine.Epsilon ? PhysicsEngine.Epsilon : value;
  10. }
  11. public initialize(iterations?: number): void {
  12. this._world = new OIMO.World(null, null, iterations);
  13. this._world.clear();
  14. }
  15. public setGravity(gravity: Vector3): void {
  16. this._gravity = this._world.gravity = gravity;
  17. }
  18. public getGravity(): Vector3 {
  19. return this._gravity;
  20. }
  21. public registerMesh(mesh: AbstractMesh, impostor: number, options: PhysicsBodyCreationOptions): any {
  22. this.unregisterMesh(mesh);
  23. if (!mesh.rotationQuaternion) {
  24. mesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(mesh.rotation.y, mesh.rotation.x, mesh.rotation.z);
  25. }
  26. mesh.computeWorldMatrix(true);
  27. var bbox = mesh.getBoundingInfo().boundingBox;
  28. // The delta between the mesh position and the mesh bounding box center
  29. var deltaPosition = mesh.position.subtract(bbox.center);
  30. //calculate rotation to fit Oimo's needs (Euler...)
  31. var rot = new OIMO.Euler().setFromQuaternion({ x: mesh.rotationQuaternion.x, y: mesh.rotationQuaternion.y, z: mesh.rotationQuaternion.z, s: mesh.rotationQuaternion.w });
  32. //get the correct bounding box
  33. var oldQuaternion = mesh.rotationQuaternion;
  34. mesh.rotationQuaternion = new Quaternion(0, 0, 0, 1);
  35. mesh.computeWorldMatrix(true);
  36. var bodyConfig: any = {
  37. name: mesh.uniqueId,
  38. pos: [bbox.center.x, bbox.center.y, bbox.center.z],
  39. rot: [rot.x / OIMO.TO_RAD, rot.y / OIMO.TO_RAD, rot.z / OIMO.TO_RAD],
  40. move: options.mass != 0,
  41. config: [options.mass, options.friction, options.restitution],
  42. world: this._world
  43. };
  44. // register mesh
  45. switch (impostor) {
  46. case PhysicsEngine.SphereImpostor:
  47. var radiusX = bbox.maximumWorld.x - bbox.minimumWorld.x;
  48. var radiusY = bbox.maximumWorld.y - bbox.minimumWorld.y;
  49. var radiusZ = bbox.maximumWorld.z - bbox.minimumWorld.z;
  50. var size = Math.max(
  51. this._checkWithEpsilon(radiusX),
  52. this._checkWithEpsilon(radiusY),
  53. this._checkWithEpsilon(radiusZ)) / 2;
  54. bodyConfig.type = 'sphere';
  55. bodyConfig.size = [size];
  56. break;
  57. case PhysicsEngine.PlaneImpostor:
  58. //Oimo "fakes" a cylinder as a box, so why don't we!
  59. case PhysicsEngine.CylinderImpostor:
  60. case PhysicsEngine.BoxImpostor:
  61. var min = bbox.minimumWorld;
  62. var max = bbox.maximumWorld;
  63. var box = max.subtract(min);
  64. var sizeX = this._checkWithEpsilon(box.x);
  65. var sizeY = this._checkWithEpsilon(box.y);
  66. var sizeZ = this._checkWithEpsilon(box.z);
  67. bodyConfig.type = 'box';
  68. bodyConfig.size = [sizeX, sizeY, sizeZ];
  69. break;
  70. }
  71. var body = new OIMO.Body(bodyConfig);
  72. //We have to access the rigid body's properties to set the quaternion.
  73. //The setQuaternion function of Oimo only sets the newOrientation that is only set after an impulse is given or a collision.
  74. //body.body.orientation = new OIMO.Quat(mesh.rotationQuaternion.w, mesh.rotationQuaternion.x, mesh.rotationQuaternion.y, mesh.rotationQuaternion.z);
  75. //TEST
  76. //body.body.resetQuaternion(new OIMO.Quat(mesh.rotationQuaternion.w, mesh.rotationQuaternion.x, mesh.rotationQuaternion.y, mesh.rotationQuaternion.z));
  77. //update the internal rotation matrix
  78. //body.body.syncShapes();
  79. this._registeredMeshes.push({
  80. mesh: mesh,
  81. body: body,
  82. delta: deltaPosition
  83. });
  84. //for the sake of consistency.
  85. mesh.rotationQuaternion = oldQuaternion;
  86. return body;
  87. }
  88. public registerMeshesAsCompound(parts: PhysicsCompoundBodyPart[], options: PhysicsBodyCreationOptions): any {
  89. var types = [],
  90. sizes = [],
  91. positions = [],
  92. rotations = [];
  93. var initialMesh = parts[0].mesh;
  94. for (var index = 0; index < parts.length; index++) {
  95. var part = parts[index];
  96. var bodyParameters = this._createBodyAsCompound(part, options, initialMesh);
  97. types.push(bodyParameters.type);
  98. sizes.push.apply(sizes, bodyParameters.size);
  99. positions.push.apply(positions, bodyParameters.pos);
  100. rotations.push.apply(rotations, bodyParameters.rot);
  101. }
  102. var body = new OIMO.Body({
  103. name: initialMesh.uniqueId,
  104. type: types,
  105. size: sizes,
  106. pos: positions,
  107. rot: rotations,
  108. move: options.mass != 0,
  109. config: [options.mass, options.friction, options.restitution],
  110. world: this._world
  111. });
  112. //Reset the body's rotation to be of the initial mesh's.
  113. var rot = new OIMO.Euler().setFromQuaternion({ x: initialMesh.rotationQuaternion.x, y: initialMesh.rotationQuaternion.y, z: initialMesh.rotationQuaternion.z, s: initialMesh.rotationQuaternion.w });
  114. body.resetRotation(rot.x / OIMO.TO_RAD, rot.y / OIMO.TO_RAD, rot.z / OIMO.TO_RAD);
  115. this._registeredMeshes.push({
  116. mesh: initialMesh,
  117. body: body
  118. });
  119. return body;
  120. }
  121. private _createBodyAsCompound(part: PhysicsCompoundBodyPart, options: PhysicsBodyCreationOptions, initialMesh: AbstractMesh): any {
  122. var mesh = part.mesh;
  123. if (!mesh.rotationQuaternion) {
  124. mesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(mesh.rotation.y, mesh.rotation.x, mesh.rotation.z);
  125. }
  126. // We need the bounding box/sphere info to compute the physics body
  127. mesh.computeWorldMatrix(true);
  128. var rot = new OIMO.Euler().setFromQuaternion({ x: mesh.rotationQuaternion.x, y: mesh.rotationQuaternion.y, z: mesh.rotationQuaternion.z, s: mesh.rotationQuaternion.w });
  129. var bodyParameters: any = {
  130. name: mesh.uniqueId,
  131. pos: [mesh.position.x, mesh.position.y, mesh.position.z],
  132. //A bug in Oimo (Body class) prevents us from using rot directly.
  133. rot: [0, 0, 0],
  134. //For future reference, if the bug will ever be fixed.
  135. realRot: [rot.x / OIMO.TO_RAD, rot.y / OIMO.TO_RAD, rot.z / OIMO.TO_RAD]
  136. };
  137. var oldQuaternion = mesh.rotationQuaternion;
  138. mesh.rotationQuaternion = new Quaternion(0, 0, 0, 1);
  139. mesh.computeWorldMatrix(true);
  140. switch (part.impostor) {
  141. case PhysicsEngine.SphereImpostor:
  142. var bbox = mesh.getBoundingInfo().boundingBox;
  143. var radiusX = bbox.maximumWorld.x - bbox.minimumWorld.x;
  144. var radiusY = bbox.maximumWorld.y - bbox.minimumWorld.y;
  145. var radiusZ = bbox.maximumWorld.z - bbox.minimumWorld.z;
  146. var size = Math.max(
  147. this._checkWithEpsilon(radiusX),
  148. this._checkWithEpsilon(radiusY),
  149. this._checkWithEpsilon(radiusZ)) / 2;
  150. bodyParameters.type = 'sphere';
  151. bodyParameters.size = [size, size, size];
  152. break;
  153. case PhysicsEngine.PlaneImpostor:
  154. case PhysicsEngine.CylinderImpostor:
  155. case PhysicsEngine.BoxImpostor:
  156. bbox = mesh.getBoundingInfo().boundingBox;
  157. var min = bbox.minimumWorld;
  158. var max = bbox.maximumWorld;
  159. var box = max.subtract(min);
  160. var sizeX = this._checkWithEpsilon(box.x);
  161. var sizeY = this._checkWithEpsilon(box.y);
  162. var sizeZ = this._checkWithEpsilon(box.z);
  163. bodyParameters.type = 'box';
  164. bodyParameters.size = [sizeX, sizeY, sizeZ];
  165. break;
  166. }
  167. mesh.rotationQuaternion = oldQuaternion;
  168. return bodyParameters;
  169. }
  170. public unregisterMesh(mesh: AbstractMesh): void {
  171. for (var index = 0; index < this._registeredMeshes.length; index++) {
  172. var registeredMesh = this._registeredMeshes[index];
  173. if (registeredMesh.mesh === mesh || registeredMesh.mesh === mesh.parent) {
  174. if (registeredMesh.body) {
  175. this._world.removeRigidBody(registeredMesh.body.body);
  176. this._unbindBody(registeredMesh.body);
  177. }
  178. this._registeredMeshes.splice(index, 1);
  179. return;
  180. }
  181. }
  182. }
  183. private _unbindBody(body: any): void {
  184. for (var index = 0; index < this._registeredMeshes.length; index++) {
  185. var registeredMesh = this._registeredMeshes[index];
  186. if (registeredMesh.body === body) {
  187. registeredMesh.body = null;
  188. }
  189. }
  190. }
  191. /**
  192. * Update the body position according to the mesh position
  193. * @param mesh
  194. */
  195. public updateBodyPosition = function (mesh: AbstractMesh): void {
  196. for (var index = 0; index < this._registeredMeshes.length; index++) {
  197. var registeredMesh = this._registeredMeshes[index];
  198. var body = registeredMesh.body.body;
  199. var updated: boolean = false;
  200. var newPosition: Vector3;
  201. if (registeredMesh.mesh === mesh || registeredMesh.mesh === mesh.parent) {
  202. mesh.computeWorldMatrix(true);
  203. newPosition = mesh.getBoundingInfo().boundingBox.center;
  204. updated = true;
  205. }
  206. // Case where the parent has been updated
  207. else if (registeredMesh.mesh.parent === mesh) {
  208. mesh.computeWorldMatrix(true);
  209. registeredMesh.mesh.computeWorldMatrix(true);
  210. newPosition = registeredMesh.mesh.getAbsolutePosition();
  211. updated = true;
  212. }
  213. if (updated) {
  214. body.setPosition(new OIMO.Vec3(newPosition.x, newPosition.y, newPosition.z));
  215. body.setQuaternion(mesh.rotationQuaternion);
  216. body.sleeping = false;
  217. //force Oimo to update the body's position
  218. body.updatePosition(1);
  219. }
  220. }
  221. }
  222. public applyImpulse(mesh: AbstractMesh, force: Vector3, contactPoint: Vector3): void {
  223. for (var index = 0; index < this._registeredMeshes.length; index++) {
  224. var registeredMesh = this._registeredMeshes[index];
  225. if (registeredMesh.mesh === mesh || registeredMesh.mesh === mesh.parent) {
  226. // Get object mass to have a behaviour similar to cannon.js
  227. var mass = registeredMesh.body.body.massInfo.mass;
  228. // The force is scaled with the mass of object
  229. registeredMesh.body.body.applyImpulse(contactPoint.scale(OIMO.INV_SCALE), force.scale(OIMO.INV_SCALE * mass));
  230. return;
  231. }
  232. }
  233. }
  234. public createLink(mesh1: AbstractMesh, mesh2: AbstractMesh, pivot1: Vector3, pivot2: Vector3, options?: any): boolean {
  235. var body1 = null,
  236. body2 = null;
  237. for (var index = 0; index < this._registeredMeshes.length; index++) {
  238. var registeredMesh = this._registeredMeshes[index];
  239. if (registeredMesh.mesh === mesh1) {
  240. body1 = registeredMesh.body.body;
  241. } else if (registeredMesh.mesh === mesh2) {
  242. body2 = registeredMesh.body.body;
  243. }
  244. }
  245. if (!body1 || !body2) {
  246. return false;
  247. }
  248. if (!options) {
  249. options = {};
  250. }
  251. new OIMO.Link({
  252. type: options.type,
  253. body1: body1,
  254. body2: body2,
  255. min: options.min,
  256. max: options.max,
  257. axe1: options.axe1,
  258. axe2: options.axe2,
  259. pos1: [pivot1.x, pivot1.y, pivot1.z],
  260. pos2: [pivot2.x, pivot2.y, pivot2.z],
  261. collision: options.collision,
  262. spring: options.spring,
  263. world: this._world
  264. });
  265. return true;
  266. }
  267. public dispose(): void {
  268. this._world.clear();
  269. while (this._registeredMeshes.length) {
  270. this.unregisterMesh(this._registeredMeshes[0].mesh);
  271. }
  272. }
  273. public isSupported(): boolean {
  274. return OIMO !== undefined;
  275. }
  276. public getWorldObject(): any {
  277. return this._world;
  278. }
  279. public getPhysicsBodyOfMesh(mesh: AbstractMesh) {
  280. for (var index = 0; index < this._registeredMeshes.length; index++) {
  281. var registeredMesh = this._registeredMeshes[index];
  282. if (registeredMesh.mesh === mesh) {
  283. return registeredMesh.body;
  284. }
  285. }
  286. return null;
  287. }
  288. private _getLastShape(body: any): any {
  289. var lastShape = body.shapes;
  290. while (lastShape.next) {
  291. lastShape = lastShape.next;
  292. }
  293. return lastShape;
  294. }
  295. public runOneStep(time: number): void {
  296. this._world.step();
  297. // Update the position of all registered meshes
  298. var i = this._registeredMeshes.length;
  299. var m;
  300. while (i--) {
  301. var body = this._registeredMeshes[i].body.body;
  302. var mesh = this._registeredMeshes[i].mesh;
  303. if (!this._registeredMeshes[i].delta) {
  304. this._registeredMeshes[i].delta = Vector3.Zero();
  305. }
  306. if (!body.sleeping) {
  307. //TODO check that
  308. if (body.shapes.next) {
  309. var parentShape = this._getLastShape(body);
  310. mesh.position.x = parentShape.position.x * OIMO.WORLD_SCALE;
  311. mesh.position.y = parentShape.position.y * OIMO.WORLD_SCALE;
  312. mesh.position.z = parentShape.position.z * OIMO.WORLD_SCALE;
  313. } else {
  314. mesh.position.copyFrom(body.getPosition()).addInPlace(this._registeredMeshes[i].delta);
  315. }
  316. mesh.rotationQuaternion.copyFrom(body.getQuaternion());
  317. mesh.computeWorldMatrix();
  318. }
  319. //check if the collide callback is set.
  320. if (mesh.onPhysicsCollide) {
  321. var meshUniqueName = mesh.uniqueId;
  322. var contact = this._world.contacts;
  323. while (contact !== null) {
  324. //is this body colliding with any other?
  325. if ((contact.body1.name == mesh.uniqueId || contact.body2.name == mesh.uniqueId) && contact.touching && /* !contact.sleeping*/ !contact.body1.sleeping && !contact.body2.sleeping) {
  326. var otherUniqueId = contact.body1.name == mesh.uniqueId ? contact.body2.name : contact.body1.name;
  327. //get the mesh and execute the callback
  328. var otherMesh = mesh.getScene().getMeshByUniqueID(otherUniqueId);
  329. if (otherMesh)
  330. mesh.onPhysicsCollide(otherMesh);
  331. }
  332. contact = contact.next;
  333. }
  334. }
  335. }
  336. }
  337. }
  338. }