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.

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