Вычисление координат ограничительной рамки из повернутого прямоугольника


У меня есть координаты верхней левой точки прямоугольника, а также его ширина, высота и угол поворота от 0 до 180 и от 0 до -180.

Я пытаюсь получить ограничительные координаты фактического поля вокруг прямоугольника.

Что такое простой способ вычисления координат ограничительной рамки

  • Min y, max y, min x, max x?

точка A не всегда находится на границе min y, она может быть где угодно.

I можно использовать матрицы преобразования инструментарий AS3, в случае необходимости.

11 59

11 ответов:

  • преобразование координат всех четырех углов
  • найти наименьший из всех четырех х как min_x
  • найдите самый большой из всех четырех x и назовите его max_x
  • то же самое с y's
  • ваша ограничительная коробка (min_x,min_y), (min_x,max_y), (max_x,max_y), (max_x,min_y)

АФАИК, нет никакой Королевской дороги, которая приведет вас туда намного быстрее.

Если вам интересно, как преобразовать координаты, попробуйте:

x2 = x0+(x-x0)*cos(theta)+(y-y0)*sin(theta)
y2 = y0-(x-x0)*sin(theta)+(y-y0)*cos(theta)

где (x0, y0) - это центр, вокруг которого вы вращаетесь. Возможно, вам придется возиться с этим в зависимости от ваших тригонометрических функций (ожидают ли они Градусы или радианы) смысл / знак вашей системы координат против того, как вы указываете углы и т. д.

Я понимаю, что вы просите ActionScript, но, на всякий случай, если кто-то здесь ищет ответ iOS или OS-X, это так:

+ (CGRect) boundingRectAfterRotatingRect: (CGRect) rect toAngle: (float) radians
{
    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect result = CGRectApplyAffineTransform (rect, xfrm);

    return result;
}

Если ваша ОС предлагает сделать всю тяжелую работу за вас, пусть это! :)

Swift:

func boundingRectAfterRotatingRect(rect: CGRect, toAngle radians: CGFloat) -> CGRect {
    let xfrm = CGAffineTransformMakeRotation(radians)
    return CGRectApplyAffineTransform (rect, xfrm)
}

метод, описанный MarkusQ, работает отлично, но имейте в виду, что вам не нужно преобразовывать другие три угла, если у вас уже есть точка A.

альтернативный метод, который более эффективен, заключается в том, чтобы проверить, в каком квадранте находится ваш угол поворота, а затем просто вычислить ответ напрямую. Это более эффективно, поскольку у вас есть только худший случай из двух операторов if (проверка угла), тогда как другой подход имеет худший случай из двенадцати (6 для каждого компонента, когда проверка других трех углов, чтобы увидеть, если они больше, чем текущий Макс или меньше, чем текущий мин) я думаю.

основной алгоритм, который использует не более чем ряд приложений теоремы Пифагора, показан ниже. Я обозначил угол поворота тета и выразил проверку там в градусах, поскольку это псевдокод.

ct = cos( theta );
st = sin( theta );

hct = h * ct;
wct = w * ct;
hst = h * st;
wst = w * st;

if ( theta > 0 )
{
    if ( theta < 90 degrees )
    {
        // 0 < theta < 90
        y_min = A_y;
        y_max = A_y + hct + wst;
        x_min = A_x - hst;
        x_max = A_x + wct;
    }
    else
    {
        // 90 <= theta <= 180
        y_min = A_y + hct;
        y_max = A_y + wst;
        x_min = A_x - hst + wct;
        x_max = A_x;
    }
}
else
{
    if ( theta > -90 )
    {
        // -90 < theta <= 0
        y_min = A_y + wst;
        y_max = A_y + hct;
        x_min = A_x;
        x_max = A_x + wct - hst;
    }
    else
    {
        // -180 <= theta <= -90
        y_min = A_y + wst + hct;
        y_max = A_y;
        x_min = A_x + wct;
        x_max = A_x - hst;
    }
}

этот подход предполагает, что у вас есть то, что вы говорите, т. е. точка A и значение для тета, которое лежит в диапазон [-180, 180]. Я также предположил, что тета увеличивается по часовой стрелке, так как это то, что прямоугольник, который был повернут на 30 градусов на вашей диаграмме, похоже, указывает на то, что вы используете, я не был уверен, что часть справа пыталась обозначить. Если это неправильный путь, то просто замените симметричные предложения, а также знак St-терминов.

    fitRect: function( rw,rh,radians ){
            var x1 = -rw/2,
                x2 = rw/2,
                x3 = rw/2,
                x4 = -rw/2,
                y1 = rh/2,
                y2 = rh/2,
                y3 = -rh/2,
                y4 = -rh/2;

            var x11 = x1 * Math.cos(radians) + y1 * Math.sin(radians),
                y11 = -x1 * Math.sin(radians) + y1 * Math.cos(radians),
                x21 = x2 * Math.cos(radians) + y2 * Math.sin(radians),
                y21 = -x2 * Math.sin(radians) + y2 * Math.cos(radians), 
                x31 = x3 * Math.cos(radians) + y3 * Math.sin(radians),
                y31 = -x3 * Math.sin(radians) + y3 * Math.cos(radians),
                x41 = x4 * Math.cos(radians) + y4 * Math.sin(radians),
                y41 = -x4 * Math.sin(radians) + y4 * Math.cos(radians);

            var x_min = Math.min(x11,x21,x31,x41),
                x_max = Math.max(x11,x21,x31,x41);

            var y_min = Math.min(y11,y21,y31,y41);
                y_max = Math.max(y11,y21,y31,y41);

            return [x_max-x_min,y_max-y_min];
        }

