babylon.collisionCoordinator.ts 18 KB

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