Browse Source

First implementation of the worker

Worker is located in a different namespace.
It is initialized using a blob initialization (with a predefined URL)
for it to be integrated in the main js file of babylon.
Raanan Weber 10 years ago
parent
commit
5149974148

+ 4 - 2
Babylon/Collisions/babylon.collisionCoordinator.js

@@ -95,11 +95,13 @@ var BABYLON;
         CollisionCoordinatorWorker.prototype.init = function (scene) {
             this._scene = scene;
             this._scene.registerAfterRender(this._afterRender);
-            //TODO init worker
+            var blobURL = URL.createObjectURL(new Blob(['(', BABYLON.CollisionWorker.toString(), ')()'], { type: 'application/javascript' }));
+            this._worker = new Worker(blobURL);
+            URL.revokeObjectURL(blobURL);
         };
         CollisionCoordinatorWorker.prototype.destroy = function () {
             this._scene.unregisterAfterRender(this._afterRender);
-            //TODO destroy worker
+            this._worker.terminate();
         };
         CollisionCoordinatorWorker.prototype.onMeshAdded = function (mesh) {
             mesh.registerAfterWorldMatrixUpdate(this.onMeshUpdated);

+ 4 - 3
Babylon/Collisions/babylon.collisionCoordinator.ts

@@ -182,13 +182,14 @@ module BABYLON {
         public init(scene: Scene): void {
             this._scene = scene;
             this._scene.registerAfterRender(this._afterRender);
-            //TODO init worker
-
+            var blobURL = URL.createObjectURL(new Blob(['(', BABYLON.CollisionWorker.toString(), ')()'], { type: 'application/javascript' }));
+            this._worker = new Worker(blobURL);
+            URL.revokeObjectURL(blobURL);
         }
 
         public destroy(): void {
             this._scene.unregisterAfterRender(this._afterRender);
-            //TODO destroy worker
+            this._worker.terminate();
         }
 
         public onMeshAdded(mesh: AbstractMesh) {

+ 215 - 0
Babylon/Collisions/babylon.collisionWorker.js

@@ -0,0 +1,215 @@
+var BABYLON;
+(function (BABYLON) {
+    var CollisionWorker;
+    (function (CollisionWorker) {
+        var CollisionCache = (function () {
+            function CollisionCache() {
+                this._meshes = {};
+                this._geometries = {};
+            }
+            CollisionCache.prototype.getMeshes = function () {
+                return this._meshes;
+            };
+            CollisionCache.prototype.getGeometries = function () {
+                return this._geometries;
+            };
+            CollisionCache.prototype.getMesh = function (id) {
+                return this._meshes[id];
+            };
+            CollisionCache.prototype.addMesh = function (mesh) {
+                this._meshes[mesh.uniqueId] = mesh;
+            };
+            CollisionCache.prototype.getGeometry = function (id) {
+                return this._geometries[id];
+            };
+            CollisionCache.prototype.addGeometry = function (geometry) {
+                this._geometries[geometry.id] = geometry;
+            };
+            return CollisionCache;
+        })();
+        CollisionWorker.CollisionCache = CollisionCache;
+        var CollideWorker = (function () {
+            function CollideWorker(collider, _collisionCache, finalPosition) {
+                this.collider = collider;
+                this._collisionCache = _collisionCache;
+                this.finalPosition = finalPosition;
+                this.collisionsScalingMatrix = BABYLON.Matrix.Zero();
+                this.collisionTranformationMatrix = BABYLON.Matrix.Zero();
+            }
+            CollideWorker.prototype.collideWithWorld = function (position, velocity, maximumRetry, excludedMeshUniqueId) {
+                var closeDistance = BABYLON.Engine.CollisionsEpsilon * 10.0;
+                //is initializing here correct? A quick look - looks like it is fine.
+                if (this.collider.retry >= maximumRetry) {
+                    this.finalPosition.copyFrom(position);
+                    return;
+                }
+                this.collider._initialize(position, velocity, closeDistance);
+                // Check all meshes
+                var meshes = this._collisionCache.getMeshes();
+                for (var uniqueId in meshes) {
+                    if (meshes.hasOwnProperty(uniqueId) && parseInt(uniqueId) != excludedMeshUniqueId) {
+                        var mesh = meshes[uniqueId];
+                        if (mesh.checkCollisions)
+                            this.checkCollision(mesh);
+                    }
+                }
+                if (!this.collider.collisionFound) {
+                    position.addToRef(velocity, this.finalPosition);
+                    return;
+                }
+                if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
+                    this.collider._getResponse(position, velocity);
+                }
+                if (velocity.length() <= closeDistance) {
+                    //console.log("webworker collision with " + this.collider.collidedMesh);
+                    this.finalPosition.copyFrom(position);
+                    return;
+                }
+                this.collider.retry++;
+                this.collideWithWorld(position, velocity, maximumRetry, excludedMeshUniqueId);
+            };
+            CollideWorker.prototype.checkCollision = function (mesh) {
+                if (!this.collider._canDoCollision(BABYLON.Vector3.FromArray(mesh.sphereCenter), mesh.sphereRadius, BABYLON.Vector3.FromArray(mesh.boxMinimum), BABYLON.Vector3.FromArray(mesh.boxMaximum))) {
+                    return;
+                }
+                ;
+                // Transformation matrix
+                BABYLON.Matrix.ScalingToRef(1.0 / this.collider.radius.x, 1.0 / this.collider.radius.y, 1.0 / this.collider.radius.z, this.collisionsScalingMatrix);
+                var worldFromCache = BABYLON.Matrix.FromArray(mesh.worldMatrixFromCache);
+                worldFromCache.multiplyToRef(this.collisionsScalingMatrix, this.collisionTranformationMatrix);
+                this.processCollisionsForSubMeshes(this.collisionTranformationMatrix, mesh);
+                //return colTransMat;
+            };
+            CollideWorker.prototype.processCollisionsForSubMeshes = function (transformMatrix, mesh) {
+                var len;
+                // No Octrees for now
+                //if (this._submeshesOctree && this.useOctreeForCollisions) {
+                //    var radius = collider.velocityWorldLength + Math.max(collider.radius.x, collider.radius.y, collider.radius.z);
+                //    var intersections = this._submeshesOctree.intersects(collider.basePointWorld, radius);
+                //    len = intersections.length;
+                //    subMeshes = intersections.data;
+                //} else {
+                //    subMeshes = this.subMeshes;
+                //    len = subMeshes.length;
+                //}
+                if (!mesh.geometryId) {
+                    console.log("no mesh geometry id");
+                    return;
+                }
+                var meshGeometry = this._collisionCache.getGeometry(mesh.geometryId);
+                if (!meshGeometry) {
+                    console.log("couldn't find geometry", mesh.geometryId);
+                    return;
+                }
+                for (var index = 0; index < mesh.subMeshes.length; index++) {
+                    var subMesh = mesh.subMeshes[index];
+                    // Bounding test
+                    if (len > 1 && !this.checkSubmeshCollision(subMesh))
+                        continue;
+                    subMesh['getMesh'] = function () {
+                        return mesh.uniqueId;
+                    };
+                    this.collideForSubMesh(subMesh, transformMatrix, meshGeometry);
+                }
+            };
+            CollideWorker.prototype.collideForSubMesh = function (subMesh, transformMatrix, meshGeometry) {
+                var positionsArray = [];
+                for (var i = 0; i < meshGeometry.positions.length; i = i + 3) {
+                    var p = BABYLON.Vector3.FromArray([meshGeometry.positions[i], meshGeometry.positions[i + 1], meshGeometry.positions[i + 2]]);
+                    positionsArray.push(p);
+                }
+                subMesh['_lastColliderTransformMatrix'] = transformMatrix.clone();
+                subMesh['_lastColliderWorldVertices'] = [];
+                subMesh['_trianglePlanes'] = [];
+                var start = subMesh.verticesStart;
+                var end = (subMesh.verticesStart + subMesh.verticesCount);
+                for (var i = start; i < end; i++) {
+                    subMesh['_lastColliderWorldVertices'].push(BABYLON.Vector3.TransformCoordinates(positionsArray[i], transformMatrix));
+                }
+                subMesh['getMaterial'] = function () {
+                    return true;
+                };
+                //}
+                // Collide
+                this.collider._collide(subMesh, subMesh['_lastColliderWorldVertices'], meshGeometry.indices, subMesh.indexStart, subMesh.indexStart + subMesh.indexCount, subMesh.verticesStart);
+            };
+            //TODO - this! :-)
+            CollideWorker.prototype.checkSubmeshCollision = function (subMesh) {
+                return true;
+            };
+            return CollideWorker;
+        })();
+        CollisionWorker.CollideWorker = CollideWorker;
+        var CollisionDetectorTransferable = (function () {
+            function CollisionDetectorTransferable() {
+            }
+            CollisionDetectorTransferable.prototype.onInit = function (payload) {
+                this._collisionCache = new CollisionCache();
+                var reply = {
+                    error: 0 /* SUCCESS */,
+                    taskType: 0 /* INIT */
+                };
+                postMessage(reply, undefined);
+            };
+            CollisionDetectorTransferable.prototype.onUpdate = function (payload) {
+                for (var id in payload.updatedGeometries) {
+                    if (payload.updatedGeometries.hasOwnProperty(id)) {
+                        this._collisionCache.addGeometry(payload.updatedGeometries[id]);
+                    }
+                }
+                for (var uniqueId in payload.updatedMeshes) {
+                    if (payload.updatedMeshes.hasOwnProperty(uniqueId)) {
+                        this._collisionCache.addMesh(payload.updatedMeshes[uniqueId]);
+                    }
+                }
+                var replay = {
+                    error: 0 /* SUCCESS */,
+                    taskType: 1 /* UPDATE */
+                };
+                console.log("updated");
+                postMessage(replay, undefined);
+            };
+            CollisionDetectorTransferable.prototype.onCollision = function (payload) {
+                var finalPosition = BABYLON.Vector3.Zero();
+                //create a new collider
+                var collider = new BABYLON.Collider();
+                collider.radius = BABYLON.Vector3.FromArray(payload.collider.radius);
+                var colliderWorker = new CollideWorker(collider, this._collisionCache, finalPosition);
+                colliderWorker.collideWithWorld(BABYLON.Vector3.FromArray(payload.collider.position), BABYLON.Vector3.FromArray(payload.collider.velocity), payload.maximumRetry, payload.excludedMeshUniqueId);
+                var replyPayload = {
+                    collidedMeshUniqueId: collider.collidedMesh,
+                    collisionId: payload.collisionId,
+                    newPosition: finalPosition.asArray()
+                };
+                var reply = {
+                    error: 0 /* SUCCESS */,
+                    taskType: 2 /* COLLIDE */,
+                    payload: replyPayload
+                };
+                postMessage(reply, undefined);
+            };
+            return CollisionDetectorTransferable;
+        })();
+        CollisionWorker.CollisionDetectorTransferable = CollisionDetectorTransferable;
+        //check if we are in a web worker, as this code should NOT run on the main UI thread
+        if (self && !self.document) {
+            var collisionDetector = new CollisionDetectorTransferable();
+            var onNewMessage = function (event) {
+                var message = event.data;
+                switch (message.taskType) {
+                    case 0 /* INIT */:
+                        collisionDetector.onInit(message.payload);
+                        break;
+                    case 2 /* COLLIDE */:
+                        collisionDetector.onCollision(message.payload);
+                        break;
+                    case 1 /* UPDATE */:
+                        collisionDetector.onUpdate(message.payload);
+                        break;
+                }
+            };
+            self.onmessage = onNewMessage;
+        }
+    })(CollisionWorker = BABYLON.CollisionWorker || (BABYLON.CollisionWorker = {}));
+})(BABYLON || (BABYLON = {}));
+//# sourceMappingURL=babylon.collisionWorker.js.map

+ 252 - 0
Babylon/Collisions/babylon.collisionWorker.ts

@@ -0,0 +1,252 @@
+module BABYLON.CollisionWorker {
+
+    export class CollisionCache {
+        private _meshes: { [n: number]: SerializedMesh; } = {};
+        private _geometries: { [s: number]: SerializedGeometry; } = {};
+
+        public getMeshes(): { [n: number]: SerializedMesh; } {
+            return this._meshes;
+        }
+
+        public getGeometries(): { [s: number]: SerializedGeometry; } {
+            return this._geometries;
+        }
+
+        public getMesh(id: any): SerializedMesh {
+            return this._meshes[id];
+        }
+
+        public addMesh(mesh: SerializedMesh) {
+            this._meshes[mesh.uniqueId] = mesh;
+        }
+
+        public getGeometry(id: string): SerializedGeometry {
+            return this._geometries[id];
+        }
+
+        public addGeometry(geometry: SerializedGeometry) {
+            this._geometries[geometry.id] = geometry;
+        }
+    }
+
+    export class CollideWorker {
+
+        private collisionsScalingMatrix = BABYLON.Matrix.Zero();
+        private collisionTranformationMatrix = BABYLON.Matrix.Zero();
+
+        constructor(public collider: BABYLON.Collider, private _collisionCache: CollisionCache, private finalPosition: BABYLON.Vector3) {
+
+        }
+
+        public collideWithWorld(position: BABYLON.Vector3, velocity: BABYLON.Vector3, maximumRetry: number, excludedMeshUniqueId?: number) {
+
+            var closeDistance = BABYLON.Engine.CollisionsEpsilon * 10.0;
+            //is initializing here correct? A quick look - looks like it is fine.
+            
+            if (this.collider.retry >= maximumRetry) {
+                this.finalPosition.copyFrom(position);
+                return;
+            }
+
+            this.collider._initialize(position, velocity, closeDistance);
+        
+
+            // Check all meshes
+            var meshes = this._collisionCache.getMeshes();
+            for (var uniqueId in meshes) {
+                if (meshes.hasOwnProperty(uniqueId) && parseInt(uniqueId) != excludedMeshUniqueId) {
+                    var mesh: SerializedMesh = meshes[uniqueId];
+                    if (mesh.checkCollisions)
+                        this.checkCollision(mesh);
+                }
+            }
+
+            if (!this.collider.collisionFound) {
+                position.addToRef(velocity, this.finalPosition);
+                return;
+            }
+
+            if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
+                this.collider._getResponse(position, velocity);
+            }
+
+            if (velocity.length() <= closeDistance) {
+                //console.log("webworker collision with " + this.collider.collidedMesh);
+                this.finalPosition.copyFrom(position);
+                return;
+            }
+
+            this.collider.retry++;
+            this.collideWithWorld(position, velocity, maximumRetry, excludedMeshUniqueId);
+        }
+
+        private checkCollision(mesh: SerializedMesh) {
+
+            if (!this.collider._canDoCollision(BABYLON.Vector3.FromArray(mesh.sphereCenter), mesh.sphereRadius, BABYLON.Vector3.FromArray(mesh.boxMinimum), BABYLON.Vector3.FromArray(mesh.boxMaximum))) {
+                return;
+            };
+
+            // Transformation matrix
+            BABYLON.Matrix.ScalingToRef(1.0 / this.collider.radius.x, 1.0 / this.collider.radius.y, 1.0 / this.collider.radius.z, this.collisionsScalingMatrix);
+            var worldFromCache = BABYLON.Matrix.FromArray(mesh.worldMatrixFromCache);
+            worldFromCache.multiplyToRef(this.collisionsScalingMatrix, this.collisionTranformationMatrix);
+
+            this.processCollisionsForSubMeshes(this.collisionTranformationMatrix, mesh);
+            //return colTransMat;
+        }
+
+        private processCollisionsForSubMeshes(transformMatrix: BABYLON.Matrix, mesh: SerializedMesh): void {
+            var len: number;
+
+            // No Octrees for now
+            //if (this._submeshesOctree && this.useOctreeForCollisions) {
+            //    var radius = collider.velocityWorldLength + Math.max(collider.radius.x, collider.radius.y, collider.radius.z);
+            //    var intersections = this._submeshesOctree.intersects(collider.basePointWorld, radius);
+
+            //    len = intersections.length;
+            //    subMeshes = intersections.data;
+            //} else {
+            //    subMeshes = this.subMeshes;
+            //    len = subMeshes.length;
+            //}
+
+            if (!mesh.geometryId) {
+                console.log("no mesh geometry id");
+                return;
+            }
+
+            var meshGeometry = this._collisionCache.getGeometry(mesh.geometryId);
+            if (!meshGeometry) {
+                console.log("couldn't find geometry", mesh.geometryId);
+                return;
+            }
+
+            for (var index = 0; index < mesh.subMeshes.length; index++) {
+                var subMesh = mesh.subMeshes[index];
+
+                // Bounding test
+                if (len > 1 && !this.checkSubmeshCollision(subMesh))
+                    continue;
+
+                subMesh['getMesh'] = function () {
+                    return mesh.uniqueId;
+                }
+                this.collideForSubMesh(subMesh, transformMatrix, meshGeometry);
+            }
+        }
+
+        private collideForSubMesh(subMesh: SerializedSubMesh, transformMatrix: BABYLON.Matrix, meshGeometry: SerializedGeometry): void {
+            var positionsArray = [];
+            for (var i = 0; i < meshGeometry.positions.length; i = i + 3) {
+                var p = BABYLON.Vector3.FromArray([meshGeometry.positions[i], meshGeometry.positions[i + 1], meshGeometry.positions[i + 2]]);
+                positionsArray.push(p);
+            }
+            subMesh['_lastColliderTransformMatrix'] = transformMatrix.clone();
+            subMesh['_lastColliderWorldVertices'] = [];
+            subMesh['_trianglePlanes'] = [];
+            var start = subMesh.verticesStart;
+            var end = (subMesh.verticesStart + subMesh.verticesCount);
+            for (var i = start; i < end; i++) {
+                subMesh['_lastColliderWorldVertices'].push(BABYLON.Vector3.TransformCoordinates(positionsArray[i], transformMatrix));
+            }
+            subMesh['getMaterial'] = function () {
+                return true;
+            }
+        
+            //}
+            // Collide
+            this.collider._collide(subMesh, subMesh['_lastColliderWorldVertices'], <any> meshGeometry.indices, subMesh.indexStart, subMesh.indexStart + subMesh.indexCount, subMesh.verticesStart);
+        }
+
+        //TODO - this! :-)
+        private checkSubmeshCollision(subMesh: SerializedSubMesh) {
+            return true;
+        }
+
+
+    }
+
+    export interface ICollisionDetector {
+        onInit(payload: InitPayload): void;
+        onUpdate(payload: UpdatePayload): void;
+        onCollision(payload: CollidePayload): void;
+    }
+
+    export class CollisionDetectorTransferable implements ICollisionDetector {
+        private _collisionCache: CollisionCache;
+
+        public onInit(payload: InitPayload) {
+            this._collisionCache = new CollisionCache();
+            var reply: WorkerReply = {
+                error: WorkerReplyType.SUCCESS,
+                taskType: WorkerTaskType.INIT
+            }
+            postMessage(reply, undefined);
+        }
+
+        public onUpdate(payload: UpdatePayload) {
+            for (var id in payload.updatedGeometries) {
+                if (payload.updatedGeometries.hasOwnProperty(id)) {
+                    this._collisionCache.addGeometry(payload.updatedGeometries[id]);
+                }
+            }
+            for (var uniqueId in payload.updatedMeshes) {
+                if (payload.updatedMeshes.hasOwnProperty(uniqueId)) {
+                    this._collisionCache.addMesh(payload.updatedMeshes[uniqueId]);
+                }
+            }
+
+            var replay: WorkerReply = {
+                error: WorkerReplyType.SUCCESS,
+                taskType: WorkerTaskType.UPDATE
+            }
+            console.log("updated");
+            postMessage(replay, undefined);
+        }
+
+        public onCollision(payload: CollidePayload) {
+            var finalPosition = BABYLON.Vector3.Zero();
+            //create a new collider
+            var collider = new BABYLON.Collider();
+            collider.radius = BABYLON.Vector3.FromArray(payload.collider.radius);
+
+            var colliderWorker = new CollideWorker(collider, this._collisionCache, finalPosition);
+            colliderWorker.collideWithWorld(BABYLON.Vector3.FromArray(payload.collider.position), BABYLON.Vector3.FromArray(payload.collider.velocity), payload.maximumRetry, payload.excludedMeshUniqueId);
+            var replyPayload: CollisionReplyPayload = {
+                collidedMeshUniqueId: <any> collider.collidedMesh,
+                collisionId: payload.collisionId,
+                newPosition: finalPosition.asArray()
+            }
+            var reply: WorkerReply = {
+                error: WorkerReplyType.SUCCESS,
+                taskType: WorkerTaskType.COLLIDE,
+                payload: replyPayload
+            }
+            postMessage(reply, undefined);
+        }
+    }
+
+    //check if we are in a web worker, as this code should NOT run on the main UI thread
+    if (self && !self.document) {
+
+        var collisionDetector: ICollisionDetector = new CollisionDetectorTransferable();
+
+        var onNewMessage = function (event: MessageEvent) {
+            var message = <BabylonMessage> event.data;
+            switch (message.taskType) {
+                case WorkerTaskType.INIT:
+                    collisionDetector.onInit(<InitPayload> message.payload);
+                    break;
+                case WorkerTaskType.COLLIDE:
+                    collisionDetector.onCollision(<CollidePayload> message.payload);
+                    break;
+                case WorkerTaskType.UPDATE:
+                    collisionDetector.onUpdate(<UpdatePayload> message.payload);
+                    break;
+            }
+        }
+
+        self.onmessage = onNewMessage;
+    }
+
+}

+ 1 - 0
Tools/BuildOurOwnBabylonJS/BuildOurOwnBabylonJS/babylonJS.xml

@@ -107,6 +107,7 @@
   <script src="Babylon/Cameras/babylon.camera.js"></script>
   <script src="Babylon/Collisions/babylon.collider.js"></script>
   <script src="Babylon/Collisions/babylon.collisionCoordinator.js"></script>
+  <script src="Babylon/Collisions/babylon.collisionWorker.ts"></script>
   <script src="Babylon/Lights//Shadows/babylon.shadowGenerator.js"></script>
   <script src="Babylon/Lights/babylon.directionalLight.js"></script>
   <script src="Babylon/Lights/babylon.hemisphericLight.js"></script>

+ 1 - 0
Tools/Gulp/gulpfile.js

@@ -79,6 +79,7 @@ gulp.task('scripts', ['shaders'] ,function() {
       '../../Babylon/Lights/Shadows/babylon.shadowGenerator.js',
       '../../Babylon/Collisions/babylon.collider.js',
 	  '../../Babylon/Collisions/babylon.collisionCoordinator.js',
+	  '../../Babylon/Collisions/babylon.collisionWorker.js',
       '../../Babylon/Cameras/babylon.camera.js',
       '../../Babylon/Cameras/babylon.targetCamera.js',
       '../../Babylon/Cameras/babylon.freeCamera.js',