Search code examples
javascripthtml5-canvasrotationgame-developmenttrigonometry

How do I rotate one thing in relation to the rotation angle and position of another thing?


So I am working on a simple canvas js game. I have a creature that is going to have multiple bodyparts. Right now I just have two bodyparts and they are both circles, think of them as a head and body. When I spawn the creature the body is positioned directly behind the head. I am trying to make each bodypart of the creature rotate correctly. Currently I have the bodyparts rotating to face the mouse, but I need the body to stick to the back of the head as they rotate to face the mouse.

Here is the object being created:

this.testCreature = new TestingRotation([new BodyTest(2000, 2000, 100, 100), new BodyTest(2000, 2100, 100, 100)])

Here is the rotate function that lives in the TestingRotation class:


 rotate(movement, cameraX, cameraY) {
    if (movement.mousedown && movement.mousemove) {
      const mouseX = movement.mouseX;
      const mouseY = movement.mouseY;

   this.bodyParts.forEach(bodypart => {

      console.log(bodypart.rotationAngle)
    if(bodypart == this.bodyParts[0]){
      const centerX = bodypart.x + bodypart.width / 2
      const centerY = bodypart.y + bodypart.height / 2


      const deltaX = mouseX - centerX;
      const deltaY = mouseY - centerY;

          
  
      bodypart.targetRotationAngle = Math.atan2(deltaY, deltaX) - Math.PI / 2;  // Adjusted orientation
  
      // Gradually adjust the rotation angle towards the target angle
      const diff = bodypart.targetRotationAngle - bodypart.rotationAngle
  
      // Calculate the shortest difference in angles
      const shortestDiff = ((diff % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2) - Math.PI;
  
      if (Math.abs(shortestDiff) > 0.01) {
        
          bodypart.rotationAngle += shortestDiff * this.rotationSpeed;
      }
  
    }

    else{

      const centerX = this.bodyParts[0].x + this.bodyParts[0].width / 2
      const centerY = this.bodyParts[0].y + this.bodyParts[0].height / 2

      const deltaX = mouseX - centerX;
      const deltaY = mouseY - centerY;

  
      bodypart.targetRotationAngle = Math.atan2(deltaY, deltaX) - Math.PI / 2;  // Adjusted orientation
  
      // Gradually adjust the rotation angle towards the target angle
      const diff = bodypart.targetRotationAngle - bodypart.rotationAngle
  
      // Calculate the shortest difference in angles
      const shortestDiff = ((diff % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2) - Math.PI;
  
      if (Math.abs(shortestDiff) > 0.01) {
        
          bodypart.rotationAngle += shortestDiff * this.rotationSpeed;
      }

    }

   });
  }
}

And here is the class that draws the bodyparts:

export class BodyTest {
    constructor(x, y, width, height) {
      this.x = x;
      this.y = y;
      this.width = width;
      this.height = height;
      this.rotationAngle = 0; // Add the rotation property
      this.targetRotationAngle = 0;
    }
  
    getBodyPos() {
      let x = this.x;
      let y = this.y;
      let radius = this.height / 2;
  
      return { x, y, radius };
    }
  
    draw(ctx) {
      const bodyPos = this.getBodyPos();
      const centerX = bodyPos.x 
      const centerY = bodyPos.y 
      const radius = bodyPos.radius;
  
      ctx.save(); // Save the current drawing state
      ctx.translate(centerX, centerY); // Translate to the center of the body part
      ctx.rotate(this.rotationAngle); // Apply the rotation
  
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(0, 0, radius, 0, Math.PI * 2);
      ctx.closePath();
      ctx.fillStyle = 'purple';
      ctx.fill();

      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(0, -30, 5, 0, Math.PI * 2);
      ctx.closePath();
      ctx.fillStyle = 'orange';
      ctx.fill();
  
      ctx.restore(); // Restore the previous drawing state
    }
  }

Solution

  • I can't fully understand what's the problem but rotating relative to another object is pretty easy with math.

    Here is an example which demonstrates several options to do it. First in demo is just using angle and distance relative to target and calculating final position. Further I will describe calculating new position only from previous position and target.

    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const pr = window.devicePixelRatio;
    canvas.width = canvas.clientWidth * pr;
    canvas.height = canvas.clientHeight * pr;
    
    const target = {
      x: 100*pr, 
      y: 100*pr, 
      s: 8*pr,
      angle: 1,
      color: "#f59"
    };
    
    const main = {
      x: 140 * pr,
      y: 120 * pr,
      angle: 0,
      dist: 50,
      phi: 0,
      s: 10*pr,
      color: "#5fb"
    };
    
    const drawObj = (ctx, o) => {
        ctx.save();
        ctx.translate(o.x, o.y);
        ctx.rotate(o.angle);
        ctx.fillStyle = o.color;
        ctx.fillRect(-o.s, -o.s, o.s * 2, o.s * 2);
        ctx.restore();
    }
    
    const processMain = () => {
      main.phi = (Date.now() / 1000) % (Math.PI * 2);
      main.x = target.x + Math.cos(main.phi) * main.dist;
      main.y = target.y + Math.sin(main.phi) * main.dist;
      return main;
    }
    
    const draw = () => {
      ctx.fillStyle = '#333';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      drawObj(ctx, target);
      drawObj(ctx, processMain());
      requestAnimationFrame(draw);
    }
    
    draw();
    #canvas {
      width: 200px;
      height: 200px;
      border: 1px #aaa solid;
    }
    <canvas id="canvas"></canvas>

    This is relative to previous position (probably what you want)

    const processMain = () => {
      const phi = Math.atan2(main.y - target.y, main.x - target.x) + 0.01;
      const dist = Math.hypot(main.y - target.y, main.x - target.x);
      main.x = target.x + Math.cos(phi) * dist;
      main.y = target.y + Math.sin(phi) * dist;
      return main;
    }
    

    You also can make main object to be rotated with orientation to target.

    const processMain = () => {
      const phi = Math.atan2(main.y - target.y, main.x - target.x) + 0.01;
      const dist = Math.hypot(main.y - target.y, main.x - target.x);
      main.angle += 0.01; // or just `main.angle = phi;`
      main.x = target.x + Math.cos(phi) * dist;
      main.y = target.y + Math.sin(phi) * dist;
      return main;
    }
    

    See this codepen

    You also can use canv tiny library to work with all this problems. This allows you to control your canvas as space with objects. You can set some objects to be children of other and much more. See it's demo page to see what you can do.