javascript - How to animate along a curved path using gravity -
i'm trying create simulation in ball follows motion path, similar this:
https://bl.ocks.org/mbostock/1705868
however, instead of using tweening, ball's motion determined gravity , object's velocity - similar rollercoaster, this:
https://www.myphysicslab.com/roller/roller-single-en.html
this have far, there's small problem in roller coaster gaining energy, instead of losing it, each frame:
https://jsbin.com/jidazom/edit?html,js,output
x
any suggestions on how fix appreciated!
you can try js bin. modified code fit own understanding of effects of gravity. in calculations, use vertical component of slope, calculated @ current position (using small delta on each side, , not relying on previous position):
function geteffectivegravityfactor() { // vertical component of slope @ 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 path react pool table cushions. don't know if plan do. rebound not elastic, there can slight gain or loss of energy when limit reached.
i introduced friction coefficient, quite rough gives idea of possible implementation.
since not sure if requestanimationframe
executed @ fixed intervals, took actual time interval account in calculations. part may not necessary.
here 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() { // vertical component of slope @ 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) { // limit reached: change direction direction = !direction; pathpos = math.min(maxrange, math.max(0, pathpos)); } else { speedchange = gravity * difftime * geteffectivegravityfactor(); if (speedchange < -speed) { // direction change caused 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)); });
Comments
Post a Comment