我有一个已经对其应用旋转的矩形。我想获得未旋转的尺寸(x,y,宽度,高度)。
以下是当前元素的尺寸:
Bounds at a 90 rotation: {
height 30
width 0
x 25
y 10
}
以下是将旋转设置为无后的尺寸:
Bounds at rotation 0 {
height 0
width 30
x 10
y 25
}
过去,我能够将旋转度设置为0,然后读取更新的bounds。但是,我正在使用的其中一个功能存在一个错误,因此现在我必须手动进行操作。
是否有一个简单的公式可以使用我已有的信息获取旋转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 }
我发现了类似的问题here。
更新2
这是我的代码。它将尝试获取线的中心点,然后获取x,y,宽度和高度:
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;
}
答案 0 :(得分:13)
我想我可以毫不费力地(很少的方程式)来处理边界大小的计算,但是我不确定如何处理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)
希望没有错别字...
答案 1 :(得分:11)
我认为我可能会找到解决方案,但是为了安全起见,我宁愿事先重复我们拥有的和需要确保我正确理解所有内容的内容。正如我在评论中说的那样,英语不是我的母语,并且由于我对问题的理解不够,我已经写错了答案:)
我们知道在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
。当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
:
很明显,我们可以使用Pitagorean定理来计算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>
答案 2 :(得分:5)
使用宽度,高度,x和y的计算
使用度数计算弧度并计算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"/>
我们假设矩形的原点是中心,位置为0,0
double for循环将为i
和j
创建以下值对:(-1,-1),(-1,1),(1,-1)和(1 ,1)
使用每对,我们可以计算4个平方向量之一。
(即(-1,1),i = -1, j = 1
)
const px = w*i/2; //-> 30 * -1/2 = -15
const py = h*j/2; //-> 50 * 1/2 = 25
//[-15,25]
一旦有了一个点,就可以通过包含旋转来计算该点的新位置。
const nx = (px*c) - (py*s);
const ny = (px*s) + (py*c);
一旦所有点都是根据旋转计算出来的,我们就可以重新绘制正方形。
在绘制调用之前,使用translate
将光标定位在矩形的x
和y
上。这就是为什么我能够假定矩形的中心和原点为0,0
的原因。
const canvas = document.getElementById("canvas");
const range = document.getElementById("range");
const rotat = document.getElementById("rotat");
range.addEventListener("input", function(e) {
rotat.innerText = this.value;
handleRotation(this.value);
})
const context = canvas.getContext("2d");
const container = document.getElementById("container");
const rect = {
x: 50,
y: 75,
w: 30,
h: 50
}
function handleRotation(rotation) {
const { w, h, x, y } = rect;
const dr = Math.PI / 180;
const s = Math.sin(rotation * dr);
const c = Math.cos(rotation * dr);
const points = [];
for(let i = -1; i < 2; i+=2){
for(let j = -1; j < 2; j+=2){
const px = w*i/2;
const py = h*j/2;
const nx = (px*c) - (py*s);
const ny = (px*s) + (py*c);
points.push([nx, ny]);
}
}
//console.log(points);
draw(points);
}
function draw(points) {
context.clearRect(0,0,canvas.width, canvas.height);
context.save();
context.translate(rect.x+(rect.w/2), rect.y + (rect.h/2))
context.beginPath();
context.moveTo(...points.shift());
[...points.splice(0,1), ...points.reverse()]
.forEach(p=>{
context.lineTo(...p);
})
context.fill();
context.restore();
}
window.onload = () => handleRotation(0);
div {
display: flex;
background-color: lightgrey;
padding: 0 5px;
}
div>p {
padding: 0px 10px;
}
div>input {
flex-grow: 1;
}
canvas {
border: 1px solid black;
}
<div>
<p id="rotat">0</p>
<input type="range" id="range" min="-360" max="360" value="0" step="5" />
</div>
<canvas id="canvas"></canvas>
答案 3 :(得分:1)
这是围绕矩形中心旋转的基本代码(仅在负角度下,不旋转是相同的事情。
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)开始到(width,height)结束的向量被投影到目标角度(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>