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.

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