Расчет смещений после поворота квадрата от угла

1

Я хочу вычислить 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 градусов, он будет вращаться вокруг оси, отмеченной черной точкой на изображении.

Изображение 174551

Для каждого из 4-х вращений у меня есть массив нападающих, а также фактические числа, которые я желаю. Как вы можете видеть, цифры вроде бы есть, но... Я изначально думал, что сдвиг массива - это все, что мне нужно, но его больше, чем это.

// 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 дней), но как я могу получить правильные цифры? Я ищу волшебную формулу для любого угла.

Теги:
rotation
trigonometry

2 ответа

2
Лучший ответ

Точки преобразования

Самый простой способ сделать это - создать простую матрицу вращения. Это только направление оси x и y в виде векторов, каждая из которых имеет длину, равную размеру пикселя (или единицы независимо от того, что может быть), и местоположение источника.

Чтобы повернуть точку

Сначала определите точку

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

Тогда начало и вращение

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

Из поворота мы вычисляем вектор, являющийся направлением оси х

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 находится на 90 градусов по часовой стрелке от x. Чтобы повернуть любой вектор 90deg, вы поменяете 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

Различные этапы выше могут быть уплотнены, следующая часть ответа поворачивает прямоугольник с использованием вышеуказанного метода.

Вращение прямоугольника

Следующая функция вернет 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;

Я держу дистанционное вычисление вне функции, поскольку это немного более полезно, так как вам может понадобиться дополнительная информация о повернутых точках.

DEMO

В качестве примера

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; }
<canvas id="canvas"></canvas>
  • 0
    Это демо гипнотическое!
0

Я попробовал это, не утверждая, что это эффективный или лучший способ, но я не мог соответствовать вашим ожидаемым значениям. Либо я сделал что-то неправильно, либо ваш первый набор ожидаемых значений неверен?

'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}'
  );
});
  • 0
    Благодарю. Дело в том, что я ищу формулу для любого угла, а не только для меня.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню