如何使用重力沿弯曲路径制作动画

时间:2017-07-25 14:08:02

标签: javascript animation d3.js svg physics

我试图创建一个球跟随运动路径的模拟,类似于:

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

然而,我不想使用补间,而是希望球的运动由重力和物体的速度决定 - 类似于过山车,就像这样: / p>

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

这是我到目前为止所做的,但是有一个小问题,过山车每一帧都会略微增加能量,而不是失去它:

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

x

非常感谢任何有关如何解决这个问题的建议!

2 个答案:

答案 0 :(得分:4)

您可以试试JS Bin。我修改了代码以适应我自己对引力效应的理解。在计算中,我使用斜率的垂直分量,在当前位置计算(每侧使用一个小的delta,而不依赖于先前的位置):

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;
}

路径的极限反应就像台球桌垫一样。我不知道这是不是你打算做的。反弹并不总是完全弹性,因此当达到极限时可能会有轻微的能量增益或损失。

我还引入了一个摩擦系数,这个系数非常粗糙,但可以了解可能的实现方法。

由于我不确定requestAnimationFrame是否以非常固定的间隔执行,因此我在计算中考虑了实际的时间间隔。这部分可能没有必要。

以下是完整的代码:

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));
});

答案 1 :(得分:-1)

你似乎正朝着正确的方向前进。 d3.js是我能想到的最佳选择,而且这里已经有一些d3.js重力问题有很好的答案:

Convert d3.js bubbles into forced/gravity based layout

Understanding D3.js Force Layout - 8: gravity也可以作为一个起点。

至于&#39;任何&#39;建议:更快失败,(尽可能快速,自动地保持书写和测试的循环),并随时记录文档(未来你会感激不尽。)