1
\$\begingroup\$

I was looking over the API for a game engine a friend was developing in her spare time. She made the decision to make positive angles rotate in a clockwise direction. This immediately struck me as odd. My intuition was that angle should be measured counter-clockwise. I envision the typical unit circle when thinking about 2D rotations: enter image description here

Her reasoning for picking this convention was that she was using a "positive y" = "down" coordinate system. From a mathematical sense, it makes sense to me that rotations would be clockwise if you're using this orientation. sin(90 degrees) would give me y = +1, which would be in a downward direction on my screen. So shouldn't I be thinking about angles in a clockwise way?

Why have we arrived at the convention that rotations should be counter-clockwise then, even in engines where positive y is down?

\$\endgroup\$
  • 2
    \$\begingroup\$ "This immediately struck me as odd. My intuition was that angle should be measured counter-clockwise" — that may itself be your answer, if you consider that many developers might have the same first reaction as you. ;) \$\endgroup\$ – DMGregory Jan 23 at 17:24
  • \$\begingroup\$ Can you give examples of two game engines where positive is counterclockwise? \$\endgroup\$ – Tanner Swett Jan 30 at 23:29
3
\$\begingroup\$

Why have we arrived at the convention that rotations should be counter-clockwise then, even in engines where positive y is down?

Have we? Let us try CSS:

const box = document.getElementById("box");

function step(timestamp)
{
    let deg = timestamp/10;
    box.style.transform = "rotate(" + deg + "deg)"; 
    window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step);
#box
{
  width: 100px;
  height: 100px;
  border: 1px solid black;
}
<div id="box"></div>

Hmm... It look clockwise to me.

Let us try with canvas:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

canvas.width = 250;
canvas.height = 250;

const cx = canvas.width/2;
const cy = canvas.height/2;

const x = -50;
const y = -50;
const w = 100;
const h = 100;

function step(timestamp)
{
    let deg = timestamp/10;
    let rad = deg * Math.PI/180;
    
    // erase
    ctx.save();    
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);    
    ctx.restore();
    
    // draw
    ctx.save();
    ctx.translate(cx, cy); // pivot point
    ctx.rotate(rad); // rotate square in radians   
    ctx.fillRect(x, y, w, h);    
    ctx.restore();
    
    window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step);
<canvas id="canvas"></canvas>

Still clockwise.

WebGL?

const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");

const vsSource = `
    attribute vec4 aVertexPosition;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    void main() {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
    }
`;

const fsSource = `
    void main() {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
`;

function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  return shader;
}

const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
  },
};

// ---

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

const positions = [
 -1.0,  1.0,
  1.0,  1.0,
 -1.0, -1.0,
  1.0, -1.0,
];
const numComponents = 2;
const componentType = gl.FLOAT;
const vertexCount = 4;

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

// ---

gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);

// ---

function step(timestamp)
{
    let deg = timestamp/10;
    let rad = deg * Math.PI/180;
    
    //erase
    
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    //draw
    
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(
        programInfo.attribLocations.vertexPosition,
        numComponents,
        componentType,
        false,
        0,
        0);
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);    
    gl.useProgram(programInfo.program);    
    gl.uniformMatrix4fv(
      programInfo.uniformLocations.projectionMatrix,
      false,      
      [1.0, 0.0, 0.0, 0.0,
       0.0, 1.0, 0.0, 0.0,
       0.0, 0.0, 1.0, 0.0,
       0.0, 0.0, 0.0, 1.0]);       
    gl.uniformMatrix4fv(
      programInfo.uniformLocations.modelViewMatrix,
      false,
      [Math.cos(rad), -Math.sin(rad), 0.0, 0.0,
       Math.sin(rad),  Math.cos(rad), 0.0, 0.0,
       0.0          , 0.0           , 1.0, 0.0,
       0.0          , 0.0           , 0.0, 1.0]);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount);
    
    window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step);
#canvas
{
  width: 250px;
  height: 250px;
}
<canvas id="canvas"></canvas>

Clockwise too. And I believe I have wrote that rotation matrix as per math convention. You can compare with Rotation Matrix at Wolfram MathWorld or Rotation Matrix at Wikipedia.

I would remind you that web technologies are standard.

So, if your friend is doing the rotation like this, then it is correct. And you are right, this makes sense with a vertical axis going down. We think of angles going counter-clockwise because we think of the vertical axis going up, which is mathematical convention. However, the vertical axis in screen goes down, because historical reasons.

I could have, of course, flipped the vertical axis in the projection matrix. Then the rotation would be counter-clockwise. In fact, it is more or less common that developers do some axis flipping to make it match their expectations.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I feel this is a little bit like the Coriolis effect. Does Water Swirl the Other Way in the Southern Hemisphere?. The Coriolis Effect Test: two hemispheres, one sink. Do object actually rotate the other way in that game engine? \$\endgroup\$ – Theraot Jan 28 at 9:06
  • \$\begingroup\$ In the WebGL one you are setting your own rotation matrix, so it can be whichever way you like. \$\endgroup\$ – user253751 Jan 30 at 16:54
  • \$\begingroup\$ I get why CSS and Canvas are following the 2D convention of Y+ = down, but does WebGL actually not use the same default coordinate system as OpenGL? On OpenGL the default coordinate system with a "default" viewport is Y+ = up. \$\endgroup\$ – Romen Jan 30 at 22:25
-1
\$\begingroup\$

Positive angles of rotation follow the “Right Hand Rule.” Place your right hand with palm at origin and fingers along positive x-axis. Curl your fingers toward the positive y-axis. The direction your fingers curl is a positive angle by convention.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Ok, but... Why are rotations in 2D game engines often counter-clockwise = positive systems? \$\endgroup\$ – Vaillancourt Jan 30 at 12:58
  • 2
    \$\begingroup\$ This is neither right-handed nor left-handed: I can perform the gesture you describe with either hand. To have a handedness, we need to specify the direction your thumb is pointing: into the screen or out of the screen. So 3D coordinate systems with an x, y, and Z axis come in left-handed and right-handed versions, following the left-hand and right-hand rule respectively, but this does not directly apply to 2D coordinate systems. \$\endgroup\$ – DMGregory Jan 30 at 14:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.