babylon.collisionCoordinator.ts 16 KB

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