vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
8.4 KiB

  1. import PhysicsBase from './PhysicsBase';
  2. class PhysicsWorker extends PhysicsBase {
  3. constructor(postMessage) {
  4. super();
  5. this.body = {
  6. nodes: {},
  7. edges: {}
  8. };
  9. this.postMessage = postMessage;
  10. this.previousStates = {};
  11. this.toRemove = {
  12. nodeIds: [],
  13. edgeIds: []
  14. };
  15. this.isWorker = true;
  16. this.emit = (event, data) => {this.postMessage({type: 'emit', data: {event: event, data: data}})};
  17. }
  18. handleMessage(event) {
  19. var msg = event.data;
  20. switch (msg.type) {
  21. case 'physicsTick':
  22. this.processRemovals();
  23. this.physicsTick();
  24. this.sendTickResults();
  25. break;
  26. case 'updatePositions':
  27. this.receivePositions(msg.data);
  28. break;
  29. case 'updateProperties':
  30. this.updateProperties(msg.data);
  31. break;
  32. case 'addElements':
  33. this.addElements(msg.data);
  34. break;
  35. case 'removeElements':
  36. this.removeElements(msg.data);
  37. break;
  38. case 'stabilize':
  39. this.stabilize(msg.data);
  40. break;
  41. case 'setStabilized':
  42. this.stabilized = msg.data;
  43. break;
  44. case 'stabilized':
  45. this.startedStabilization = false;
  46. this.stabilizationIterations = 0;
  47. break;
  48. case 'initPhysicsData':
  49. console.debug('init physics data');
  50. this.initPhysicsData(msg.data);
  51. break;
  52. case 'options':
  53. this.options = msg.data;
  54. this.timestep = this.options.timestep;
  55. this.initPhysicsSolvers();
  56. break;
  57. default:
  58. console.warn('unknown message from PhysicsEngine', msg);
  59. }
  60. }
  61. sendTickResults() {
  62. let nodeIndices = this.physicsBody.physicsNodeIndices;
  63. let positions = {};
  64. for (let i = 0; i < nodeIndices.length; i++) {
  65. let nodeId = nodeIndices[i];
  66. let node = this.body.nodes[nodeId];
  67. positions[nodeId] = {x:node.x, y:node.y};
  68. }
  69. this.postMessage({
  70. type: 'tickResults',
  71. data: {
  72. positions: positions,
  73. stabilized: this.stabilized,
  74. stabilizationIterations: this.stabilizationIterations
  75. }
  76. });
  77. }
  78. receivePositions(data) {
  79. let updatedNode = this.body.nodes[data.id];
  80. if (updatedNode) {
  81. updatedNode.x = data.x;
  82. updatedNode.y = data.y;
  83. this.physicsBody.forces[updatedNode.id] = {x: 0, y: 0};
  84. this.physicsBody.velocities[updatedNode.id] = {x: 0, y: 0};
  85. }
  86. }
  87. stabilize(data) {
  88. this.stabilized = false;
  89. this.targetIterations = data.targetIterations;
  90. this.stabilizationIterations = 0;
  91. setTimeout(() => this._stabilizationBatch(), 0);
  92. }
  93. updateProperties(data) {
  94. if (data.type === 'node') {
  95. let optionsNode = this.body.nodes[data.id];
  96. if (optionsNode) {
  97. let opts = data.options;
  98. if (opts.fixed) {
  99. if (opts.fixed.x !== undefined) {
  100. optionsNode.options.fixed.x = opts.fixed.x;
  101. }
  102. if (opts.fixed.y !== undefined) {
  103. optionsNode.options.fixed.y = opts.fixed.y;
  104. }
  105. }
  106. if (opts.mass !== undefined) {
  107. optionsNode.options.mass = opts.mass;
  108. }
  109. if (opts.edges && opts.edges.length) {
  110. optionsNode.edges.length = opts.edges.length;
  111. }
  112. } else {
  113. console.warn('sending properties to unknown node', data.id, data.options);
  114. }
  115. } else if (data.type === 'edge') {
  116. let edge = this.body.edges[data.id];
  117. if (edge) {
  118. let opts = data.options;
  119. if (opts.connected) {
  120. edge.connected = opts.connected;
  121. }
  122. } else {
  123. console.warn('sending properties to unknown edge', data.id, data.options);
  124. }
  125. } else {
  126. console.warn('sending properties to unknown element', data.id, data.options);
  127. }
  128. }
  129. addElements(data, replaceElements = true) {
  130. let nodeIds = Object.keys(data.nodes);
  131. for (let i = 0; i < nodeIds.length; i++) {
  132. let nodeId = nodeIds[i];
  133. let newNode = data.nodes[nodeId];
  134. if (replaceElements) {
  135. this.body.nodes[nodeId] = newNode;
  136. }
  137. this.physicsBody.forces[nodeId] = {x: 0, y: 0};
  138. // forces can be reset because they are recalculated. Velocities have to persist.
  139. if (this.physicsBody.velocities[nodeId] === undefined) {
  140. this.physicsBody.velocities[nodeId] = {x: 0, y: 0};
  141. }
  142. if (this.physicsBody.physicsNodeIndices.indexOf(nodeId) === -1) {
  143. this.physicsBody.physicsNodeIndices.push(nodeId);
  144. }
  145. }
  146. let edgeIds = Object.keys(data.edges);
  147. for (let i = 0; i < edgeIds.length; i++) {
  148. let edgeId = edgeIds[i];
  149. if (replaceElements) {
  150. this.body.edges[edgeId] = data.edges[edgeId];
  151. }
  152. if (this.physicsBody.physicsEdgeIndices.indexOf(edgeId) === -1) {
  153. this.physicsBody.physicsEdgeIndices.push(edgeId);
  154. }
  155. }
  156. }
  157. removeElements(data) {
  158. // schedule removal of elements on the next physicsTick
  159. // avoids having to defensively check every node read in each physics implementation
  160. this.toRemove.nodeIds.push.apply(this.toRemove.nodeIds, data.nodeIds);
  161. this.toRemove.edgeIds.push.apply(this.toRemove.edgeIds, data.edgeIds);
  162. // Handle case where physics is disabled.
  163. if (!this.options.enabled) {
  164. this.processRemovals();
  165. }
  166. }
  167. processRemovals() {
  168. while (this.toRemove.nodeIds.length > 0) {
  169. let nodeId = this.toRemove.nodeIds.pop();
  170. let index = this.physicsBody.physicsNodeIndices.indexOf(nodeId);
  171. if (index > -1) {
  172. this.physicsBody.physicsNodeIndices.splice(index,1);
  173. }
  174. delete this.physicsBody.forces[nodeId];
  175. delete this.physicsBody.velocities[nodeId];
  176. delete this.body.nodes[nodeId];
  177. }
  178. while (this.toRemove.edgeIds.length > 0) {
  179. let edgeId = this.toRemove.edgeIds.pop();
  180. let index = this.physicsBody.physicsEdgeIndices.indexOf(edgeId);
  181. if (index > -1) {
  182. this.physicsBody.physicsEdgeIndices.splice(index,1);
  183. }
  184. delete this.body.edges[edgeId];
  185. }
  186. }
  187. initPhysicsData(data) {
  188. this.physicsBody.forces = {};
  189. this.physicsBody.physicsNodeIndices = [];
  190. this.physicsBody.physicsEdgeIndices = [];
  191. this.body.nodes = data.nodes;
  192. this.body.edges = data.edges;
  193. this.addElements(data, false);
  194. // clean deleted nodes from the velocity vector
  195. for (let nodeId in this.physicsBody.velocities) {
  196. if (this.body.nodes[nodeId] === undefined) {
  197. delete this.physicsBody.velocities[nodeId];
  198. }
  199. }
  200. }
  201. /**
  202. * Perform the actual step
  203. *
  204. * @param nodeId
  205. * @param maxVelocity
  206. * @returns {number}
  207. * @private
  208. */
  209. _performStep(nodeId,maxVelocity) {
  210. let node = this.body.nodes[nodeId];
  211. let timestep = this.timestep;
  212. let forces = this.physicsBody.forces;
  213. let velocities = this.physicsBody.velocities;
  214. // store the state so we can revert
  215. this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
  216. if (node.options.fixed.x === false) {
  217. let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
  218. let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
  219. velocities[nodeId].x += ax * timestep; // velocity
  220. velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
  221. node.x += velocities[nodeId].x * timestep; // position
  222. }
  223. else {
  224. forces[nodeId].x = 0;
  225. velocities[nodeId].x = 0;
  226. }
  227. if (node.options.fixed.y === false) {
  228. let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
  229. let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
  230. velocities[nodeId].y += ay * timestep; // velocity
  231. velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
  232. node.y += velocities[nodeId].y * timestep; // position
  233. }
  234. else {
  235. forces[nodeId].y = 0;
  236. velocities[nodeId].y = 0;
  237. }
  238. let totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
  239. return totalVelocity;
  240. }
  241. _finalizeStabilization() {
  242. this.sendTickResults();
  243. this.postMessage({
  244. type: 'finalizeStabilization'
  245. });
  246. }
  247. }
  248. export default PhysicsWorker;