You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

旋转左上角锚定的外层矩形,修正不同尺寸内层矩形定位算法

解决外层矩形旋转后内层矩形左上角位置计算错误问题

问题场景

我有一个以左上角为锚点的外层矩形R,内部包含多个不同宽高的内层矩形。已实现R旋转α角度的算法,但需要计算每个不随R旋转的内层矩形的新左上角位置。当前的rotatePoint函数无法适配不同尺寸的内层矩形,例如旋转90度后,2×2尺寸的内层矩形左上角位置计算错误,结果与1×1尺寸的矩形完全一致。

问题原因

原函数仅直接旋转内层矩形的左上角点,但外层矩形旋转时,内层矩形的相对位置应该以外层矩形的中心为旋转基准。不同尺寸的内层矩形,其左上角到自身中心的偏移量不同,原方法未考虑这个偏移,导致大尺寸矩形的位置计算偏差。

解决方案

修改rotatePoint函数,增加内层矩形的宽高参数,先计算内层矩形的中心坐标,以外层中心为基准旋转该中心点,再根据旋转后的中心点反推出内层矩形的新左上角位置。

修改后的核心代码

1. 调整后的rotatePoint函数

static rotatePoint(x0, y0, xi, yi, innerWidth, innerHeight, degrees) {
    // 计算内层矩形的中心坐标
    const innerCenterX = xi + innerWidth / 2;
    const innerCenterY = yi + innerHeight / 2;

    // 计算内层中心相对于外层中心的偏移量
    let x = innerCenterX - x0;
    let y = innerCenterY - y0;

    const rad = degrees * (Math.PI/180);
    const cos_rad = Math.cos(rad);
    const sin_rad = Math.sin(rad);

    // 旋转内层中心坐标
    const rotatedCenterX = x0 + cos_rad * x + sin_rad * y;
    const rotatedCenterY = y0 + -sin_rad * x + cos_rad * y;

    // 从旋转后的中心反推内层矩形的新左上角位置
    const qx = rotatedCenterX - innerWidth / 2;
    const qy = rotatedCenterY - innerHeight / 2;

    return { x: qx, y: qy };
}

2. 对应修改调用逻辑的rotateUnit函数

static rotateUnit(origin, inner, direction) {
    const angle = origin.rotation + ((direction > 0) ? 45 : -45);
    const delta_angle = origin.rotation - angle;
    origin.rotation = angle;
    const box_x = origin.x + (origin.width / 2);
    const box_y = origin.y + (origin.height / 2);
    
    for (var i of inner) {
        var new_pos = Rect.rotatePoint(box_x, box_y, 
                                        i.x,
                                        i.y,
                                        i.width, // 传入内层矩形宽度
                                        i.height, // 传入内层矩形高度
                                        delta_angle);
        
        i.x = new_pos.x;
        i.y = new_pos.y;
    }
}

逻辑说明

  1. 计算内层中心:通过内层矩形左上角坐标加上自身宽高的一半,得到其中心位置,确保旋转基准点正确。
  2. 计算相对偏移:以内层中心与外层中心的偏移量作为旋转向量,保证旋转基于外层中心的相对位置。
  3. 旋转中心坐标:对偏移向量进行旋转变换,得到旋转后的内层中心绝对坐标。
  4. 反推左上角位置:从旋转后的中心坐标减去内层宽高的一半,得到适配尺寸的新左上角位置。

完整可视化代码

class Rect {
    static rotateUnit(origin, inner, direction) {
        const angle = origin.rotation + ((direction > 0) ? 45 : -45);
        const delta_angle = origin.rotation - angle;
        origin.rotation = angle;
        const box_x = origin.x + (origin.width / 2);
        const box_y = origin.y + (origin.height / 2);
        
        for (var i of inner) {
            var new_pos = Rect.rotatePoint(box_x, box_y, 
                                            i.x,
                                            i.y,
                                            i.width,
                                            i.height,
                                            delta_angle);
            
            i.x = new_pos.x;
            i.y = new_pos.y;
        }
    }
    
    static rotatePoint(x0, y0, xi, yi, innerWidth, innerHeight, degrees) {
        // 计算内层矩形的中心坐标
        const innerCenterX = xi + innerWidth / 2;
        const innerCenterY = yi + innerHeight / 2;

        // 计算内层中心相对于外层中心的偏移量
        let x = innerCenterX - x0;
        let y = innerCenterY - y0;

        const rad = degrees * (Math.PI/180);
        const cos_rad = Math.cos(rad);
        const sin_rad = Math.sin(rad);

        // 旋转内层中心坐标
        const rotatedCenterX = x0 + cos_rad * x + sin_rad * y;
        const rotatedCenterY = y0 + -sin_rad * x + cos_rad * y;

        // 从旋转后的中心反推内层矩形的新左上角位置
        const qx = rotatedCenterX - innerWidth / 2;
        const qy = rotatedCenterY - innerHeight / 2;

        return { x: qx, y: qy };
    }
    
    static printCoordinates(origin, inner, offset) {
        const ratio = 5
        var canvas = document.getElementById("canvas");
        if (canvas.getContext) {
            var ctx = canvas.getContext("2d");
            ctx.fillStyle = "rgb(0,0,0)";
            ctx.fillRect(origin.x/ratio+offset/ratio, origin.y/ratio+offset/ratio, 70*origin.width/ratio, 70*origin.height/ratio);
            ctx.fillStyle = "rgb(200,0,0)";
            for (var i of inner) {
                ctx.fillRect(i.x/ratio+offset/ratio, i.y/ratio+offset/ratio, 70*i.width/ratio, 70*i.height/ratio);
            }
        }
    }
}

/* 
* SIZE (WIDTH AND HEIGHT) CORRELATES TO 
* PIXELS BY A CONSTANT OF 1:70
*/
const rect_origin = {
    rotation: 0,
    x: 2302.492125,
    y: 1700.492125,
    width: 0.9842500000000001,
    height: 0.9842500000000001
}
const inner = [
    {
        x: 2336,
        y: 1734,
        width: 1.9685000000000001,
        height: 1.9685000000000001
    },
    {
        x: 2336,
        y: 1872,
        width: 0.9842500000000001,
        height: 0.9842500000000001
    },
    {
        x: 2405,
        y: 1872,
        width: 0.9842500000000001,
        height: 0.9842500000000001
    },
    {
        x: 2336,
        y: 1941,
        width: 0.9842500000000001,
        height: 0.9842500000000001
    },
    {
        x: 2405,
        y: 1941,
        width: 0.9842500000000001,
        height: 0.9842500000000001
    }
]

Rect.printCoordinates(rect_origin, inner, -1500);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, -1100);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, -700);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, -300);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, 100);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, 500);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, 900);
Rect.rotateUnit(rect_origin, inner, 1);
Rect.printCoordinates(rect_origin, inner, 1300);
<canvas id="canvas" width="1000" height="1000"></canvas>

内容的提问来源于stack exchange,提问作者Victor Lundgren

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:15:55