我想我可能会找到一个解决方案,但为了安全起见,我更愿意事先重复我们拥有的内容以及我们需要确保我正确理解所有内容的内容。正如我在评论中所说,英语不是我的母语,由于我对问题缺乏理解,我已经写了一个错误的答案:)
我们有什么
我们知道在x 和y 有一个大小为w 和h 的边界矩形(绿色),其中包含另一个旋转alpha 度的矩形(灰色虚线)。
我们知道 y 轴相对于笛卡尔轴翻转,这使得角度被认为是顺时针而不是逆时针。
我们需要什么
首先,我们需要找到内部矩形的 4 个顶点(A、B、C 和 D),并且知道顶点的位置,内部矩形的大小( W 和 H)。
作为第二步,我们需要将内部矩形反向旋转到 0 度,并找到它的位置 X 和 Y。
找到顶点
一般来说,对于每个顶点,我们只知道一个坐标,即 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,我们可以轻松计算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>