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.

191 lines
6.9 KiB

  1. /**
  2. * Created by Alex on 2/23/2015.
  3. */
  4. import {BarnesHutSolver} from "./components/physics/BarnesHutSolver";
  5. import {Repulsion} from "./components/physics/RepulsionSolver";
  6. import {HierarchicalRepulsion} from "./components/physics/HierarchicalRepulsionSolver";
  7. import {SpringSolver} from "./components/physics/SpringSolver";
  8. import {HierarchicalSpringSolver} from "./components/physics/HierarchicalSpringSolver";
  9. import {CentralGravitySolver} from "./components/physics/CentralGravitySolver";
  10. class PhysicsEngine {
  11. constructor(body, options) {
  12. this.body = body;
  13. this.physicsBody = {calculationNodes: {}, calculationNodeIndices:[], forces: {}, velocities: {}};
  14. this.previousStates = {};
  15. this.setOptions(options);
  16. }
  17. setOptions(options) {
  18. if (options !== undefined) {
  19. this.options = options;
  20. this.init();
  21. }
  22. }
  23. init() {
  24. var options;
  25. if (this.options.model == "repulsion") {
  26. options = this.options.repulsion;
  27. this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
  28. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  29. }
  30. else if (this.options.model == "hierarchicalRepulsion") {
  31. options = this.options.hierarchicalRepulsion;
  32. this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options);
  33. this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options);
  34. }
  35. else { // barnesHut
  36. options = this.options.barnesHut;
  37. this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options);
  38. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  39. }
  40. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  41. this.modelOptions = options;
  42. }
  43. /**
  44. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  45. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  46. * This function joins the datanodes and invisible (called support) nodes into one object.
  47. * We do this so we do not contaminate this.body.nodes with the support nodes.
  48. *
  49. * @private
  50. */
  51. _updateCalculationNodes() {
  52. this.physicsBody.calculationNodes = {};
  53. this.physicsBody.forces = {};
  54. this.physicsBody.calculationNodeIndices = [];
  55. for (let i = 0; i < this.body.nodeIndices.length; i++) {
  56. let nodeId = this.body.nodeIndices[i];
  57. this.physicsBody.calculationNodes[nodeId] = this.body.nodes[nodeId];
  58. }
  59. // if support nodes are used, we have them here
  60. var supportNodes = this.body.supportNodes;
  61. for (let i = 0; i < this.body.supportNodeIndices.length; i++) {
  62. let supportNodeId = this.body.supportNodeIndices[i];
  63. if (this.body.edges[supportNodes[supportNodeId].parentEdgeId] !== undefined) {
  64. this.physicsBody.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  65. }
  66. else {
  67. console.error("Support node detected that does not have an edge!")
  68. }
  69. }
  70. this.physicsBody.calculationNodeIndices = Object.keys(this.physicsBody.calculationNodes);
  71. for (let i = 0; i < this.physicsBody.calculationNodeIndices.length; i++) {
  72. let nodeId = this.physicsBody.calculationNodeIndices[i];
  73. this.physicsBody.forces[nodeId] = {x:0,y:0};
  74. // forces can be reset because they are recalculated. Velocities have to persist.
  75. if (this.physicsBody.velocities[nodeId] === undefined) {
  76. this.physicsBody.velocities[nodeId] = {x:0,y:0};
  77. }
  78. }
  79. // clean deleted nodes from the velocity vector
  80. for (let nodeId in this.physicsBody.velocities) {
  81. if (this.physicsBody.calculationNodes[nodeId] === undefined) {
  82. delete this.physicsBody.velocities[nodeId];
  83. }
  84. }
  85. }
  86. revert() {
  87. var nodeIds = Object.keys(this.previousStates);
  88. var nodes = this.physicsBody.calculationNodes;
  89. var velocities = this.physicsBody.velocities;
  90. for (let i = 0; i < nodeIds.length; i++) {
  91. let nodeId = nodeIds[i];
  92. if (nodes[nodeId] !== undefined) {
  93. velocities[nodeId].x = this.previousStates[nodeId].vx;
  94. velocities[nodeId].y = this.previousStates[nodeId].vy;
  95. nodes[nodeId].x = this.previousStates[nodeId].x;
  96. nodes[nodeId].y = this.previousStates[nodeId].y;
  97. }
  98. else {
  99. delete this.previousStates[nodeId];
  100. }
  101. }
  102. }
  103. moveNodes() {
  104. var nodesPresent = false;
  105. var nodeIndices = this.physicsBody.calculationNodeIndices;
  106. var maxVelocity = this.options.maxVelocity === 0 ? 1e9 : this.options.maxVelocity;
  107. var moving = false;
  108. var vminCorrected = this.options.minVelocity / Math.max(this.body.functions.getScale(),0.05);
  109. for (let i = 0; i < nodeIndices.length; i++) {
  110. let nodeId = nodeIndices[i];
  111. let nodeVelocity = this._performStep(nodeId, maxVelocity);
  112. moving = nodeVelocity > vminCorrected;
  113. nodesPresent = true;
  114. }
  115. if (nodesPresent == true) {
  116. if (vminCorrected > 0.5*this.options.maxVelocity) {
  117. return true;
  118. }
  119. else {
  120. return moving;
  121. }
  122. }
  123. return false;
  124. }
  125. _performStep(nodeId,maxVelocity) {
  126. var node = this.physicsBody.calculationNodes[nodeId];
  127. var timestep = this.options.timestep;
  128. var forces = this.physicsBody.forces;
  129. var velocities = this.physicsBody.velocities;
  130. // store the state so we can revert
  131. this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
  132. if (!node.xFixed) {
  133. let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
  134. let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
  135. velocities[nodeId].x += ax * timestep; // velocity
  136. velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
  137. node.x += velocities[nodeId].x * timestep; // position
  138. }
  139. else {
  140. forces[nodeId].x = 0;
  141. velocities[nodeId].x = 0;
  142. }
  143. if (!node.yFixed) {
  144. let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
  145. let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
  146. velocities[nodeId].y += ay * timestep; // velocity
  147. velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
  148. node.y += velocities[nodeId].y * timestep; // position
  149. }
  150. else {
  151. forces[nodeId].y = 0;
  152. velocities[nodeId].y = 0;
  153. }
  154. var totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
  155. return totalVelocity;
  156. }
  157. calculateForces() {
  158. this.gravitySolver.solve();
  159. this.nodesSolver.solve();
  160. this.edgesSolver.solve();
  161. }
  162. }
  163. export {PhysicsEngine};