【问题标题】:Calculating offsets after square rotated from corner从角旋转正方形后计算偏移量
【发布时间】:2017-08-04 20:13:07
【问题描述】:

当我旋转一个正方形时,我想计算旋转点的 4 个偏移量。

旋转轴最初是正方形的左上角。当我执行旋转时,我想知道形状将在所有 4 个方向(minX、minY、maxX、maxy)上传播多远。

我目前有一般数学:

const rotation = .35  // radians = 20 degrees
const size = 50 // size of original square

const o1 = Math.round(size * Math.sin(rotation))
const o2 = Math.round(size * Math.cos(rotation))

使用这些数字,我知道如何使用它们来创建偏移数组

const offsets = [o1, 0, o2, o1 + o2]

当我将正方形从 20、110、200 和 290 度旋转时,它将围绕图像上黑点标记的轴旋转。

对于 4 个轮换中的每一个,我都有我想要的 offests 数组和实际数字。正如你所看到的那样,数字在那里,但是......我最初认为数组移位是我所需要的,但它不止于此。

// 20 degrees
console.log(offsets) // [17, 0, 47, 64] 
// The dimensions I actually need
// minX: -17,
// minY: 0
// maxX: 47
// maxY: -64

// 110 degrees
console.log(offsets) // [47, 0, -17, 30] 
// The dimensions I actually need
// minX: -64,
// minY: -17,
// maxX: 0,
// maxY: 47

// 200 degrees
console.log(offsets) // [-17, 0, -47, -64] 
// The dimensions I actually need
// minX: -47,
// minY: -64,
// maxX: 17,
// maxY: 0

// 290 degrees
console.log(offsets) // [-47, 0, 17, -30] 
// The dimensions I actually need
// minX: 0,
// minY: -47,
// maxX: 64,
// maxY: 17

如果需要,我当然可以移动阵列(比如每 90 度),但我怎样才能得到正确的数字?我正在寻找任何角度的神奇公式。

