nicholaswmin
nicholaswmin

Reputation: 22939

Rotating a 'body along a path using the external angles

I've got a small script where:

Everything is working fine but I'm having a small problem with rotations. The body should rotate to align itself with the reflex/external angles.

As you can see in the following MCVE the 2nd rotation is clockwise, when it should be counterclockwise.

The opposite happens at the 3rd segment. It rotates counter-clockwise, where it should rotate clockwise, since that rotation would follow the external angle.

What am I doing wrong?

paper.setup(document.querySelector('canvas'))

// Path

const path = new paper.Path({
  segments: [[-100, 300], [100, 300], [100, 0], [0, 100], [-100, 200], [-200, -50]],
  strokeColor: '#E4141B',
  strokeWidth: 5,
  strokeCap: 'round',
  position: paper.view.center
})

path.segments.forEach(segment => {
  const text = new paper.PointText({
    point: [50, 50],
    content: `${parseInt(path.getTangentAt(segment.location).angle)} deg`,
    fillColor: 'black',
    fontFamily: 'Courier New',
    fontWeight: 'bold',
    fontSize: 15,
    position: segment.point
  })
})

// Car

const car = new paper.Path.Rectangle(
  new paper.Rectangle(new paper.Point(50, 50), new paper.Point(150, 100))
)
car.fillColor = '#e9e9ff'
car.rotationLabel = new paper.PointText({
  point: [50, 50],
  content: '0',
  fillColor: 'black',
  fontFamily: 'Courier New',
  fontWeight: 'bold',
  fontSize: 10,
  position: car.position
})

// Car custom

car.currentRotation = 0
car.rotateAroundCenter = function(rotation) {
  rotation = parseInt(rotation)
  this.rotate(rotation)
  this.currentRotation += rotation
}

car.updateRotationLabel = function() {
  this.rotationLabel.position = this.position
  this.rotationLabel.content = this.currentRotation
}

car.getCurrentRotation = function() {
  return this.currentRotation
}

car.isNotAlignedWith = function(rotation) {
  return this.currentRotation !== parseInt(rotation)
}

// Animation-along-a-path

