我觉得我可能有一个解决方案,但为了安全起见,我更希望先重复一下我们已经知道的和需要确定的内容,以确保我正确理解了一切。正如我在评论中所说,英语不是我的母语,由于我对问题的理解不足,我已经写了一个错误的答案 :)
我们有什么
![what we have](https://istack.dev59.com/73qju.webp)
我们知道在x
和y
处有一个边界矩形(绿色),大小为w
和h
,其中包含另一个矩形(灰色虚线)旋转了alpha
度。
我们知道y轴相对于笛卡尔坐标系被翻转,并且这使角度顺时针考虑而不是逆时针考虑。
我们需要什么
![what we need at first](https://istack.dev59.com/Nk0El.webp)
首先,我们需要找到内部矩形的4个顶点(A
、B
、C
和D
),并知道顶点的位置,内部矩形的大小(W
和H
)。
作为第二步,我们需要将内部矩形逆时针旋转90度,并找到它的位置 X
和 Y
。
![what we need at the end](https://istack.dev59.com/lJBMF.webp)
查找顶点
一般来说,对于每个顶点,我们只知道一个坐标,即x或y。另一个坐标沿着边界框滑动,与角度alpha有关。
让我们从A
开始:我们知道Ay
,我们需要Ax
。
我们知道Ax
在角度alpha
下位于x
和x + w
之间。
当alpha
为0°时,Ax
是x + 0
。当alpha
为90°时,Ax
是x + w
。当alpha为45°时,Ax
是x + w / 2
。
基本上,Ax
随着sin(alpha)的增长而增长,因此我们得到:
![计算 Ax](https://istack.dev59.com/7dUtc.gif)
有了 Ax
,我们可以轻松计算出 Cx
:
![计算 Cx](https://istack.dev59.com/7NBQg.gif)
同样地,我们可以计算出 By
,然后是 Dy
:
![计算 By](https://istack.dev59.com/pptG2.gif)
![计算 Dy](https://istack.dev59.com/gYOCz.gif)
编写一些代码:
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
以便于解释:
![additional points](https://istack.dev59.com/ZEkcj.webp)
很明显,我们可以使用勾股定理计算出 W
和 H
:
![compute H](https://istack.dev59.com/jLYbI.gif)
![compute W](https://istack.dev59.com/ch64J.gif)
其中:
![compute EA](https://istack.dev59.com/is1C2.gif)
![compute ED](https://istack.dev59.com/IbIqX.gif)
![compute AF](https://istack.dev59.com/HDoLu.gif)
![compute FB](https://istack.dev59.com/0lCA3.gif)
在代码中:
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
)。
![compute diagonals angles](https://istack.dev59.com/ORtug.webp)
让我们放大一点并添加一些额外的字母以增加清晰度:
![add some letters to compute beta](https://istack.dev59.com/7RyXL.webp)
我们可以使用正弦定理得到计算beta
的方程式:
![law of sines](https://istack.dev59.com/uqlIU.gif)
为了进行一些计算,我们有:
![compute GI](https://istack.dev59.com/LFJXO.gif)
![compute IC](https://istack.dev59.com/kq9ZA.gif)
![delta](https://istack.dev59.com/cJxfm.gif)
![sin delta](https://istack.dev59.com/adS8H.gif)
我们需要首先计算GC
,以便至少有一个方程式的一侧完全已知。 GC
是内部矩形嵌入的圆周的半径,也是内部矩形对角线的一半。
得到内部矩形的两条边后,我们可以再次使用勾股定理:
![compute GC](https://istack.dev59.com/qhxnL.gif)
有了GC
,我们可以解出beta
的正弦定理:
![compute beta 1](https://istack.dev59.com/Lmwal.gif)
我们知道sin(delta)
等于1。
![计算 beta 2](https://istack.dev59.com/ynx9A.gif)
![计算 beta 3](https://istack.dev59.com/qXmBf.gif)
![计算 beta 4](https://istack.dev59.com/nNQBw.gif)
![计算 beta 5](https://istack.dev59.com/VTkTB.gif)
现在,beta
是顶点 C
相对于未旋转的x轴的角度。
![C顶点角度为beta](https://istack.dev59.com/wxA4S.gif)
再次查看此图像,我们可以轻松获取所有其他顶点的角度:
![所有顶点的角度](https://istack.dev59.com/afKs5.webp)
![D的角度](https://istack.dev59.com/cP6WC.gif)
![A的角度](https://istack.dev59.com/AIlqK.gif)
![B的角度](https://istack.dev59.com/jK32h.gif)
现在我们已经几乎拥有所有元素,我们可以计算A
顶点的新坐标:
![计算 A 的位置](https://istack.dev59.com/3u00W.webp)
![计算最终A_x](https://istack.dev59.com/8EUWF.gif)
![计算最终A_y](https://istack.dev59.com/LpvXc.gif)
我们需要翻译Ax
和Ay
,因为它们与圆心有关,即x + w / 2
和y + h / 2
:
![compute translated A_x](https://istack.dev59.com/Ts6XB.gif)
![compute translated A_y](https://istack.dev59.com/B20FD.gif)
因此,编写最后一段代码:
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 }
}
集成所有内容...
const unrotate = (bounds, rotation) => {
const points = vertices(bounds, rotation),
dimensions = sides(bounds, points)
const { x, y } = origin(bounds, dimensions)
return { ...dimensions, x, y }
}
我真的希望这可以解决你的问题,也希望没有拼写错误。这是一个非常有趣的周末方式 :D
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 }
}
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 }
}
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 }
}
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>