代码之家  ›  专栏  ›  技术社区  ›  1.21 gigawatts

获取未旋转旋转矩形的边界

  •  10
  • 1.21 gigawatts  · 技术社区  · 7 年前

    我有一个已经应用了旋转的矩形。我想得到未旋转的尺寸(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,然后读取更新的边界。然而,我使用的一个函数中有一个bug,所以现在我必须手动执行。

    有没有一个简单的公式可以使用我已有的信息来获得旋转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;
    }
    
    4 回复  |  直到 7 年前
        1
  •  16
  •   YupMa    4 年前

    我想我可以不用花太多精力(很少使用方程式)来计算边界大小,但我不确定你会喜欢什么 x y 待处理。

    首先,让我们正确地命名事物:

    enter image description here

    现在,我们想把它旋转一个角度 alpha (弧度):

    enter image description here

    要计算绿色边,很明显它由两个重复的矩形三角形组成,如下所示:

    enter image description here

    所以,首先求解角度,我们知道:

    1. 三角形的角之和为 PI ,或180°;
    2. 旋转是 阿尔法 ;
    3. 单角度伽马是 PI / 2 ,或90°;
    4. 最后一个角度beta是 gamma - alpha ;

    现在,知道了所有的角度和一条边,我们可以用正弦定律来计算其他边。

    简单回顾一下,正弦定律告诉我们,一条边的长度和它的相反角度之比是相等的。更多信息请点击此处: https://en.wikipedia.org/wiki/Law_of_sines

    在我们的例子中,对于左上角三角形(和右下角三角形),我们有:

    enter image description here

    记住这一点 AD 是我们原来的高度。

    鉴于 sin(gamma) 是1,我们也知道 公元 ,我们可以写出方程式:

    enter image description here

    enter image description here

    对于右上角三角形(和左下角三角形),我们有:

    enter image description here

    enter image description here

    enter image description here

    有了所有需要的边,我们可以轻松计算宽度和高度:

    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 + AF,
        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
  •  11
  •   1.21 gigawatts    7 年前

    我想我可能会得到一个解决方案,但为了安全起见,我更愿意事先重复我们所拥有的以及我们需要的,以确保我正确理解了所有事情。正如我在评论中所说,英语不是我的母语,由于我对这个问题缺乏理解,我已经写了一个错误的答案:)

    我们所拥有的

    what we have

    我们至少知道这一点 x y 有一个矩形(绿色)大小 w h 其中包含另一个旋转的矩形(灰色虚线) alpha 度。

    我们知道y轴相对于笛卡尔轴是翻转的,这使得角度被认为是顺时针的,而不是逆时针的。

    我们需要什么

    what we need at first

    首先,我们需要找到内部矩形的4个顶点( A , B , C D )并且,知道顶点的位置,内部矩形的大小( W H ).

    作为第二步,我们需要将内部矩形反向旋转0度,并找到它的位置 X Y .

    what we need at the end

    找到顶点

    一般来说,对于每个顶点,我们只知道一个坐标,x或y。另一个坐标相对于角度alpha沿边界框的一侧“滑动”。

    让我们从 A. :我们知道 Ay ,我们需要 Ax .

    我们知道 斧头 介于 十、 x + w 关于角度 阿尔法 .

    什么时候 阿尔法 为0°, 斧头 x + 0 什么时候 阿尔法 是90度, 斧头 x+w .当α为45°时, 斧头 x + w / 2 .

    大体上 斧头 与罪(阿尔法)相关的成长,给了我们:

    computing Ax

    斧头 ,我们可以很容易地计算 Cx :

    computing Cx

    我们可以用同样的方法计算 By 然后 Dy :

    computing By

    computing 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 为了解释清楚:

    additional points

    很明显,我们可以用皮塔戈林定理来计算 W H 与:

    compute H

    compute W

    哪里:

    compute EA

    compute ED

    compute AF

    compute FB

    代码:

    // 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 )内部矩形的对角线。

    compute diagonals angles

    让我们放大一点,添加一些额外的字母,以更清晰地显示:

    add some letters to compute beta

    我们可以用正弦定律得到要计算的方程 贝塔 :

    law of sines

    为了进行一些计算,我们有:

    compute GI

    compute IC

    delta

    sin delta

    我们需要计算 GC 首先是为了至少完全了解方程的一面。 GC 是内矩形内接的圆周半径,也是内矩形对角线的一半。

    有了内矩形的两侧,我们可以再次使用Pitagorean定理:

    compute GC

    具有 GC 我们可以解决正弦定律 贝塔 :

    compute beta 1

    我们知道 sin(delta) 是1

    compute beta 2

    compute beta 3

    compute beta 4

    compute beta 5

    现在 贝塔 是顶点的角度 C 相对于未旋转的x轴。

    C vertex angle is beta

    再看看这张图片,我们可以很容易地得到所有其他顶点的角度:

    all vertices angles

    D angle

    A angle

    B angle

    现在我们几乎拥有了一切,我们可以计算出 A. 顶点:

    compute A position

    compute final A_x

    compute final A_y

    从这里开始,我们需要把两者都翻译过来 斧头 因为它们与圆周的中心有关 x+w/2 y + h / 2 :

    compute translated A_x

    compute translated A_y

    所以,写最后一段代码:

    // 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
  •  5
  •   kockburn    7 年前

    这是怎么回事?

    使用宽度、高度、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"/>

    获得4分

    我们假设矩形的原点是中心,位置为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>
        4
  •  2
  •   Strom    7 年前

    这是矩形围绕其中心旋转的基本代码(不旋转是同一件事,只有负角度)。

    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>
    推荐文章