let i = 0
paper.view.onFrame = () => {
  car.updateRotationLabel()

  const rotation = path.getTangentAt(i).angle
  const rotationSign = car.getCurrentRotation() < rotation ? 1 : -1

  car.position = path.getPointAt(i)

  if (car.isNotAlignedWith(rotation)) {
    car.rotateAroundCenter(rotationSign)
  } else {
    car.position = path.getPointAt(i);

    i++

    if (i > path.length - 1) {
      paper.view.onFrame = () => {}
      console.log('done')
    }
  }
}
canvas {
  width: 100%;
  height: 100%;
  background: #666;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<canvas></canvas>

FWIW I've drawn the external(reflex) angles that the path should rotate along when rotating.

enter image description here

Note: The black angle text on each segment is the tangent of that segment.

Upvotes: 2

Views: 201

Answers (2)

Yevgeniy.Chernobrivets
Yevgeniy.Chernobrivets

Reputation: 3194

I would suggest to use direction vector as current direction instead of just angle because it will be much easier to determine in what direction you should rotate, etc.

paper.setup(document.querySelector('canvas'))

// Path

const path = new paper.Path({
  segments: [[-100, 300], [100, 300], [100, 0], [0, 100], [-100, 200], [-200, -50]],
  strokeColor: '#E4141B',
  strokeWidth: 5,
  strokeCap: 'round',
  position: paper.view.center
})

path.segments.forEach(segment => {
  const text = new paper.PointText({
    point: [50, 50],
    content: `${parseInt(path.getTangentAt(segment.location).angle)} deg`,
    fillColor: 'black',
    fontFamily: 'Courier New',
    fontWeight: 'bold',
    fontSize: 15,
    position: segment.point
  })
})

// Car

const car = new paper.Path.Rectangle(
  new paper.Rectangle(new paper.Point(50, 50), new paper.Point(150, 100))
)
car.fillColor = '#e9e9ff'
car.rotationLabel = new paper.PointText({
  point: [50, 50],
  content: '0',
  fillColor: 'black',
  fontFamily: 'Courier New',
  fontWeight: 'bold',
  fontSize: 10,
  position: car.position
})

// Car custom

car.currentRotation = new paper.Point(1, 0)
car.rotateAroundCenter = function(rotation) {
  this.rotate(rotation)
  this.currentRotation = this.currentRotation.rotate(rotation)
}

car.updateRotationLabel = function() {
  this.rotationLabel.position = this.position
  this.rotationLabel.content = this.currentRotation.angle;
}

car.getCurrentRotation = function() {
  return this.currentRotation
}

car.isNotAlignedWith = function(rotation) {
  const precision = 0.00001;
  return Math.abs(1 - rotation.dot(this.currentRotation)) <= precision ? false : true;
}

// Animation-along-a-path

let i = 0

paper.view.onFrame = () => {
  car.updateRotationLabel()

  const requiredDirection = path.getTangentAt(i)
  const normal = requiredDirection.rotate(-90);
  const rotationSign = car.getCurrentRotation().dot(normal) > 0 ? 1 : -1

  car.position = path.getPointAt(i)

  if (car.isNotAlignedWith(requiredDirection)) {
    car.rotateAroundCenter(rotationSign);
  } else {
    car.position = path.getPointAt(i);
    i++

    if (i > path.length - 1) {
      paper.view.onFrame = () => {}
      console.log('done')
    }
  }
}
canvas {
  width: 100%;
  height: 100%;
  background: #666;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<canvas></canvas>

Upvotes: 2

Sebastian Speitel
Sebastian Speitel

Reputation: 7336

There are two things to solve to problem.

The first is that the real angle range your program should work in is only 0 to 360 degrees. I solved this by calculating the modulo 360 of the angles and adding 360 if they are still below 0 to be safe that they are in range 0-360.

The second thing is that there are two cases in which the block should rotation in direction 1. Once when the angle it should point to is greater than the one it is facing right now. But also when the difference is more than 180 and it's the other way around because angles are a circle like number, which means wrapping around can be the shortest route to another value (E.g. The difference from 10° to 350° in a range from 0 to 360 is just 20° if wrapping around instead of the 340° when going the normal way).

paper.setup(document.querySelector('canvas'))
const MAXANGLE = 360;
// Path

const path = new paper.Path({
  segments: [
    [-100, 300],
    [100, 300],
    [100, 0],
    [0, 100],
    [-100, 200],
    [-200, -50]
  ],
  strokeColor: '#E4141B',
  strokeWidth: 5,
  strokeCap: 'round',
  position: paper.view.center
})

path.segments.forEach(segment => {
  const text = new paper.PointText({
    point: [50, 50],
    content: `${parseInt(path.getTangentAt(segment.location).angle)} deg`,
    fillColor: 'black',
    fontFamily: 'Courier New',
    fontWeight: 'bold',
    fontSize: 15,
    position: segment.point
  })
})

// Car

const car = new paper.Path.Rectangle(
  new paper.Rectangle(new paper.Point(50, 50), new paper.Point(150, 100))
)
car.fillColor = '#e9e9ff'
car.rotationLabel = new paper.PointText({
  point: [50, 50],
  content: '0',
  fillColor: 'black',
  fontFamily: 'Courier New',
  fontWeight: 'bold',
  fontSize: 10,
  position: car.position
})

// Car custom

car.currentRotation = 0
car.rotateAroundCenter = function(rotation) {
  rotation = parseInt(rotation)
  this.rotate(rotation)
  this.currentRotation += rotation
}

car.updateRotationLabel = function() {
  this.rotationLabel.position = this.position
  this.rotationLabel.content = this.currentRotation
}

car.getCurrentRotation = function() {
  return this.currentRotation
}

car.isNotAlignedWith = function(rotation) {
  var a1 = this.currentRotation % MAXANGLE;
  var a2 = parseInt(rotation) % MAXANGLE;
  if (a1 < 0) a1 += MAXANGLE;
  if (a2 < 0) a2 += MAXANGLE;
  return a1 !== a2;
}

car.getRotationAngle = function(rotation) {
  var a1 = this.currentRotation % MAXANGLE;
  var a2 = parseInt(rotation) % MAXANGLE;
  if (a1 < 0) a1 += MAXANGLE;
  if (a2 < 0) a2 += MAXANGLE;
  return (a2 > a1 && a2 - a1 <= MAXANGLE / 2) || (a1 > a2 && a1 - a2 >= MAXANGLE / 2) ? 1 : -1;
}

// Animation-along-a-path

let i = 0
paper.view.onFrame = () => {
  car.updateRotationLabel()

  const rotation = path.getTangentAt(i).angle
  const rotationSign = car.getRotationAngle(rotation);

  car.position = path.getPointAt(i)

  if (car.isNotAlignedWith(rotation)) {
    car.rotateAroundCenter(rotationSign)
  } else {
    car.position = path.getPointAt(i);

    i++

    if (i > path.length - 1) {
      paper.view.onFrame = () => {}
      console.log('done')
    }
  }
}
canvas {
  width: 100%;
  height: 100%;
  background: #666;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<canvas></canvas>

Upvotes: 1

Related Questions