babylon.collisionCoordinator.ts 18 KB

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