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

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

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

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

  • мин y, МАКС М, мин х макс х?

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

I при необходимости можно использовать матрицу transform toolkit в as3.

11 ответов


  • преобразование координат всех четырех углов
  • найти наименьший из всех четырех x, как 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;
}

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

Свифт:

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 (), и он вернет прямоугольник, который ограничивает вашу повернутую форму.

(правка) 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 (), я заметил, что вопрос помечен как as3, 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, но формулы могут быть легко переведены на любой язык.

подписи:

public static float getAngleFromPoint (конечная точка центра, конечная точка касания)

public static float gettwofingerdistance (поплавок firstTouchX, поплавок firstTouchY, поплавок secondTouchX, поплавок secondTouchY)

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

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

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

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

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

  4. Теперь вы сможете использовать функцию getPointFromAngle в верхнем левом и верхнем правом углу. использование возвращенного radius из шага 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


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

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


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

        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;
        }

    }