collisionCoordinator.ts 18 KB

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