Если вы используете GDI+, вы можете создать новый GrpaphicsPath -> добавить к нему любые точки или фигуры -> применить преобразование поворота -> использовать GraphicsPath.GetBounds () и он вернет прямоугольник, который ограничивает вашу повернутую форму.

(edit) VB.Net образец

Public Shared Sub RotateImage(ByRef img As Bitmap, degrees As Integer)
' http://stackoverflow.com/questions/622140/calculate-bounding-box-coordinates-from-a-rotated-rectangle-picture-inside#680877
'
Using gp As New GraphicsPath
  gp.AddRectangle(New Rectangle(0, 0, img.Width, img.Height))

  Dim translateMatrix As New Matrix
  translateMatrix.RotateAt(degrees, New PointF(img.Width \ 2, img.Height \ 2))
  gp.Transform(translateMatrix)

  Dim gpb = gp.GetBounds

  Dim newwidth = CInt(gpb.Width)
  Dim newheight = CInt(gpb.Height)

  ' http://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations
  '
  Dim rotatedBmp As New Bitmap(newwidth, newheight)

  rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

  Using g As Graphics = Graphics.FromImage(rotatedBmp)
    g.Clear(Color.White)
    translateMatrix = New Matrix
    translateMatrix.Translate(newwidth \ 2, newheight \ 2)
    translateMatrix.Rotate(degrees)
    translateMatrix.Translate(-img.Width \ 2, -img.Height \ 2)
    g.Transform = translateMatrix
    g.DrawImage(img, New PointF(0, 0))
  End Using
  img.Dispose()
  img = rotatedBmp
End Using

End Sub

хотя гуру кода заявил метод GetBounds (), я заметил, что вопрос помечен как 3, flex, поэтому вот фрагмент AS3, который иллюстрирует эту идею.

var box:Shape = new Shape();
box.graphics.beginFill(0,.5);
box.graphics.drawRect(0,0,100,50);
box.graphics.endFill();
box.rotation = 20;
box.x = box.y = 100;
addChild(box);

var bounds:Rectangle = box.getBounds(this);

var boundingBox:Shape = new Shape();
boundingBox.graphics.lineStyle(1);
boundingBox.graphics.drawRect(bounds.x,bounds.y,bounds.width,bounds.height);
addChild(boundingBox);

Я заметил, что есть два метода, которые, кажется, делают то же самое: getBounds() и getRect()

/**
     * Applies the given transformation matrix to the rectangle and returns
     * a new bounding box to the transformed rectangle.
     */
    public static function getBoundsAfterTransformation(bounds:Rectangle, m:Matrix):Rectangle {
        if (m == null) return bounds;

        var topLeft:Point = m.transformPoint(bounds.topLeft);
        var topRight:Point = m.transformPoint(new Point(bounds.right, bounds.top));
        var bottomRight:Point = m.transformPoint(bounds.bottomRight);
        var bottomLeft:Point = m.transformPoint(new Point(bounds.left, bounds.bottom));

        var left:Number = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var top:Number = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        var right:Number = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var bottom:Number = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        return new Rectangle(left, top, right - left, bottom - top);
    }

примените матрицу вращения к угловым точкам. Затем используйте минимум / максимум соответственно полученных координат x, y для определения новой ограничительной рамки.

вот три функции из моей библиотеки с открытым исходным кодом. Функции полностью протестированы на Java, но формулы могут быть легко переведены на любой язык.

подписи:

публичный статический поплавок getAngleFromPoint(финальная точка точка, окончательная точка метки)

публичный статический getTwoFingerDistance поплавка(поплавок firstTouchX, поплавок firstTouchY, secondTouchX поплавок, поплавок secondTouchY)

Point getPointFromAngle(final double угол, окончательный двойной радиус)

Это решение предполагает, что плотность пикселей равномерно. До поворота объекта выполните следующие действия:

  1. используйте getAngleFromPoint для вычисления угла от центра к верхнему правому углу (допустим, это возвращает 20 градусов), что означает, что левый угол upp составляет -20 градусов или 340 градусов.

  2. используйте getTwoFingerDistance для возврата диагонального расстояния между центральной точкой и верхний правый угол (это расстояние должно быть очевидно одинаковым для всех углов, это расстояние будет использоваться в следующем расчете).

  3. Теперь предположим, что мы повернуть объект по часовой стрелке на 30 градусов. Теперь мы знаем, что верхний правый угол должен быть на 50 градусов, а верхний левый угол-на 10 градусов.

  4. Теперь вы можете использовать функцию getPointFromAngle в верхнем левом и верхнем правом углу. использование возвращенного радиуса из шага 2. Позиция X, умноженная на 2 из верхнего правого угла, должна дать вам новую ширину, а позиция Y, умноженная на 2 из верхнего левого угла, должна дать новую высоту.

