UNPKG

cyclone-physics

Version:

Pure Javascript physics engine based on http://procyclone.com/

172 lines (168 loc) 8.42 kB
elation.require(['physics.processors'], function() { elation.extend("physics.processor.worker", function(parent, args={}) { elation.physics.processor.base.call(this, parent); this.worker = new elation.worker.thread('physics.worker', 'physicsworker'); console.log('make new worker', this.worker); elation.events.add(this.worker, 'message', ev => this.handleMessage(ev)); let subprocessor = args.subprocessor || 'cpu', subprocessorargs = args.processorargs || {}; this.worker.postMessage({type: 'system_init', processortype: subprocessor, args: subprocessorargs}); this.emptyresponse = []; this.objects = {}; this.update = function(objects, t) { if (t == 0) return; // paused, do nothing let changes = this.summarizeChanges(objects); this.worker.postMessage({type: 'object_changes', changes: changes}); return this.emptyresponse; } this.iterate = function(objects, t) { } this.summarizeChanges = function(objects, summary) { // TODO - this could be made more efficient by only summarizing RigidBody objects that changed in response to something in this thread (eg, scripts) // We should also be summarizing to an ArrayBuffer, and swapping buffers back and forth, rather than allocating a new array each frame if (!summary) summary = []; for (let i = 0; i < objects.length; i++) { let object = objects[i]; //object.updateState(); if (object.hasChanged()) { // FIXME - for now, we just return a full serialization of all changed objects, with a full list of the properties we care about for physics let objdata = this.serializeObject(object); if (!this.objects[objdata.id]) { // || this.objects[objdata.id] !== object) { this.objects[objdata.id] = object; elation.events.add(object, 'add', ev => this.sendAdd(ev.data)); elation.events.add(object, 'remove', ev => this.sendRemove(ev.target, ev.data)); if (object.collider) { objdata.collider = object.collider.toJSON(); /* setTimeout(() => { this.sendColliderUpdate(object); }, 1000); */ } elation.events.add(object, 'collider_change', ev => this.sendColliderUpdate(object)); if (object.forces.length > 0) { objdata.forces = []; for (let i = 0; i < object.forces.length; i++) { let f = object.forces[i]; objdata.forces.push(f.toJSON()); elation.events.add(f, 'physics_force_update', ev => this.worker.postMessage({type: 'force_update', objectid: objdata.id, forcenum: i, force: f.toJSON()})); } } } summary.push(objdata); //object.resetChangedFlag(); } if (object.children && object.children.length > 0) { this.summarizeChanges(object.children, summary); } } return summary; } this.handleMessage = function(ev) { // TODO - see physx-worker.js for all message types let msg = ev.data; if (msg.type == 'object_updates') { this.handleObjectUpdates(msg.updates); } else if (msg.type == 'contact_begin') { let body1 = this.objects[msg.thing1], thing1 = body1.object, body2 = this.objects[msg.thing2], thing2 = body2.object; // FIXME - PhysX wasm port doesn't return contact information, need to implement that in the web bindings // For now we just treat it like a sphere so we can provide a best guess // https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/AdvancedCollisionDetection.html#extracting-contact-information // https://github.com/prestomation/PhysX/blob/emscripten/physx/source/physxwebbindings/src/PxWebBindings.cpp#L43-L59 // FIXME^2 - we should take into account the radii of each object's bounding sphere when faking our collision point let point = V().copy(msg.point), normal = V().copy(msg.normal), penetration = msg.penetration || 0; if (!point) point = V().copy(body1.positionWorld).add(body2.positionWorld).multiplyScalar(.5); if (!normal) normal = V().copy(body1.positionWorld).sub(body2.positionWorld).normalize(); let contact = new elation.physics.contact({ normal: normal, point: point, penetration: penetration, bodies: [body1, body2] }); elation.events.fire({type: 'physics_collide', element: body1, data: contact}); elation.events.fire({type: 'physics_collide', element: body2, data: contact}); /* let contact2 = new elation.physics.contact({ normal: normal.clone().multiplyScalar(-1), point: point, penetration: penetration, bodies: [body2, body1] }); elation.events.fire({type: 'physics_collide', element: body2, data: contact2}); */ } } this.handleObjectUpdates = function(updates) { for (let k in updates) { let update = updates[k], obj = this.objects[k]; if (obj && /*obj.hasChanged() &&*/ obj.parent) { //console.log('update', update.position, update.velocity); if (update.position && !obj.position.changed) obj.position.copy(update.position); if (update.orientation && !obj.orientation.changed) obj.orientation.copy(update.orientation); if (update.velocity && !obj.velocity.changed) obj.velocity.copy(update.velocity); if (update.angular && !obj.angular.changed) obj.angular.copy(update.angular); obj.updateState(); obj.position.reset(); obj.orientation.reset(); obj.positionWorld.reset(); obj.orientationWorld.reset(); obj.velocity.reset(); obj.angular.reset(); elation.events.fire({element: obj, type: "physics_update"}); } } } this.sendColliderUpdate = function(object) { console.log('send a collider change event', object.id, object.collider, JSON.stringify(object.collider), object); this.worker.postMessage({type: 'collider_change', objectid: object.id, collider: JSON.parse(JSON.stringify(object.collider))}); } this.sendAdd = function(obj) { // TODO - should notify the worker of newly-added objects instead of detecting it from the object_changes messages //console.log('send the add to the worker (do nothing)', ev); //this.worker.postMessage({type: 'collider_change', objectid: object.id, collider: object.collider.toJSON()}); console.log('hey, got an add event, what to do?', obj); let objdata = this.serializeObject(obj); this.worker.postMessage({type: 'object_add', object: objdata}); } this.sendRemove = function(object, parent) { //console.log('send the remove to the worker', object, parent, object.id, parent.id); this.worker.postMessage({type: 'object_remove', parentid: parent.id, objectid: object.id}); } this.serializeObject = function(object) { let objdata = { id: object.id || object.object.objects['3d'].uuid, position: object.position.toJSON(), orientation: object.orientation.toJSON(), //{ x: orientation.x, y: orientation.y, z: orientation.z, w: orientation.w }, // Three.js's quaternion uses _-prefixed names when serialized for some reason scale: object.scale.toJSON ? object.scale.toJSON() : object.scale, velocity: object.velocity.toJSON(), acceleration: object.acceleration.toJSON(), angular: object.angular.toJSON(), mass: object.mass, gravity: object.gravity, restitution: object.restitution, material: { dynamicfriction: object.material.dynamicfriction, staticfriction: object.material.staticfriction, }, //forces: object.force_accumulator, linearDamping: object.linearDamping, angularDamping: object.angularDamping, //sleeping: true, }; if (object.parent) { //console.log('object in main thread had parent', object.id, object.parent.id, object, object.parent); objdata.parentid = object.parent.id; } if (object.collider) { objdata.collider = object.collider.toJSON(); } return objdata; } }, false, elation.physics.processor.base); });