module BABYLON { //If this file is included in the main thread, this will be initialized. export var WorkerIncluded: boolean = true; 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) { //TODO CollisionsEpsilon should be defined here and not in the engine. var closeDistance = /*BABYLON.Engine.CollisionsEpsilon * 10.0*/ 0.01; //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) { 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; var subMeshes; // 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 = mesh.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 < len; index++) { var subMesh = subMeshes[index]; // Bounding test if (len > 1 && !this.checkSubmeshCollision(subMesh)) continue; //Unneeded //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(); //The following two arrays should be initialized CORRECTLY to save some calculation time. 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)); } //} // Collide this.collider._collide(subMesh['_trianglePlanes'] = [], subMesh['_lastColliderWorldVertices'], meshGeometry.indices, subMesh.indexStart, subMesh.indexStart + subMesh.indexCount, subMesh.verticesStart, subMesh.hasMaterial); } //TODO - this! :-) private checkSubmeshCollision(subMesh: SerializedSubMesh) : boolean { return this.collider._canDoCollision(BABYLON.Vector3.FromArray(subMesh.sphereCenter), subMesh.sphereRadius, BABYLON.Vector3.FromArray(subMesh.boxMinimum), BABYLON.Vector3.FromArray(subMesh.boxMaximum)); } } 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 } 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: collider.collidedMesh, collisionId: payload.collisionId, newPosition: finalPosition.asArray() } var reply: WorkerReply = { error: WorkerReplyType.SUCCESS, taskType: WorkerTaskType.COLLIDE, payload: replyPayload } postMessage(reply, undefined); } } //TypeScript doesn't know WorkerGlobalScope declare class WorkerGlobalScope { } //check if we are in a web worker, as this code should NOT run on the main UI thread try { if (self && self instanceof WorkerGlobalScope) { //Window hack to allow including babylonjs native code. the is for typescript. window = {}; //scripts were not included, standalone worker if (!BABYLON.Collider) { importScripts("./babylon.collisionCoordinator.js"); importScripts("./babylon.collider.js"); importScripts("../Math/babylon.math.js"); } var collisionDetector: ICollisionDetector = new CollisionDetectorTransferable(); var onNewMessage = function (event: MessageEvent) { var message = event.data; switch (message.taskType) { case WorkerTaskType.INIT: collisionDetector.onInit( message.payload); break; case WorkerTaskType.COLLIDE: collisionDetector.onCollision( message.payload); break; case WorkerTaskType.UPDATE: collisionDetector.onUpdate( message.payload); break; } } self.onmessage = onNewMessage; } } catch (e) { console.log("single worker init"); } }