【问题讨论】:

    标签: javascript rotation trigonometry


    【解决方案1】:

    转换点

    最简单的方法是创建一个简单的旋转矩阵。这只是 x 和 y 轴作为向量的方向,每个向量的长度为像素大小(或任何可能的单位)和原点的位置。

    旋转一个点

    先定义点

    var x = ?;  // the point to rotate
    var y = ?;
    

    然后是原点和旋转

    const ox = ?; // location of origin
    const oy = ?;
    const rotation = ?; // in radians
    

    从我们计算的旋转到x轴方向的向量

    var xAxisX = Math.cos(rotation);
    var xAxisY = Math.sin(rotation);
    

    你也可以有一个比例尺

    const scale = ?;
    

    这会改变 x 和 y 轴的长度,所以 x 轴的计算是

    var xAxisX = Math.cos(rotation) * scale;
    var xAxisY = Math.sin(rotation) * scale;
    

    不,我们可以将旋转应用于该点。首先将点相对于原点移动。

    x -= ox;
    y -= oy;
    

    然后沿x轴移动点x距离

    var rx = x * xAxisX;
    var ry = x * xAxisY;
    

    然后沿 y 轴移动 y 距离。 y 轴与 x 顺时针成 90 度。要将任何向量旋转 90 度,请交换 x 和 y 并否定新的 x。因此沿y轴移动如下

    rx -= y * xAxisY;  // use x axis y for y axis x and negate
    ry += y * xAxisX;  // use x axis x for y axis y 
    

    现在这个点已经被旋转了但是仍然相对于原点,我们需要把它移回世界空间。为此,只需添加原点

    rx += ox;
    ry += oy;
    

    而 rx,ry 是围绕原点旋转的点,如果你这样做了就会缩放。

    在 2D 上下文中匹配旋转

    您可以让 2D 上下文为您做同样的事情

    ctx.setTransform(xAxisX, xAxisY, -xAxisY, xAxisX, ox, oy);
    ctx.fillRect(x,y,1,1); // draw the rotated pixel
    ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default transform
    

    或者您可以通过函数调用添加旋转

    ctx.setTransform(1, 0, 0, 1, ox, oy);
    ctx.rotate(rotation);
    // and if scale then 
    // ctx.scale(scale,scale)
    ctx.fillRect(x,y,1,1); // draw the rotated pixel
    ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default transform
    

    上面的各个步骤都可以compact了,下一部分答案用上面的方法旋转一个矩形。

    旋转矩形

    以下函数将返回 4 个旋转的角。

    // angle is the amount of rotation in radians
    // ox,oy is the origin (center of rotation)
    // x,y is the top left of the rectangle
    // w,h is the width and height of the rectangle
    // returns an array of points as arrays [[x,y],[x1,y1],...]
    // Order of returned points topLeft, topRight, bottomRight, bottomLeft
    function rotateRect(angle,ox,oy,x,y,w,h){
        const xAx = Math.cos(angle);  // x axis x
        const xAy = Math.sin(angle);  // x axis y
        x -= ox;  // move rectangle onto origin
        y -= oy; 
        return [[ // return array holding the resulting points
                x * xAx - y * xAy + ox,   // Get the top left rotated position
                x * xAy + y * xAx + oy,   // and move it back to the origin
            ], [
                (x + w) * xAx - y * xAy + ox,   // Get the top right rotated position
                (x + w) * xAy + y * xAx + oy,   
            ], [
                (x + w) * xAx - (y + h) * xAy + ox,   // Get the bottom right rotated position
                (x + w) * xAy + (y + h) * xAx + oy,   
            ], [
                x * xAx - (y + h) * xAy + ox,   // Get the bottom left rotated position
                x * xAy + (y + h) * xAx + oy,   
            ]
        ]; 
    }
    

    寻找偏移量

    使用功能

    var angle = 1;  // amount to rotate in radians
    var ox = 0;   // origin top left of rectangle
    var oy = 0; 
    const rotatedRect = rotateRect(angle,ox,oy,0,0,50,50);
    const r = rotatedRect; // alias to make following code more readable
    var leftOfOrigin  = Math.min(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
    var rightOfOrigin = Math.max(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
    var aboveOrigin   = Math.min(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
    var belowOrigin   = Math.max(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
    

    我将距离计算保留在函数之外,因为您可能需要更多关于旋转点的信息。

    演示

    举个例子

    const ctx = canvas.getContext("2d");
    canvas.width = 512;
    canvas.height = 512;
    
    
    
    // angle is the amount of rotation in radians
    // ox,oy is the origin (center of rotation)
    // x,y is the top left of the rectangle
    // w,h is the width and height of the rectangle
    // returns an array of points as arrays [[x,y],[x1,y1],...]
    // Order of returned points topLeft, topRight, bottomRight, bottomLeft
    function rotateRect(angle,ox,oy,x,y,w,h){
        const xAx = Math.cos(angle);  // x axis x
        const xAy = Math.sin(angle);  // x axis y
        x -= ox;  // move rectangle onto origin
        y -= oy; 
        return [[ // return array holding the resulting points
                x * xAx - y * xAy + ox,   // Get the top left rotated position
                x * xAy + y * xAx + oy,   // and move it back to the origin
            ], [
                (x + w) * xAx - y * xAy + ox,   // Get the top right rotated position
                (x + w) * xAy + y * xAx + oy,   
            ], [
                (x + w) * xAx - (y + h) * xAy + ox,   // Get the bottom right rotated position
                (x + w) * xAy + (y + h) * xAx + oy,   
            ], [
                x * xAx - (y + h) * xAy + ox,   // Get the bottom left rotated position
                x * xAy + (y + h) * xAx + oy,   
            ]
        ]; 
    }
    function drawRectangle(angle, ox, oy, rect){
        ctx.strokeStyle = "red";
        ctx.lineWidth = 2;
        ctx.setTransform(1,0,0,1,ox,oy);
        ctx.rotate(angle);
        ctx.strokeRect(rect.x - ox, rect.y - oy, rect.w, rect.h);
        ctx.setTransform(1,0,0,1,0,0); // restore transform to default
    }
    function drawBounds(rotatedRect){
        const r = rotatedRect; // alias to make following code more readable
        const left     = Math.min(r[0][0], r[1][0], r[2][0], r[3][0]);
        const right    = Math.max(r[0][0], r[1][0], r[2][0], r[3][0]);
        const top      = Math.min(r[0][1], r[1][1], r[2][1], r[3][1]);
        const bottom   = Math.max(r[0][1], r[1][1], r[2][1], r[3][1]);
    
        ctx.strokeStyle = "#999";
        ctx.lineWidth = 2;
        ctx.strokeRect(left, top, right - left, bottom - top);
    }
    
    function drawDistance(text,x,y,dist,direction,textOverflowDir){
        if(dist.toFixed(2) == 0) { return }
        function drawArrows(){
            ctx.strokeStyle = "blue";
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.lineTo(8,-12);
            ctx.lineTo(0,-7);
            ctx.lineTo(8,-2);
            ctx.moveTo(dist - 8, -12);
            ctx.lineTo(dist, -7);
            ctx.lineTo(dist - 8, -2);
            ctx.stroke();
        }
        
        
        ctx.setTransform(1,0,0,1,x,y);
        ctx.rotate(direction);
        const width = ctx.measureText(text).width;
        ctx.fillStyle = "blue";
        ctx.fillRect(-1, - 16, 2, 14);
        ctx.fillRect(dist -1,  - 16, 2, 14);
        if(width + 8 > dist){
            ctx.fillRect(1, -8, dist - 2, 2);
            drawArrows();
            ctx.fillStyle = "black";
            if(textOverflowDir < 0){
                ctx.fillText(text, - width / 2 - 4, - 9);
            }else{
                ctx.fillText(text,dist + width / 2 + 6, - 9);
            }
        }else{
            ctx.fillRect(-1,       - 8, (dist - width) / 2 - 4, 2);
            ctx.fillRect(dist - 1 - ((dist - width) / 2 - 4), - 8, (dist - width) / 2 - 4, 2);
            drawArrows();
            
            ctx.fillStyle = "black";
            ctx.fillText(text, dist / 2, - 9);
        }
        ctx.setTransform(1,0,0,1,0,0); //restore default transform
    }
    // set up the font
    ctx.font = "16px arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    
    var angle = 3.2;  // amount to rotate in radians
    var ox = 256;   // origin top left of rectangle
    var oy = 256; 
    const rect = {
        x : 256,
        y : 256,
        w : 164,
        h : 164,
    }
    
    
    
    
    function mainLoop(){
        ctx.clearRect(0,0,512,512);
        angle += 0.01; // slowly rotate 
        // draw origin 
        ctx.fillStyle = "#FA2";
        ctx.fillRect(ox-1,0,2,512);
        ctx.fillRect(0,oy-1,512,2);
        
        const rotatedRect = rotateRect(angle, ox, oy, rect.x, rect.y, rect.w, rect.h);
        drawBounds(rotatedRect);
        drawRectangle(angle, ox, oy, rect);
        
        const r = rotatedRect; // alias to make following code more readable
        var leftOfOrigin  = Math.min(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
        var rightOfOrigin = Math.max(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
        var aboveOrigin   = Math.min(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
        var belowOrigin   = Math.max(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
        
        // draw distances
        
        drawDistance(leftOfOrigin.toFixed(2), ox + leftOfOrigin, oy +aboveOrigin, - leftOfOrigin, 0, -1);
        drawDistance(rightOfOrigin.toFixed(2), ox, oy + aboveOrigin, rightOfOrigin, 0, 1);
        drawDistance(belowOrigin.toFixed(2), ox + leftOfOrigin, oy + belowOrigin,  belowOrigin, - Math.PI / 2, -1);
        drawDistance(aboveOrigin.toFixed(2), ox + leftOfOrigin, oy, - aboveOrigin, - Math.PI / 2, 1);
    
        requestAnimationFrame(mainLoop);
    }
    requestAnimationFrame(mainLoop);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    【讨论】:

    • 那个演示很催眠!
    【解决方案2】:

    我试了一下,并没有声称它是有效的或最好的方法,但我无法达到您的预期值。是我做错了什么还是你的第一组期望值不正确?

    'use strict';
    
    const degToRad = deg => (deg * Math.PI) / 180;
    
    const rotatePoint = (pivot, point, radians) => {
      const cosA = Math.cos(radians);
      const sinA = Math.sin(radians);
      const [x, y] = pivot;
      const difX = point[0] - x;
      const difY = point[1] - y;
    
      return [
        Math.round(((cosA * difX) - (sinA * difY)) + x),
        Math.round((sinA * difX) + (cosA * difY) + y),
      ];
    };
    
    const rotateSquare = (square, pivot, angle) => {
      const radians = degToRad(angle);
      return square.map(point => rotatePoint(pivot, point, radians));
    };
    
    const extents = (points, pivot) => points.reduce((acc, point) => {
      const [difX, difY] = point.map((value, index) => value - pivot[index]);
      return [
        Math.min(acc[0], difX),
        Math.min(acc[1], difY),
        Math.max(acc[2], difX),
        Math.max(acc[3], difY),
      ];
    }, [0, 0, 0, 0]);
    
    const createSquare = (x, y, size) => [
      [x, y],
      [x + size, y],
      [x + size, y + size],
      [x, y + size],
    ];
    
    const pivot = [0, 0];
    const square = createSquare(...pivot, 50);
    const angles = [20, 110, 200, 290];
    const rotations = angles.map(angle => rotateSquare(square, pivot, angle));
    const offsets = rotations.map(rotation => extents(rotation, pivot));
    
    const expecteds = [
      [-17, 0, 47, -64],
      [-64, -17, 0, 47],
      [-47, -64, 17, 0],
      [0, -47, 64, 17],
    ];
    
    offsets.forEach((offset, index) => {
      const actual = JSON.stringify(offset);
      const expected = JSON.stringify(expecteds[index]);
      console.log(
        `Actual:${actual}`,
        `Expected:${expected}`,
        `Same:${actual === expected}`
      );
    });

    【讨论】:

    • 谢谢。问题是我正在寻找任何角度的公式 - 不仅仅是我拥有的那些。
    猜你喜欢
    • 2014-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多