babylon.collisionCoordinator.ts 15 KB

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