matt
matt

Reputation: 58

svg.js animated rotation of elements gives unexpected results (visible "jiggling")

I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).

If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).

I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:

var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
  x: 70,
  y: 70
}
var length = 60

var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
  .stroke({
    color: "#00f",
    width: 4
  })
blueLine.angle = 0

var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
  .stroke({
    color: "#0f0",
    width: 4
  })
greenLine.angle = 0

var previous = 0
var dt = 0
var step = function(timestamp) {
  dt = timestamp - previous
  previous = timestamp
  blueLine.angle += 0.18 * dt
  blueLine.rotate(blueLine.angle, origin.x, origin.y)
  var endX = Math.cos(toRad(blueLine.angle)) * length
  var endY = Math.sin(toRad(blueLine.angle)) * length

  // Comment out this line, and rotation works fine
  greenLine.move(endX, endY)

  greenLine.angle = blueLine.angle - 10

  // Comment out this line, and movement works fine
  greenLine.rotate(greenLine.angle, origin.x, origin.y)

  // But they don't work together. If I both move and rotate 
  // the green line, it goes in this crazy huge arc, rather 
  // than rotating neatly around the end of the blue line 
  // as expected.
  window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)

function toRad(deg) {
  return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>

Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.

I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.

Thank you kind vector graphics experts!

Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:

Rider.Leg = function(foot, front, xOffset, yOffset) {
    var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
    var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
    this.foot = foot
    this.draw = foot.draw
    this.geo = {
        upper: {
            x: this.foot.pedal.gear.x + 150,
            y: this.foot.pedal.gear.y - 750,
            length: 396
        },
        lower: {
            length: 390
        }
    }
    this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
        .transform({ scale: 0.95, cx: 0, cy: 0 })
    this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}

// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
    var angle = this.pedalAngle() - Math.PI
    var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
    var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
    var x = this.geo.upper.length * Math.cos(ha + angle)
    var y = this.geo.upper.length * Math.sin(ha + angle)
    this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
    this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
    this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)

}

// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
    var pos = this.foot.getPos()
    var xDist = this.geo.upper.x - pos.x
    var yDist = this.geo.upper.y - pos.y
    return Math.hypot(xDist, yDist)
}

// Gets the angle between the hip joint and the pedal 
Rider.Leg.prototype.pedalAngle = function () {
    var pos = this.foot.getPos()
    var xDist = this.geo.upper.x - pos.x
    var yDist = this.geo.upper.y - pos.y
    return Math.atan2(yDist, xDist)
}

Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
    return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}

Upvotes: 1

Views: 355

Answers (1)

Fuzzyma
Fuzzyma

Reputation: 8474

When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.

Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:

greenLine.transform({x:endX, y:endY, relative: true})

To move the line by a certain amount. That should work way better.

Upvotes: 1

Related Questions