Получить границы не повернутого повернутого прямоугольника
У меня есть прямоугольник, к которому уже применен поворот. Я хочу получить не повернутые размеры (x, y, ширина, высота).
Вот размеры элемента в настоящее время:
Bounds at a 90 rotation: {
height 30
width 0
x 25
y 10
}
Вот размеры после поворота, равные none:
Bounds at rotation 0 {
height 0
width 30
x 10
y 25
}
Раньше я мог установить вращение на 0, а затем прочитать обновленные границы. Однако в одной из функций, которые я использовал, есть ошибка, поэтому теперь я должен сделать это вручную.
Есть ли простая формула, чтобы получить границы при вращении 0, используя информацию, которая у меня уже есть?
Обновление: объект вращается вокруг центра объекта.
ОБНОВИТЬ:
Что мне нужно, это что-то вроде функции ниже:
function getRectangleAtRotation(rect, rotation) {
var rotatedRectangle = {}
rotatedRectangle.x = Math.rotation(rect.x * rotation);
rotatedRectangle.y = Math.rotation(rect.y * rotation);
rotatedRectangle.width = Math.rotation(rect.width * rotation);
rotatedRectangle.height = Math.rotation(rect.height * rotation);
return rotatedRectangle;
}
var rectangle = {x: 25, y: 10, height: 30, width: 0 };
var rect2 = getRectangleAtRotation(rect, -90); // {x:10, y:25, height:0, width:30 }
Я нашел подобный вопрос здесь.
ОБНОВЛЕНИЕ 2
Вот код, который у меня есть. Он пытается получить центральную точку линии, а затем x, y, width и height:
var centerPoint = getCenterPoint(line);
var lineBounds = {};
var halfSize;
halfSize = Math.max(Math.abs(line.end.x-line.start.x)/2, Math.abs(line.end.y-line.start.y)/2);
lineBounds.x = centerPoint.x-halfSize;
lineBounds.y = centerPoint.y;
lineBounds.width = line.end.x;
lineBounds.height = line.end.y;
function getCenterPoint(node) {
return {
x: node.boundsInParent.x + node.boundsInParent.width/2,
y: node.boundsInParent.y + node.boundsInParent.height/2
}
}
Я знаю, что в моем примере используется прямой угол, и вы можете поменять местами x и y, но вращение может быть любым.
ОБНОВЛЕНИЕ 3
Мне нужна функция, которая возвращает необращенные границы прямоугольника. У меня уже есть границы для определенного поворота.
function getUnrotatedRectangleBounds(rect, currentRotation) {
// magic
return unrotatedRectangleBounds;
}
Ответы
Ответ 1
Я думаю, что могу справиться с вычислением размера границ без особых усилий (несколько уравнений). Вместо этого я не уверен, как бы вы хотели, чтобы x
и y
обрабатывались.
Во-первых, давайте правильно назовем вещи:
Теперь мы хотим повернуть его на некоторый угол alpha
(в радианах):
Для вычисления зеленых сторон ясно, что он состоит из двух повторяющихся прямоугольников-треугольников следующим образом:
Итак, сначала решая углы, мы знаем, что:
- сумма углов треугольника равна
PI/2
или 180 °; - вращение
alpha
; - один угол гамма равен
PI/4
или 90 °; - последний угол, бета, является
gamma - alpha
;
Теперь, зная все углы и стороны, мы можем использовать закон синусов для вычисления других сторон.
Вкратце, закон синусов говорит нам, что существует соотношение между отношением длины стороны и ее противоположного угла. Более подробная информация здесь: https://en.wikipedia.org/wiki/Law_of_sines
В нашем случае для верхнего левого треугольника (и нижнего правого) мы имеем:
Помните, что AD
- наша первоначальная высота.
Учитывая, что sin(gamma)
равен 1, и мы также знаем значение AD
, мы можем написать уравнения:
Для верхнего правого треугольника (и нижнего левого) мы имеем:
Имея все необходимые стороны, мы можем легко рассчитать ширину и высоту:
width = EA + AF
height = ED + FB
На данный момент мы можем написать довольно простой метод, который, учитывая прямоугольник и угол поворота в радианах, может возвращать новые границы:
function rotate(rectangle, alpha) {
const { width: AB, height: AD } = rectangle
const gamma = Math.PI / 4,
beta = gamma - alpha,
EA = AD * Math.sin(alpha),
ED = AD * Math.sin(beta),
FB = AB * Math.sin(alpha),
AF = AB * Math.sin(beta)
return {
width: EA + EF,
height: ED + FB
}
}
Этот метод может быть использован как:
const rect = { width: 30, height: 50 }
const rotation = Math.PI / 4.2 // this is a random value it put here
const bounds = rotate(rect, rotation)
Надеюсь, что нет опечаток...
Ответ 2
Я думаю, что мог бы найти решение, но для безопасности я предпочитаю предварительно повторить то, что у нас есть и что нам нужно, чтобы быть уверенным, что я все правильно понял. Как я сказал в комментарии, английский не является моим родным языком, и я уже написал неправильный ответ из-за моего непонимания проблемы :)
Что мы имеем
Мы знаем, что в x
и y
есть прямоугольник границ (зеленый) размера w
и h
который содержит другой прямоугольник (серый с точками), повернутый на alpha
градусы.
Мы знаем, что ось y перевернута относительно декартовой, и это делает угол, который нужно рассматривать по часовой стрелке, а не против часовой стрелки.
То, что нам нужно
Сначала нам нужно найти 4 вершины внутреннего прямоугольника (A
, B
, C
и D
) и, зная положение вершин, размер внутреннего прямоугольника (W
и H
).
В качестве второго шага нам нужно повернуть внутренний прямоугольник на 0 градусов и найти его положения X
и Y
Найти вершины
Вообще говоря, для каждой вершины мы знаем только одну координату, x или y. Другой "скользит" вдоль боковой граничной рамки по отношению к углу альфа.
Давайте начнем с A
: мы знаем Ay
, нам нужен Ax
.
Мы знаем, что Ax
лежит между x
и x + w
по отношению к углу alpha
.
Когда alpha
равна 0 °, Ax
это x + 0
. Когда alpha
равна 90 °, Ax
это x + w
. Когда альфа 45 °, Ax
это x + w/2
.
По сути, Ax
растет по отношению к греху (альфа), давая нам:
Имея Ax
, мы можем легко вычислить Cx
:
Таким же образом мы можем вычислить By
и затем Dy
:
Написание кода:
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// alpha is the rotation IN RADIANS
const vertices = (bounds, alpha) => {
const { x, y, w, h } = bounds,
A = { x: x + w * Math.sin(alpha), y },
B = { x, y: y + h * Math.sin(alpha) },
C = { x: x + w - w * Math.sin(alpha), y },
D = { x, y: y + h - h * Math.sin(alpha) }
return { A, B, C, D }
}
Нахождение сторон
Теперь, когда у нас есть все вершины, мы можем легко вычислить стороны внутреннего прямоугольника, нам нужно определить еще пару точек E
и F
для ясности объяснения:
Хорошо видно, что мы можем использовать теорему Питагорея для вычисления W
и H
с помощью:
где:
В коде:
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// vertices is a POJO with shape: { A, B, C, D }, as returned by the 'vertices' method
const sides = (bounds, vertices) => {
const { x, y, w, h } = bounds,
{ A, B, C, D } = vertices,
EA = A.x - x,
ED = D.y - y,
AF = w - EA,
FB = h - ED,
H = Math.sqrt(EA * EA + ED * ED),
W = Math.sqrt(AF * AF + FB * FB
return { h: H, w: W }
}
Нахождение положения вращающегося в противоположную сторону внутреннего прямоугольника
Прежде всего, мы должны найти углы (beta
и gamma
) диагоналей внутреннего прямоугольника.
Давайте немного увеличим масштаб и добавим несколько дополнительных букв для большей ясности:
Мы можем использовать закон синусов, чтобы получить уравнения для вычисления beta
:
Чтобы сделать некоторые расчеты, мы имеем:
Сначала нам нужно вычислить GC
, чтобы хотя бы одна сторона уравнения была полностью известна. GC
- радиус окружности, в которую вписан внутренний прямоугольник, а также половина диагонали внутреннего прямоугольника.
Имея две стороны внутреннего прямоугольника, мы снова можем использовать теорему Питагорея:
С помощью GC
мы можем решить закон синусов на beta
:
мы знаем, что sin(delta)
равен 1
Теперь beta
- это угол вершины C
относительно не повернутой оси x.
Глядя снова на это изображение, мы можем легко получить углы всех остальных вершин:
Теперь, когда у нас есть почти все, мы можем вычислить новые координаты вершины A
:
Отсюда нам нужно перевести и Ax
и Ay
потому что они связаны с центром окружности, который равен x + w/2
и y + h/2
:
Итак, написание последнего куска кода:
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// sides is a POJO with shape: { w, h }, as returned by the 'sides' method
const origin = (bounds, sides) => {
const { x, y, w, h } = bounds
const { w: W, h: H } = sides
const GC = r = Math.sqrt(W * W + H * H) / 2,
IC = H / 2,
beta = Math.asin(IC / GC),
angleA = Math.PI + beta,
Ax = x + w / 2 + r * Math.cos(angleA),
Ay = y + h / 2 + r * Math.sin(angleA)
return { x: Ax, y: Ay }
}
Собираем все вместе...
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// rotations is... the rotation of the inner rectangle IN RADIANS
const unrotate = (bounds, rotation) => {
const points = vertices(bounds, rotation),
dimensions = sides(bounds, points)
const { x, y } = origin(bounds, dimensions)
return { ...dimensions, x, y }
}
Я действительно надеюсь, что это решит вашу проблему и что нет опечаток. Это был очень, очень забавный способ провести мои выходные: D
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// alpha is the rotation IN RADIANS
const vertices = (bounds, alpha) => {
const { x, y, w, h } = bounds,
A = { x: x + w * Math.sin(alpha), y },
B = { x, y: y + h * Math.sin(alpha) },
C = { x: x + w - w * Math.sin(alpha), y },
D = { x, y: y + h - h * Math.sin(alpha) }
return { A, B, C, D }
}
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// vertices is a POJO with shape: { A, B, C, D }, as returned by the 'vertices' method
const sides = (bounds, vertices) => {
const { x, y, w, h } = bounds,
{ A, B, C, D } = vertices,
EA = A.x - x,
ED = D.y - y,
AF = w - EA,
FB = h - ED,
H = Math.sqrt(EA * EA + ED * ED),
W = Math.sqrt(AF * AF + FB * FB)
return { h: H, w: W }
}
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// sides is a POJO with shape: { w, h }, as returned by the 'sides' method
const originPoint = (bounds, sides) => {
const { x, y, w, h } = bounds
const { w: W, h: H } = sides
const GC = Math.sqrt(W * W + H * H) / 2,
r = Math.sqrt(W * W + H * H) / 2,
IC = H / 2,
beta = Math.asin(IC / GC),
angleA = Math.PI + beta,
Ax = x + w / 2 + r * Math.cos(angleA),
Ay = y + h / 2 + r * Math.sin(angleA)
return { x: Ax, y: Ay }
}
// bounds is a POJO with shape: { x, y, w, h }, update if needed
// rotations is... the rotation of the inner rectangle IN RADIANS
const unrotate = (bounds, rotation) => {
const points = vertices(bounds, rotation)
const dimensions = sides(bounds, points)
const { x, y } = originPoint(bounds, dimensions)
return { ...dimensions, x, y }
}
function shortNumber(value) {
var places = 2;
value = Math.round(value * Math.pow(10, places)) / Math.pow(10, places);
return value;
}
function getInputtedBounds() {
var rectangle = {};
rectangle.x = parseFloat(app.xInput.value);
rectangle.y = parseFloat(app.yInput.value);
rectangle.w = parseFloat(app.widthInput.value);
rectangle.h = parseFloat(app.heightInput.value);
return rectangle;
}
function rotationSliderHandler() {
var rotation = app.rotationSlider.value;
app.rotationOutput.value = rotation;
rotate(rotation);
}
function rotationInputHandler() {
var rotation = app.rotationInput.value;
app.rotationSlider.value = rotation;
app.rotationOutput.value = rotation;
rotate(rotation);
}
function unrotateButtonHandler() {
var rotation = app.rotationInput.value;
app.rotationSlider.value = 0;
app.rotationOutput.value = 0;
var outerBounds = getInputtedBounds();
var radians = Math.PI / 180 * rotation;
var unrotatedBounds = unrotate(outerBounds, radians);
updateOutput(unrotatedBounds);
}
function rotate(value) {
var outerBounds = getInputtedBounds();
var radians = Math.PI / 180 * value;
var bounds = unrotate(outerBounds, radians);
updateOutput(bounds);
}
function updateOutput(bounds) {
app.xOutput.value = shortNumber(bounds.x);
app.yOutput.value = shortNumber(bounds.y);
app.widthOutput.value = shortNumber(bounds.w);
app.heightOutput.value = shortNumber(bounds.h);
}
function onload() {
app.xInput = document.getElementById("x");
app.yInput = document.getElementById("y");
app.widthInput = document.getElementById("w");
app.heightInput = document.getElementById("h");
app.rotationInput = document.getElementById("r");
app.xOutput = document.getElementById("x2");
app.yOutput = document.getElementById("y2");
app.widthOutput = document.getElementById("w2");
app.heightOutput = document.getElementById("h2");
app.rotationOutput = document.getElementById("r2");
app.rotationSlider = document.getElementById("rotationSlider");
app.unrotateButton = document.getElementById("unrotateButton");
app.unrotateButton.addEventListener("click", unrotateButtonHandler);
app.rotationSlider.addEventListener("input", rotationSliderHandler);
app.rotationInput.addEventListener("change", rotationInputHandler);
app.rotationInput.addEventListener("input", rotationInputHandler);
app.rotationInput.addEventListener("keyup", (e) => {if (e.keyCode==13) rotationInputHandler() });
app.rotationSlider.value = app.rotationInput.value;
}
var app = {};
window.addEventListener("load", onload);
* {
font-family: sans-serif;
font-size: 12px;
outline: 0px dashed red;
}
granola {
display: flex;
align-items: top;
}
flan {
width: 90px;
display: inline-block;
}
hamburger {
display: flex:
align-items: center;
}
spagetti {
display: inline-block;
font-size: 11px;
font-weight: bold;
letter-spacing: 1.5px;
}
fish {
display: inline-block;
padding-right: 40px;
position: relative;
}
input[type=text] {
width: 50px;
}
input[type=range] {
padding-top: 10px;
width: 140px;
padding-left: 0;
margin-left: 0;
}
button {
padding-top: 3px;
padding-bottom:1px;
margin-top: 10px;
}
<granola>
<fish>
<spagetti>Bounds of Rectangle</spagetti><br><br>
<flan>x: </flan><input id="x" type="text" value="14.39"><br>
<flan>y: </flan><input id="y" type="text" value="14.39"><br>
<flan>width: </flan><input id="w" type="text" value="21.2"><br>
<flan>height: </flan><input id="h" type="text" value="21.2"><br>
<flan>rotation:</flan><input id="r" type="text" value="90"><br>
<button id="unrotateButton">Unrotate</button>
</fish>
<fish>
<spagetti>Computed Bounds</spagetti><br><br>
<flan>x: </flan><input id="x2" type="text" disabled="true"><br>
<flan>y: </flan><input id="y2" type="text"disabled="true"><br>
<flan>width: </flan><input id="w2" type="text" disabled="true"><br>
<flan>height: </flan><input id="h2" type="text" disabled="true"><br>
<flan>rotation:</flan><input id="r2" type="text" disabled="true"><br>
<input id="rotationSlider" type="range" min="-360" max="360" step="5"><br>
</fish>
</granola>
Ответ 3
Как это работает?
Расчет с использованием ширины, высоты, х и у
Радианы и углы
Используя градусы, рассчитайте радианы и рассчитайте углы sin
и cos
:
function calculateRadiansAndAngles(){
const rotation = this.value;
const dr = Math.PI / 180;
const s = Math.sin(rotation * dr);
const c = Math.cos(rotation * dr);
console.log(rotation, s, c);
}
document.getElementById("range").oninput = calculateRadiansAndAngles;
<input type="range" min="-360" max="360" id="range"/>
Ответ 4
Это основной код для вращения прямоугольника (Unrotating - это то же самое, только с отрицательным углом) вокруг его центра.
function getUnrotatedRectangleBounds(rect, currentRotation) {
//Convert deg to radians
var rot = currentRotation / 180 * Math.PI;
var hyp = Math.sqrt(rect.width * rect.width + rect.height * rect.height);
return {
x: rect.x + rect.width / 2 - hyp * Math.abs(Math.cos(rot)) / 2,
y: rect.y + rect.height / 2 - hyp * Math.abs(Math.sin(rot)) / 2,
width: hyp * Math.abs(Math.cos(rot)),
height: hyp * Math.abs(Math.sin(rot))
}
}
Вектор, начинающийся в начале координат (0,0) и заканчивающийся в (ширине, высоте), проецируется на единичный вектор для целевого угла (cos rot, sin rot) * hyp.
Абсолютные значения гарантируют, что ширина и высота являются положительными.
Координаты проекции - это ширина и высота, соответственно, нового прямоугольника.
Для значений x и y возьмите исходные значения в центре (x + rect.x) и переместите его обратно (- 1/2 * NewWidth), чтобы он центрировал новый прямоугольник.
пример
function getUnrotatedRectangleBounds(rect, currentRotation) {
//Convert deg to radians
var rot = currentRotation / 180 * Math.PI;
var hyp = Math.sqrt(rect.width * rect.width + rect.height * rect.height);
return {
x: rect.x + rect.width / 2 - hyp * Math.abs(Math.cos(rot)) / 2,
y: rect.y + rect.height / 2 - hyp * Math.abs(Math.sin(rot)) / 2,
width: hyp * Math.abs(Math.cos(rot)),
height: hyp * Math.abs(Math.sin(rot))
}
}
var originalRectangle = {x:10, y:25, width:30, height:0};
var rotatedRectangle = {x:14.39, y:14.39, width:21.2, height:21.2};
var rotation = 45;
var unrotatedRectangle = getUnrotatedRectangleBounds(rotatedRectangle, rotation);
var boundsLabel = document.getElementById("boundsLabel");
boundsLabel.innerHTML = JSON.stringify(unrotatedRectangle);
<span id="boundsLabel"></span>