Html 画布 - 在中心轴上旋转各个形状

Pau*_*son 1 html javascript canvas

正如您在演示中看到的,L 形状从屏幕顶部被裁剪掉,应该旋转 180 度并与左上角齐平。我注意到有两件事没有按预期工作,第一是当我更改ctx.translate(x, y)ctx.moveTo(x, y)并增加形状位置时,100, 100它会通过平移移动超过 100px,而 moveTo 似乎是准确的。第二个是使用负平移后ctx.stroke()对形状位置没有影响。

var shape = {};

function draw(shape) {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {

    var ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();

    var x = shape.position.x + 0.5;
    var y = shape.position.y + 0.5;
    ctx.translate(x, y);

    ctx.translate(shape.width * shape.scale/2, shape.height * shape.scale/2);
    ctx.rotate(shape.orientation * Math.PI/180);
    ctx.beginPath();
    for (var i = 0; i < shape.points.length; i++) {
      x = shape.points[i].x * shape.scale + shape.position.x + 0.5;
      y =  shape.points[i].y * shape.scale + shape.position.y + 0.5;
      ctx.lineTo(x, y);
    }

    ctx.strokeStyle = '#fff';
    ctx.stroke();
    ctx.translate(-shape.width * shape.scale/2, -shape.height * shape.scale/2);
    ctx.restore();
  }
}

// L Shape
shape.points = [];
shape.points.push({ x:0, y:0 });
shape.points.push({ x:0, y:3 });
shape.points.push({ x:2, y:3 });
shape.points.push({ x:2, y:2 });
shape.points.push({ x:1, y:2 });
shape.points.push({ x:1, y:0 });
shape.points.push({ x:0, y:0 });

shape.position = {x: 0, y: 0};
shape.scale = 30;
shape.width = 3;
shape.height = 2;
shape.orientation = 180;
draw(shape);
Run Code Online (Sandbox Code Playgroud)
#canvas {
  background: #272B34; }
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas" width="400" height="600"></canvas>
Run Code Online (Sandbox Code Playgroud)

Bli*_*n67 5

进行 2D 变换的最简单方法是通过 setTransform 函数,该函数采用 6 个数字、2 个表示 X 轴和 y 轴的方向和比例的向量以及一个表示新原点的坐标。

与其他依赖于当前状态的变换函数不同,setTransform 不受调用之前完成的任何变换的影响。

要为具有正方形长宽(x 和 y 比例相同)且 y 轴与 x 成 90 度(无倾斜)且旋转的矩阵设置变换,如下所示

// x,y the position of the orign
function setMatrix(x,y,scale,rotate){ 
    var xAx = Math.cos(rotate) * scale;  // the x axis x
    var xAy = Math.sin(rotate) * scale;  // the x axis y
    ctx.setTransform(xAx, xAy, -xAy, xAx, x, y);
}

//use
setMatrix(100,100,20,Math.PI / 4);
ctx.strokeRect(-2,-2,4,4); // draw a square centered at 100,100 
                           // scaled 20 times
                           // and rotate clockwise 45 deg
Run Code Online (Sandbox Code Playgroud)

更新

针对评论中的问题进行回复。

为什么是正弦和余弦?

您还能解释一下为什么使用 cos 和 sin 作为轴吗?

我使用Math.sinMath.cos来计算 X 轴,从而计算 Y 轴(因为 y 与 x 成 90 度),因为它比将旋转添加为单独的变换稍快一些。

当您使用任何变换函数时,除了setTransform进行矩阵乘法之外。ctx.rotate下一个片段是使用、ctx.scalectx.translate或时完成的 JS 等效最小计算ctx.transform

// mA represent the 2D context current transform
mA = [1,0,0,1,0,0];  // default transform
// mB represents the transform to apply
mB = [0,1,-1,0,0,0];  // Rotate 90 degree clockwise
// m is the resulting matrix
m[0] = mA[0] * mB[0] + mA[2] * mB[1];
m[1] = mA[1] * mB[0] + mA[3] * mB[1];
m[2] = mA[0] * mB[2] + mA[2] * mB[3];
m[3] = mA[1] * mB[2] + mA[3] * mB[3];
m[4] = mA[0] * mB[0] + mA[2] * mB[1] + mA[4];
m[5] = mA[1] * mB[0] + mA[3] * mB[1] + mA[5];
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,有 12 次乘法和 6 次加法,再加上需要内存来保存中间值,如果调用的是ctx.rotation角度的正弦和余弦,也将完成。这一切都是在 JavaScript 引擎中的本机代码中完成的,因此比在 JS 中更快,但通过在 JavaScript 中计算轴来回避矩阵乘法会减少工作量。使用setTransform只是替换当前矩阵,不需要执行矩阵乘法。

答案setMatrix函数的替代方法可以是

function setMatrix(x,y,scale,rotate){ 
    ctx.setTransform(scale,0,0,scale, x, y); // set current matrix
    ctx.rotate(rotate);  // multiply current matrix with rotation matrix
}
Run Code Online (Sandbox Code Playgroud)

它具有相同的功能,并且看起来确实更干净,尽管速度较慢,并且当您想做诸如性能非常重要的游戏之类的事情时,通常称为函数应该尽可能快。

使用该setMatrix功能

那么我该如何将其用于自定义形状(例如演示中的 L)呢?

替换您的绘图功能。顺便说一句,您应该在任何绘图函数之外获取上下文。

// assumes ctx is the 2D context in scope for this function.
function draw(shape) { 
    var i = 0;
    setMatrix(shape.position.x, shape.position.y, shape.scale, shape.orientation); // orientation is in radians
    ctx.strokeStyle = '#fff';

    ctx.beginPath();
    ctx.moveTo(shape.points[i].x, shape.points[i++].y)
    while (i < shape.points.length) {
      ctx.lineTo(shape.points[i].x, shape.points[i++].y);
    }
    ctx.closePath(); // draw line from end to start
    ctx.stroke();
}
Run Code Online (Sandbox Code Playgroud)

在您的代码中,您存储了该行,使其原点 (0,0) 位于左上角。定义形状时,您应该根据其局部坐标来定义它。这将定义旋转和缩放点,并表示位于变换原点(位置 x,y)的坐标。

因此你应该在其起源处定义你的形状

function createShape(originX, originY, points){
    var i;
    const shape = [];
    for(i = 0; i < points.length; i++){
        shape.push({
            x : points[i][0] - originX,
            y : points[i][1] - originY,
        });
    }
    return shape;
}
const shape = {};
shape.points = createShape(
     1,1.5,  // the local origin relative to the coords on next line
     [[0,0],[0,3],[2,3],[2,2],[1,2],[1,0]]  // shape coords
);
Run Code Online (Sandbox Code Playgroud)