babylon.collisionCoordinator.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. module BABYLON {
  2. export interface ICollisionCoordinator {
  3. getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: BABYLON.Vector3, collidedMesh?: BABYLON.AbstractMesh) => void, collisionIndex: number): void;
  4. init(scene: Scene): void;
  5. destroy(): void;
  6. //Update meshes and geometries
  7. onMeshAdded(mesh: AbstractMesh);
  8. onMeshUpdated(mesh: AbstractMesh);
  9. onMeshRemoved(mesh: AbstractMesh);
  10. onGeometryAdded(geometry: Geometry);
  11. onGeometryUpdated(geometry: Geometry);
  12. onGeometryDeleted(geometry: Geometry);
  13. }
  14. export interface SerializedMesh {
  15. id: string;
  16. name: string;
  17. uniqueId: number;
  18. geometryId: string;
  19. sphereCenter: Array<number>;
  20. sphereRadius: number;
  21. boxMinimum: Array<number>;
  22. boxMaximum: Array<number>;
  23. worldMatrixFromCache: any;
  24. subMeshes: Array<SerializedSubMesh>;
  25. checkCollisions: boolean;
  26. }
  27. export interface SerializedSubMesh {
  28. position: number;
  29. verticesStart: number;
  30. verticesCount: number;
  31. indexStart: number;
  32. indexCount: number;
  33. }
  34. export interface SerializedGeometry {
  35. id: string;
  36. positions: Float32Array;
  37. indices: Int32Array;
  38. normals: Float32Array;
  39. //uvs?: Float32Array;
  40. }
  41. export interface BabylonMessage {
  42. taskType: WorkerTaskType;
  43. payload: InitPayload|CollidePayload|UpdatePayload /*any for TS under 1.4*/;
  44. }
  45. export interface SerializedColliderToWorker {
  46. position: Array<number>;
  47. velocity: Array<number>;
  48. radius: Array<number>;
  49. }
  50. export enum WorkerTaskType {
  51. INIT,
  52. UPDATE,
  53. COLLIDE
  54. }
  55. export interface WorkerReply {
  56. error: WorkerReplyType;
  57. taskType: WorkerTaskType;
  58. payload?: any;
  59. }
  60. export interface CollisionReplyPayload {
  61. newPosition: Array<number>;
  62. collisionId: number;
  63. collidedMeshUniqueId: number;
  64. }
  65. export interface InitPayload {
  66. }
  67. export interface CollidePayload {
  68. collisionId: number;
  69. collider: SerializedColliderToWorker;
  70. maximumRetry: number;
  71. excludedMeshUniqueId?: number;
  72. }
  73. export interface UpdatePayload {
  74. updatedMeshes: { [n: number]: SerializedMesh; };
  75. updatedGeometries: { [s: string]: SerializedGeometry; };
  76. removedMeshes: Array<number>;
  77. removedGeometries: Array<string>;
  78. }
  79. export enum WorkerReplyType {
  80. SUCCESS,
  81. UNKNOWN_ERROR
  82. }
  83. export class CollisionCoordinatorWorker implements ICollisionCoordinator {
  84. private _scene: Scene;
  85. private _scaledPosition = Vector3.Zero();
  86. private _scaledVelocity = Vector3.Zero();
  87. private _collisionsCallbackArray: Array<(collisionIndex: number, newPosition: BABYLON.Vector3, collidedMesh?: BABYLON.AbstractMesh) => void>;
  88. private _init: boolean;
  89. private _runningUpdated: number;
  90. private _runningCollisionTask: boolean;
  91. private _worker: Worker;
  92. private _addUpdateMeshesList: { [n: number]: SerializedMesh; }
  93. private _addUpdateGeometriesList: { [s: string]: SerializedGeometry; };
  94. private _toRemoveMeshesArray: Array<number>;
  95. private _toRemoveGeometryArray: Array<string>;
  96. constructor() {
  97. this._collisionsCallbackArray = [];
  98. this._init = false;
  99. this._runningUpdated = 0;
  100. this._runningCollisionTask = false;
  101. this._addUpdateMeshesList = {};
  102. this._addUpdateGeometriesList = {};
  103. this._toRemoveGeometryArray = [];
  104. this._toRemoveMeshesArray = [];
  105. }
  106. public static SerializeMesh = function (mesh: BABYLON.AbstractMesh): SerializedMesh {
  107. var submeshes = [];
  108. if (mesh.subMeshes) {
  109. submeshes = mesh.subMeshes.map(function (sm, idx) {
  110. return {
  111. position: idx,
  112. verticesStart: sm.verticesStart,
  113. verticesCount: sm.verticesCount,
  114. indexStart: sm.indexStart,
  115. indexCount: sm.indexCount
  116. }
  117. });
  118. }
  119. var geometryId = (<BABYLON.Mesh>mesh).geometry ? (<BABYLON.Mesh>mesh).geometry.id : null;
  120. return {
  121. uniqueId: mesh.uniqueId,
  122. id: mesh.id,
  123. name: mesh.name,
  124. geometryId: geometryId,
  125. sphereCenter: mesh.getBoundingInfo().boundingSphere.centerWorld.asArray(),
  126. sphereRadius: mesh.getBoundingInfo().boundingSphere.radiusWorld,
  127. boxMinimum: mesh.getBoundingInfo().boundingBox.minimumWorld.asArray(),
  128. boxMaximum: mesh.getBoundingInfo().boundingBox.maximumWorld.asArray(),
  129. worldMatrixFromCache: mesh.worldMatrixFromCache.asArray(),
  130. subMeshes: submeshes,
  131. checkCollisions: mesh.checkCollisions
  132. }
  133. }
  134. public static SerializeGeometry = function (geometry: BABYLON.Geometry): SerializedGeometry {
  135. return {
  136. id: geometry.id,
  137. positions: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.PositionKind) || []),
  138. normals: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.NormalKind) || []),
  139. indices: new Int32Array(geometry.getIndices() || []),
  140. //uvs: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.UVKind) || [])
  141. }
  142. }
  143. public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: BABYLON.Vector3, collidedMesh?: BABYLON.AbstractMesh) => void, collisionIndex: number): void {
  144. if (this._collisionsCallbackArray[collisionIndex]) return;
  145. position.divideToRef(collider.radius, this._scaledPosition);
  146. velocity.divideToRef(collider.radius, this._scaledVelocity);
  147. this._collisionsCallbackArray[collisionIndex] = onNewPosition;
  148. }
  149. public init(scene: Scene): void {
  150. this._scene = scene;
  151. this._scene.registerAfterRender(this._afterRender);
  152. var blobURL = URL.createObjectURL(new Blob(['(', BABYLON.CollisionWorker.toString(), ')()'], { type: 'application/javascript' }));
  153. this._worker = new Worker(blobURL);
  154. URL.revokeObjectURL(blobURL);
  155. }
  156. public destroy(): void {
  157. this._scene.unregisterAfterRender(this._afterRender);
  158. this._worker.terminate();
  159. }
  160. public onMeshAdded(mesh: AbstractMesh) {
  161. mesh.registerAfterWorldMatrixUpdate(this.onMeshUpdated);
  162. this.onMeshUpdated(mesh);
  163. }
  164. public onMeshUpdated = (mesh: AbstractMesh) => {
  165. this._addUpdateMeshesList[mesh.uniqueId] = CollisionCoordinatorWorker.SerializeMesh(mesh);
  166. }
  167. public onMeshRemoved(mesh: AbstractMesh) {
  168. this._toRemoveMeshesArray.push(mesh.uniqueId);
  169. }
  170. public onGeometryAdded(geometry: Geometry) {
  171. //TODO this will break if the user uses his own function. This should be an array on callbacks!
  172. geometry.onGeometryUpdated = this.onGeometryUpdated;
  173. this.onGeometryUpdated(geometry);
  174. }
  175. public onGeometryUpdated = (geometry: Geometry) => {
  176. this._addUpdateGeometriesList[geometry.id] = CollisionCoordinatorWorker.SerializeGeometry(geometry);
  177. }
  178. public onGeometryDeleted(geometry: Geometry) {
  179. this._toRemoveGeometryArray.push(geometry.id);
  180. }
  181. private _afterRender = () => {
  182. var payload: UpdatePayload = {
  183. updatedMeshes: this._addUpdateMeshesList,
  184. updatedGeometries: this._addUpdateGeometriesList,
  185. removedGeometries: this._toRemoveGeometryArray,
  186. removedMeshes: this._toRemoveMeshesArray
  187. };
  188. var message: BabylonMessage = {
  189. payload: payload,
  190. taskType: WorkerTaskType.UPDATE
  191. }
  192. var serializable = [];
  193. for (var id in payload.updatedGeometries) {
  194. if (payload.updatedGeometries.hasOwnProperty(id)) {
  195. //prepare transferables
  196. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].indices.buffer);
  197. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].normals.buffer);
  198. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].positions.buffer);
  199. }
  200. }
  201. //this variable is here only in case the update takes longer than a frame!
  202. this._runningUpdated++;
  203. this._worker.postMessage(message, serializable);
  204. this._addUpdateMeshesList = {};
  205. this._addUpdateGeometriesList = {};
  206. this._toRemoveGeometryArray = [];
  207. this._toRemoveMeshesArray = [];
  208. }
  209. private _onMessageFromWorker = (e: MessageEvent) => {
  210. var returnData = <WorkerReply> e.data;
  211. if (returnData.error != WorkerReplyType.SUCCESS) {
  212. //TODO what errors can be returned from the worker?
  213. Tools.Warn("error returned from worker!");
  214. return;
  215. }
  216. switch (returnData.taskType) {
  217. case WorkerTaskType.INIT:
  218. //TODO is init required after worker is done initializing?
  219. this._init = true;
  220. break;
  221. case WorkerTaskType.UPDATE:
  222. this._runningUpdated--;
  223. break;
  224. case WorkerTaskType.COLLIDE:
  225. this._runningCollisionTask = false;
  226. var returnPayload: CollisionReplyPayload = returnData.payload;
  227. if (!this._collisionsCallbackArray[returnPayload.collisionId]) return;
  228. this._collisionsCallbackArray[returnPayload.collisionId](returnPayload.collisionId, Vector3.FromArray(returnPayload.newPosition), this._scene.getMeshByUniqueID(returnPayload.collidedMeshUniqueId));
  229. //cleanup
  230. this._collisionsCallbackArray[returnPayload.collisionId] = undefined;
  231. break;
  232. }
  233. }
  234. }
  235. export class CollisionCoordinatorLegacy implements ICollisionCoordinator {
  236. private _scene: Scene;
  237. private _scaledPosition = Vector3.Zero();
  238. private _scaledVelocity = Vector3.Zero();
  239. private _finalPosition = Vector3.Zero();
  240. public getNewPosition(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, excludedMesh: AbstractMesh, onNewPosition: (collisionIndex: number, newPosition: BABYLON.Vector3, collidedMesh?: BABYLON.AbstractMesh) => void, collisionIndex: number): void {
  241. position.divideToRef(collider.radius, this._scaledPosition);
  242. velocity.divideToRef(collider.radius, this._scaledVelocity);
  243. collider.retry = 0;
  244. collider.initialVelocity = this._scaledVelocity;
  245. collider.initialPosition = this._scaledPosition;
  246. this._collideWithWorld(this._scaledPosition, this._scaledVelocity, collider, maximumRetry, this._finalPosition, excludedMesh);
  247. this._finalPosition.multiplyInPlace(collider.radius);
  248. //run the callback
  249. onNewPosition(collisionIndex, this._finalPosition, collider.collidedMesh);
  250. }
  251. public init(scene: Scene): void {
  252. this._scene = scene;
  253. }
  254. public destroy(): void {
  255. //Legacy need no destruction method.
  256. }
  257. //No update in legacy mode
  258. public onMeshAdded(mesh: AbstractMesh) { }
  259. public onMeshUpdated(mesh: AbstractMesh) { }
  260. public onMeshRemoved(mesh: AbstractMesh) { }
  261. public onGeometryAdded(geometry: Geometry) { }
  262. public onGeometryUpdated(geometry: Geometry) { }
  263. public onGeometryDeleted(geometry: Geometry) { }
  264. private _collideWithWorld(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, finalPosition: Vector3, excludedMesh: AbstractMesh = null): void {
  265. var closeDistance = Engine.CollisionsEpsilon * 10.0;
  266. if (collider.retry >= maximumRetry) {
  267. finalPosition.copyFrom(position);
  268. return;
  269. }
  270. collider._initialize(position, velocity, closeDistance);
  271. // Check all meshes
  272. for (var index = 0; index < this._scene.meshes.length; index++) {
  273. var mesh = this._scene.meshes[index];
  274. if (mesh.isEnabled() && mesh.checkCollisions && mesh.subMeshes && mesh !== excludedMesh) {
  275. mesh._checkCollision(collider);
  276. }
  277. }
  278. if (!collider.collisionFound) {
  279. position.addToRef(velocity, finalPosition);
  280. return;
  281. }
  282. if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
  283. collider._getResponse(position, velocity);
  284. }
  285. if (velocity.length() <= closeDistance) {
  286. finalPosition.copyFrom(position);
  287. return;
  288. }
  289. collider.retry++;
  290. this._collideWithWorld(position, velocity, collider, maximumRetry, finalPosition, excludedMesh);
  291. }
  292. }
  293. }