эти выше 4 шага должны быть помещены в условия, основанные на том, как далеко вы повернули свой объект, иначе вы можете вернуть высоту как ширину и ширину как высоту.

оголите в разуме функции угла выражены в факторах 0-1 вместо 0-360 (просто умножьте или разделите на 360, где это уместно):

/ / получает угол из двух точек, выраженный как коэффициент 0 -1 (0 - 0/360, 0.25-90 градусов и т. д.)

public float getAngleFromPoint(final Point centerPoint, final Point touchPoint) {

    float returnVal = 0;

    //+0 - 0.5
    if(touchPoint.x > centerPoint.x) {

        returnVal = (float) (Math.atan2((touchPoint.x - centerPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI);

    }
    //+0.5
    else if(touchPoint.x < centerPoint.x) {

        returnVal = (float) (1 - (Math.atan2((centerPoint.x - touchPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI));

    }//End if(touchPoint.x > centerPoint.x)

    return returnVal;

}

/ / измеряет диагональное расстояние между двумя точками

public float getTwoFingerDistance(final float firstTouchX, final float firstTouchY, final float secondTouchX, final float secondTouchY) {

    float pinchDistanceX = 0;
    float pinchDistanceY = 0;

    if(firstTouchX > secondTouchX) {

        pinchDistanceX = Math.abs(secondTouchX - firstTouchX);

    }
    else if(firstTouchX < secondTouchX) {

        pinchDistanceX = Math.abs(firstTouchX - secondTouchX);

    }//End if(firstTouchX > secondTouchX)

    if(firstTouchY > secondTouchY) {

        pinchDistanceY = Math.abs(secondTouchY - firstTouchY);

    }
    else if(firstTouchY < secondTouchY) {

        pinchDistanceY = Math.abs(firstTouchY - secondTouchY);

    }//End if(firstTouchY > secondTouchY)

    if(pinchDistanceX == 0 && pinchDistanceY == 0) {

        return 0;

    }
    else {

        pinchDistanceX = (pinchDistanceX * pinchDistanceX);
        pinchDistanceY = (pinchDistanceY * pinchDistanceY);
        return (float) Math.abs(Math.sqrt(pinchDistanceX + pinchDistanceY));

    }//End if(pinchDistanceX == 0 && pinchDistanceY == 0)

}

/ / получить координаты XY от угла заданного радиуса (угол выражается в коэффициенте 0-1 0, равном 0/360 градусов и 0,75, равном 270 и т. д.)

public Point getPointFromAngle(final double angle, final double radius) {

    final Point coords = new Point();
    coords.x = (int) (radius * Math.sin((angle) * 2 * Math.PI));
    coords.y = (int) -(radius * Math.cos((angle) * 2 * Math.PI));

    return coords;

}

эти фрагменты кода из моего открытого источника библиотеки:https://bitbucket.org/warwick/hgdialrepo и https://bitbucket.org/warwick/hacergestov2. Один из них-библиотека жестов для Android, а другой-управление набором для Android. Существует также реализация OpenGLES 2.0 управления набором по адресу:https://bitbucket.org/warwick/hggldial

Я не уверен, что понимаю, но составная матрица преобразования даст вам новые координаты для всех соответствующих точек. Если вы думаете, что прямоугольник может пролиться на воображаемую область после преобразования, примените путь отсечения.

Если вы не знакомы с точным определением матриц, посмотрите здесь.

Я использовал область для первого поворота прямоугольника, а затем использовать эту повернутую область, чтобы обнаружить этот прямоугольник

        r = new Rectangle(new Point(100, 200), new Size(200, 200));         
        Color BorderColor = Color.WhiteSmoke;
        Color FillColor = Color.FromArgb(66, 85, 67);
        int angle = 13;
        Point pt = new Point(r.X, r.Y);
        PointF rectPt = new PointF(r.Left + (r.Width / 2),
                               r.Top + (r.Height / 2));
       //declare myRegion globally 
        myRegion = new Region(r);

        // Create a transform matrix and set it to have a 13 degree

        // rotation.
        Matrix transformMatrix = new Matrix();
        transformMatrix.RotateAt(angle, pt);

        // Apply the transform to the region.
        myRegion.Transform(transformMatrix);
        g.FillRegion(Brushes.Green, myRegion);
        g.ResetTransform();

теперь к обнаружению этого прямоугольника

        private void panel_MouseMove(object sender, MouseEventArgs e)
    {


        Point point = e.Location;
        if (myRegion.IsVisible(point, _graphics))
        {
            // The point is in the region. Use an opaque brush.
            this.Cursor = Cursors.Hand;
        }
        else {
            this.Cursor = Cursors.Cross;
        }

    }