babylon.collisionCoordinator.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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: BABYLON.Vector3, collidedMesh?: BABYLON.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: BABYLON.Vector3, collidedMesh?: BABYLON.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: BABYLON.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 = (<BABYLON.Mesh>mesh).geometry ? (<BABYLON.Mesh>mesh).geometry.id : null;
  132. return {
  133. uniqueId: mesh.uniqueId,
  134. id: mesh.id,
  135. name: mesh.name,
  136. geometryId: geometryId,
  137. sphereCenter: mesh.getBoundingInfo().boundingSphere.centerWorld.asArray(),
  138. sphereRadius: mesh.getBoundingInfo().boundingSphere.radiusWorld,
  139. boxMinimum: mesh.getBoundingInfo().boundingBox.minimumWorld.asArray(),
  140. boxMaximum: mesh.getBoundingInfo().boundingBox.maximumWorld.asArray(),
  141. worldMatrixFromCache: mesh.worldMatrixFromCache.asArray(),
  142. subMeshes: submeshes,
  143. checkCollisions: mesh.checkCollisions
  144. }
  145. }
  146. public static SerializeGeometry = function (geometry: BABYLON.Geometry): SerializedGeometry {
  147. return {
  148. id: geometry.id,
  149. positions: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.PositionKind) || []),
  150. normals: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.NormalKind) || []),
  151. indices: new Int32Array(geometry.getIndices() || []),
  152. //uvs: new Float32Array(geometry.getVerticesData(BABYLON.VertexBuffer.UVKind) || [])
  153. }
  154. }
  155. 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 {
  156. if (!this._init);
  157. if (this._collisionsCallbackArray[collisionIndex]) return;
  158. position.divideToRef(collider.radius, this._scaledPosition);
  159. velocity.divideToRef(collider.radius, this._scaledVelocity);
  160. this._collisionsCallbackArray[collisionIndex] = onNewPosition;
  161. var payload: CollidePayload = {
  162. collider: {
  163. position: this._scaledPosition.asArray(),
  164. velocity: this._scaledVelocity.asArray(),
  165. radius: collider.radius.asArray()
  166. },
  167. collisionId: collisionIndex,
  168. excludedMeshUniqueId: excludedMesh ? excludedMesh.uniqueId : null,
  169. maximumRetry: maximumRetry
  170. };
  171. var message: BabylonMessage = {
  172. payload: payload,
  173. taskType: WorkerTaskType.COLLIDE
  174. }
  175. this._worker.postMessage(message);
  176. }
  177. public init(scene: Scene): void {
  178. this._scene = scene;
  179. this._scene.registerAfterRender(this._afterRender);
  180. var workerUrl = BABYLON.WorkerIncluded ? Engine.CodeRepository + "Collisions/babylon.collisionWorker.js" : URL.createObjectURL(new Blob([BABYLON.CollisionWorker], { type: 'application/javascript' }));
  181. this._worker = new Worker(workerUrl);
  182. this._worker.onmessage = this._onMessageFromWorker;
  183. var message: BabylonMessage = {
  184. payload: {},
  185. taskType: WorkerTaskType.INIT
  186. }
  187. this._worker.postMessage(message);
  188. }
  189. public destroy(): void {
  190. this._scene.unregisterAfterRender(this._afterRender);
  191. this._worker.terminate();
  192. }
  193. public onMeshAdded(mesh: AbstractMesh) {
  194. mesh.registerAfterWorldMatrixUpdate(this.onMeshUpdated);
  195. this.onMeshUpdated(mesh);
  196. }
  197. public onMeshUpdated = (mesh: AbstractMesh) => {
  198. this._addUpdateMeshesList[mesh.uniqueId] = CollisionCoordinatorWorker.SerializeMesh(mesh);
  199. }
  200. public onMeshRemoved(mesh: AbstractMesh) {
  201. this._toRemoveMeshesArray.push(mesh.uniqueId);
  202. }
  203. public onGeometryAdded(geometry: Geometry) {
  204. //TODO this will break if the user uses his own function. This should be an array of callbacks!
  205. geometry.onGeometryUpdated = this.onGeometryUpdated;
  206. this.onGeometryUpdated(geometry);
  207. }
  208. public onGeometryUpdated = (geometry: Geometry) => {
  209. this._addUpdateGeometriesList[geometry.id] = CollisionCoordinatorWorker.SerializeGeometry(geometry);
  210. }
  211. public onGeometryDeleted(geometry: Geometry) {
  212. this._toRemoveGeometryArray.push(geometry.id);
  213. }
  214. private _afterRender = () => {
  215. if (!this._init) return;
  216. if (this._toRemoveGeometryArray.length == 0 && this._toRemoveMeshesArray.length == 0 && Object.keys(this._addUpdateGeometriesList).length == 0 && Object.keys(this._addUpdateMeshesList).length == 0) {
  217. return;
  218. }
  219. //5 concurrent updates were sent to the web worker and were not yet processed. Abort next update.
  220. //TODO make sure update runs as fast as possible to be able to update 60 FPS.
  221. if (this._runningUpdated > 4) {
  222. return;
  223. }
  224. ++this._runningUpdated;
  225. var payload: UpdatePayload = {
  226. updatedMeshes: this._addUpdateMeshesList,
  227. updatedGeometries: this._addUpdateGeometriesList,
  228. removedGeometries: this._toRemoveGeometryArray,
  229. removedMeshes: this._toRemoveMeshesArray
  230. };
  231. var message: BabylonMessage = {
  232. payload: payload,
  233. taskType: WorkerTaskType.UPDATE
  234. }
  235. var serializable = [];
  236. for (var id in payload.updatedGeometries) {
  237. if (payload.updatedGeometries.hasOwnProperty(id)) {
  238. //prepare transferables
  239. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].indices.buffer);
  240. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].normals.buffer);
  241. serializable.push((<UpdatePayload> message.payload).updatedGeometries[id].positions.buffer);
  242. }
  243. }
  244. this._worker.postMessage(message, serializable);
  245. this._addUpdateMeshesList = {};
  246. this._addUpdateGeometriesList = {};
  247. this._toRemoveGeometryArray = [];
  248. this._toRemoveMeshesArray = [];
  249. }
  250. private _onMessageFromWorker = (e: MessageEvent) => {
  251. var returnData = <WorkerReply> e.data;
  252. if (returnData.error != WorkerReplyType.SUCCESS) {
  253. //TODO what errors can be returned from the worker?
  254. Tools.Warn("error returned from worker!");
  255. return;
  256. }
  257. switch (returnData.taskType) {
  258. case WorkerTaskType.INIT:
  259. this._init = true;
  260. //Update the worked with ALL of the scene's current state
  261. this._scene.meshes.forEach((mesh) => {
  262. this.onMeshAdded(mesh);
  263. });
  264. this._scene.getGeometries().forEach((geometry) => {
  265. this.onGeometryAdded(geometry);
  266. });
  267. break;
  268. case WorkerTaskType.UPDATE:
  269. this._runningUpdated--;
  270. break;
  271. case WorkerTaskType.COLLIDE:
  272. this._runningCollisionTask = false;
  273. var returnPayload: CollisionReplyPayload = returnData.payload;
  274. if (!this._collisionsCallbackArray[returnPayload.collisionId]) return;
  275. this._collisionsCallbackArray[returnPayload.collisionId](returnPayload.collisionId, Vector3.FromArray(returnPayload.newPosition), this._scene.getMeshByUniqueID(returnPayload.collidedMeshUniqueId));
  276. //cleanup
  277. this._collisionsCallbackArray[returnPayload.collisionId] = undefined;
  278. break;
  279. }
  280. }
  281. }
  282. export class CollisionCoordinatorLegacy implements ICollisionCoordinator {
  283. private _scene: Scene;
  284. private _scaledPosition = Vector3.Zero();
  285. private _scaledVelocity = Vector3.Zero();
  286. private _finalPosition = Vector3.Zero();
  287. 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 {
  288. position.divideToRef(collider.radius, this._scaledPosition);
  289. velocity.divideToRef(collider.radius, this._scaledVelocity);
  290. collider.retry = 0;
  291. collider.initialVelocity = this._scaledVelocity;
  292. collider.initialPosition = this._scaledPosition;
  293. this._collideWithWorld(this._scaledPosition, this._scaledVelocity, collider, maximumRetry, this._finalPosition, excludedMesh);
  294. this._finalPosition.multiplyInPlace(collider.radius);
  295. //run the callback
  296. onNewPosition(null, this._finalPosition, collider.collidedMesh);
  297. }
  298. public init(scene: Scene): void {
  299. this._scene = scene;
  300. }
  301. public destroy(): void {
  302. //Legacy need no destruction method.
  303. }
  304. //No update in legacy mode
  305. public onMeshAdded(mesh: AbstractMesh) { }
  306. public onMeshUpdated(mesh: AbstractMesh) { }
  307. public onMeshRemoved(mesh: AbstractMesh) { }
  308. public onGeometryAdded(geometry: Geometry) { }
  309. public onGeometryUpdated(geometry: Geometry) { }
  310. public onGeometryDeleted(geometry: Geometry) { }
  311. private _collideWithWorld(position: Vector3, velocity: Vector3, collider: Collider, maximumRetry: number, finalPosition: Vector3, excludedMesh: AbstractMesh = null): void {
  312. var closeDistance = Engine.CollisionsEpsilon * 10.0;
  313. if (collider.retry >= maximumRetry) {
  314. finalPosition.copyFrom(position);
  315. return;
  316. }
  317. collider._initialize(position, velocity, closeDistance);
  318. // Check all meshes
  319. for (var index = 0; index < this._scene.meshes.length; index++) {
  320. var mesh = this._scene.meshes[index];
  321. if (mesh.isEnabled() && mesh.checkCollisions && mesh.subMeshes && mesh !== excludedMesh) {
  322. mesh._checkCollision(collider);
  323. }
  324. }
  325. if (!collider.collisionFound) {
  326. position.addToRef(velocity, finalPosition);
  327. return;
  328. }
  329. if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
  330. collider._getResponse(position, velocity);
  331. }
  332. if (velocity.length() <= closeDistance) {
  333. finalPosition.copyFrom(position);
  334. return;
  335. }
  336. collider.retry++;
  337. this._collideWithWorld(position, velocity, collider, maximumRetry, finalPosition, excludedMesh);
  338. }
  339. }
  340. }