【问题标题】:How to animate along a curved path using gravity如何使用重力沿弯曲路径制作动画
【发布时间】:2017-07-25 14:08:01
【问题描述】:

我正在尝试创建一个模拟,其中球遵循运动路径,类似于:

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

但是,我希望球的运动由重力和物体的速度决定,而不是使用补间——类似于过山车,如下所示:

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

这是我目前所拥有的,但有一个小问题,即过山车在每一帧都略微获得能量,而不是失去能量:

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

x

任何有关如何解决此问题的建议将不胜感激!

【问题讨论】:

    标签: javascript animation d3.js svg physics


    【解决方案1】:

    你可以试试这个JS Bin。我修改了代码以适应我自己对重力影响的理解。在计算中,我使用斜率的垂直分量,在当前位置计算(每边使用一个小增量,而不依赖于之前的位置):

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

    【讨论】:

      【解决方案2】:

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

      Convert d3.js bubbles into forced/gravity based layout

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

      至于“任何”建议:更快地失败,(即保持编写和测试的周期尽可能快速和自动化),并随时记录,(未来你会感激不尽。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-29
        • 2014-12-16
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 2010-11-11
        • 2020-05-28
        • 1970-01-01
        相关资源
        最近更新 更多