How to animate along a curved path using gravity

I am trying to create a simulation in which the ball follows the path of movement, similar to this:

https://bl.ocks.org/mbostock/1705868

However, instead of using tweening, I would like the movement of the ball to be determined by gravity , and the speed of the object to be like rollers, like this:

https://www.myphysicslab.com/roller/roller-single-en.html

This is what I have so far, but there is a small problem that the roller coaster is gaining energy a bit, but not losing it, every frame:

https://jsbin.com/jidazom/edit?html,js,output

x

Any suggestions on how to fix this would be greatly appreciated!

+8
javascript animation svg physics
source share
2 answers

You can try JS Bin . I modified the code to fit my own understanding of the effects of gravity. In the calculations, I use the vertical slope component calculated in the current position (using a small delta on each side and not relying on the previous position):

 function getEffectiveGravityFactor() { // Get the vertical component of the slope at current position var delta = 0.001; var pathPos1 = Math.min(maxRange, Math.max(delta, pathPos)); var pos1 = pathEl.getPointAtLength(pathPos1 - delta); var pos2 = pathEl.getPointAtLength(pathPos1 + delta); var dx, dy; if (direction) { dx = pos2.x - pos1.x; dy = pos2.y - pos1.y; } else { dx = pos1.x - pos2.x; dy = pos1.y - pos2.y; } var total = Math.sqrt(dx * dx + dy * dy); return dy / total; } 

The limits of the way react like cushions for pool tables. I do not know what you plan to do. A rebound is not always perfectly elastic, therefore, when the limit is reached, there may be a slight gain or loss of energy.

I also presented a coefficient of friction, which is pretty rough, but gives an idea of ​​a possible implementation.

Since I'm not sure that requestAnimationFrame is executed at very fixed intervals, I took into account the actual time interval in the calculations. This part may not be needed.

Here is the complete code:

 var svg = d3.select("#line").append("svg:svg").attr("width", "100%").attr("height", "100%"); var data = d3.range(50).map(function(){return Math.random()*10;}); var x = d3.scale.linear().domain([0, 10]).range([0, 700]); var y = d3.scale.linear().domain([0, 10]).range([10, 290]); var line = d3.svg.line() .interpolate("cardinal") .x(function(d,i) {return x(i);}) .y(function(d) {return y(d);}) var path = svg.append("svg:path").attr("d", line(data)); var circle = svg.append("circle") .attr("cx", 100) .attr("cy", 350) .attr("r", 3) .attr("fill", "red"); var circleBehind = svg.append("circle") .attr("cx", 50) .attr("cy", 300) .attr("r", 3) .attr("fill", "blue"); var circleAhead = svg.append("circle") .attr("cx", 125) .attr("cy", 375) .attr("r", 3) .attr("fill", "green"); var pathEl = path.node(); var pathLength = pathEl.getTotalLength(); var BBox = pathEl.getBBox(); var scale = pathLength/BBox.width; var offsetLeft = document.getElementById("line").offsetLeft; var randomizeButton = d3.select("#randomize"); var pathPos = 600; var pos = {x: 0, y: 0}; var speed = 10; var friction = 0; var direction = true; var gravity = 0.01; var maxRange = 1500; var speedChange; var currentTime, prevTime, diffTime; function getEffectiveGravityFactor() { // Get the vertical component of the slope at current position var delta = 0.001; var pathPos1 = Math.min(maxRange, Math.max(delta, pathPos)); var pos1 = pathEl.getPointAtLength(pathPos1 - delta); var pos2 = pathEl.getPointAtLength(pathPos1 + delta); var dx, dy; if (direction) { dx = pos2.x - pos1.x; dy = pos2.y - pos1.y; } else { dx = pos1.x - pos2.x; dy = pos1.y - pos2.y; } var total = Math.sqrt(dx * dx + dy * dy); return dy / total; } function play() { requestAnimationFrame(play); currentTime = Date.now(); diffTime = currentTime - prevTime; if (diffTime > 20) { prevTime = currentTime; if (pathPos < 0 || pathPos > maxRange) { // The limit was reached: change direction direction = !direction; pathPos = Math.min(maxRange, Math.max(0, pathPos)); } else { speedChange = gravity * diffTime * getEffectiveGravityFactor(); if (speedChange < -speed) { // Direction change caused by gravity direction = !direction; speed = 0; } else { speed += speedChange; speed = Math.max(0, speed - friction * diffTime * (0.0002 + 0.00002 * speed)); } } pathPos += (direction ? 1 : -1) * speed; pos = pathEl.getPointAtLength(pathPos); circle .attr("opacity", 1) .attr("cx", pos.x) .attr("cy", pos.y); posBehind = pathEl.getPointAtLength(pathPos - 10); circleBehind .attr("opacity", 1) .attr("cx", posBehind.x) .attr("cy", posBehind.y); posAhead = pathEl.getPointAtLength(pathPos + 10); circleAhead .attr("opacity", 1) .attr("cx", posAhead.x) .attr("cy", posAhead.y); } } prevTime = Date.now(); play(); var txtSpeed = document.getElementById("txtSpeed"); var txtFriction = document.getElementById("txtFriction"); txtSpeed.value = speed; txtFriction.value = friction; randomizeButton.on("click", function(){ speed = parseFloat(txtSpeed.value); friction = parseFloat(txtFriction.value); pathPos = 400; direction = true; prevTime = Date.now(); data = d3.range(50).map(function(){return Math.random()*10;}); circle.attr("opacity", 0); path .transition() .duration(300) .attr("d", line(data)); }); 
+4
source share

You seem to be going in the right direction. d3.js is the best option that I can think of, and there are already some hard questions d3.js that have excellent answers:

Convert d3.js bubbles to formatted / gravity layout

Understanding D3.js Force Layout - 8: Gravity can also be useful as a place to start.

As for the β€œany” tips: work faster (that is, follow the writing and testing cycle as quickly and automatically as possible) and document when you go (in the future you will be grateful.)

-one
source share

All Articles