我想在旋转正方形时计算旋转点的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度),但我怎样才能获得正确的数字?我正在为任何角度寻找神奇的公式。
答案 0 :(得分:2)
The easiest way to do this is create a simple rotation matrix. This is just the direction of the x and y axis as vectors each with a length the size of a pixel (or unit whatever that may be) and the location of the origin.
First define the point
var x = ?; // the point to rotate
var y = ?;
Then the origin and rotation
const ox = ?; // location of origin
const oy = ?;
const rotation = ?; // in radians
From the rotation we calculate to vector that is the direction of the x axis
var xAxisX = Math.cos(rotation);
var xAxisY = Math.sin(rotation);
Optionally you could have a scale as well
const scale = ?;
that would change the length of the x and y axis so the x axis calculation is
var xAxisX = Math.cos(rotation) * scale;
var xAxisY = Math.sin(rotation) * scale;
No we can apply the rotation to the point. First move the point relative to the origin.
x -= ox;
y -= oy;
Then move the point x distance along the x axis
var rx = x * xAxisX;
var ry = x * xAxisY;
Then move y distance along the y axis. The y axis is at 90 deg clockwise from the x. To rotate any vector 90deg you swap the x and y and negate the new x. Thus moving along the y axis is as follows
rx -= y * xAxisY; // use x axis y for y axis x and negate
ry += y * xAxisX; // use x axis x for y axis y
Now the point has been rotated but is still relative to the origin, we need to move it back to the world space. To do that just add the origin
rx += ox;
ry += oy;
And rx,ry is the rotated point around the origin, and scaled if you did that.
You can get the 2D context to do the same for you
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
Or you can add the rotation via a function call
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
The various steps above can be compacted, the next part of the answer rotates a rectangle using the above method.
The following function will return the 4 rotated corners.
// 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,
]
];
}
To use the function
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;
I keep the distance calcs outside the function as that is a little more useful as you may want more information about the rotated points.
As an example
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>
答案 1 :(得分: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}`
);
});
&#13;