/** * @class HierarchicalSpringSolver */ class HierarchicalSpringSolver { /** * @param {Object} body * @param {{physicsNodeIndices: Array, physicsEdgeIndices: Array, forces: {}, velocities: {}}} physicsBody * @param {Object} options * @constructor HierarchicalSpringSolver */ constructor(body, physicsBody, options) { this.body = body; this.physicsBody = physicsBody; this.setOptions(options); } /** * * @param {Object} options */ setOptions(options) { this.options = options; } /** * This function calculates the springforces on the nodes, accounting for the support nodes. * * @private */ solve() { var edgeLength, edge; var dx, dy, fx, fy, springForce, distance; var edges = this.body.edges; var factor = 0.5; var edgeIndices = this.physicsBody.physicsEdgeIndices; var nodeIndices = this.physicsBody.physicsNodeIndices; var forces = this.physicsBody.forces; // initialize the spring force counters for (let i = 0; i < nodeIndices.length; i++) { let nodeId = nodeIndices[i]; forces[nodeId].springFx = 0; forces[nodeId].springFy = 0; } // forces caused by the edges, modelled as springs for (let i = 0; i < edgeIndices.length; i++) { edge = edges[edgeIndices[i]]; if (edge.connected === true) { edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length; dx = (edge.from.x - edge.to.x); dy = (edge.from.y - edge.to.y); distance = Math.sqrt(dx * dx + dy * dy); distance = distance === 0 ? 0.01 : distance; // the 1/distance is so the fx and fy can be calculated without sine or cosine. springForce = this.options.springConstant * (edgeLength - distance) / distance; fx = dx * springForce; fy = dy * springForce; if (edge.to.level != edge.from.level) { if (forces[edge.toId] !== undefined) { forces[edge.toId].springFx -= fx; forces[edge.toId].springFy -= fy; } if (forces[edge.fromId] !== undefined) { forces[edge.fromId].springFx += fx; forces[edge.fromId].springFy += fy; } } else { if (forces[edge.toId] !== undefined) { forces[edge.toId].x -= factor * fx; forces[edge.toId].y -= factor * fy; } if (forces[edge.fromId] !== undefined) { forces[edge.fromId].x += factor * fx; forces[edge.fromId].y += factor * fy; } } } } // normalize spring forces springForce = 1; var springFx, springFy; for (let i = 0; i < nodeIndices.length; i++) { let nodeId = nodeIndices[i]; springFx = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFx)); springFy = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFy)); forces[nodeId].x += springFx; forces[nodeId].y += springFy; } // retain energy balance var totalFx = 0; var totalFy = 0; for (let i = 0; i < nodeIndices.length; i++) { let nodeId = nodeIndices[i]; totalFx += forces[nodeId].x; totalFy += forces[nodeId].y; } var correctionFx = totalFx / nodeIndices.length; var correctionFy = totalFy / nodeIndices.length; for (let i = 0; i < nodeIndices.length; i++) { let nodeId = nodeIndices[i]; forces[nodeId].x -= correctionFx; forces[nodeId].y -= correctionFy; } } } export default HierarchicalSpringSolver;