Проецирование 3D-точки на 2D-координату экрана - PullRequest
7 голосов
/ 06 февраля 2009

На основании информации в главе 7 3D-программирование для Windows (Чарльз Петцольд) я попытался написать в качестве вспомогательной функции, которая проецирует Point3D на стандартную 2D-точку, которая содержит соответствующие экранные координаты ( х, у):

public Point Point3DToScreen2D(Point3D point3D,Viewport3D viewPort )
{
    double screenX = 0d, screenY = 0d;

    // Camera is defined in XAML as:
    //        <Viewport3D.Camera>
    //             <PerspectiveCamera Position="0,0,800" LookDirection="0,0,-1" />
    //        </Viewport3D.Camera>

    PerspectiveCamera cam = viewPort.Camera as PerspectiveCamera;

    // Translate input point using camera position
    double inputX = point3D.X - cam.Position.X;
    double inputY = point3D.Y - cam.Position.Y;
    double inputZ = point3D.Z - cam.Position.Z;

    double aspectRatio = viewPort.ActualWidth / viewPort.ActualHeight;

    // Apply projection to X and Y
    screenX = inputX / (-inputZ * Math.Tan(cam.FieldOfView / 2));

    screenY = (inputY * aspectRatio) / (-inputZ * Math.Tan(cam.FieldOfView / 2));

    // Convert to screen coordinates
    screenX = screenX * viewPort.ActualWidth;

    screenY = screenY * viewPort.ActualHeight;


    // Additional, currently unused, projection scaling factors
    /*
    double xScale = 1 / Math.Tan(Math.PI * cam.FieldOfView / 360);
    double yScale = aspectRatio * xScale;

    double zFar = cam.FarPlaneDistance;
    double zNear = cam.NearPlaneDistance;

    double zScale = zFar == Double.PositiveInfinity ? -1 : zFar / (zNear - zFar);
    double zOffset = zNear * zScale;

    */

    return new Point(screenX, screenY);
}

Однако при тестировании эта функция возвращает неправильные координаты экрана (проверяется путем сравнения 2D-координат мыши с простой 3D-формой). Из-за отсутствия опыта в 3D-программировании я не понимаю, почему.

Раздел с комментариями к блоку содержит вычисления масштабирования, которые могут быть важны, однако я не уверен, как, и книга продолжается с MatrixCamera, использующим XAML. Сначала я просто хочу, чтобы базовый расчет работал независимо от того, насколько он неэффективен по сравнению с матрицами.

Кто-нибудь может посоветовать, что нужно добавить или изменить?

Ответы [ 6 ]

4 голосов
/ 08 февраля 2009

Я создал и успешно протестировал рабочий метод с помощью 3DUtils Codeplex.

Реальная работа выполняется в методе TryWorldToViewportTransform () из 3DUtils. Этот метод не будет работать без него (см. Ссылку выше).

Очень полезная информация была также найдена в статье Эрика Синка: Auto-Zoom .

NB. Там могут быть более надежные / эффективные подходы, если так, пожалуйста, добавьте их в качестве ответа. А пока этого достаточно для моих нужд.

    /// <summary>
    /// Takes a 3D point and returns the corresponding 2D point (X,Y) within the viewport.  
    /// Requires the 3DUtils project available at http://www.codeplex.com/Wiki/View.aspx?ProjectName=3DTools
    /// </summary>
    /// <param name="point3D">A point in 3D space</param>
    /// <param name="viewPort">An instance of Viewport3D</param>
    /// <returns>The corresponding 2D point or null if it could not be calculated</returns>
    public Point? Point3DToScreen2D(Point3D point3D, Viewport3D viewPort)
    {
        bool bOK = false;

        // We need a Viewport3DVisual but we only have a Viewport3D.
        Viewport3DVisual vpv =VisualTreeHelper.GetParent(viewPort.Children[0]) as Viewport3DVisual;

        // Get the world to viewport transform matrix
        Matrix3D m = MathUtils.TryWorldToViewportTransform(vpv, out bOK);

        if (bOK)
        {
            // Transform the 3D point to 2D
            Point3D transformedPoint = m.Transform(point3D);

            Point screen2DPoint = new Point(transformedPoint.X, transformedPoint.Y);

            return new Nullable<Point>(screen2DPoint);
        }
        else
        {
            return null; 
        }
    }
2 голосов
/ 10 января 2010

Это не относится к рассматриваемому алгоритму, но может быть полезно для peple, сталкивающегося с этим вопросом (как я сделал).

В .NET 3.5 вы можете использовать Visual3D.TransformToAncestor (Визуальный предок) . Я использую это для рисования каркаса на холсте поверх моего 3D-окна:

    void CompositionTarget_Rendering(object sender, EventArgs e)
    {
        UpdateWireframe();
    }

    void UpdateWireframe()
    {
        GeometryModel3D model = cube.Content as GeometryModel3D;

        canvas.Children.Clear();

        if (model != null)
        {
            GeneralTransform3DTo2D transform = cube.TransformToAncestor(viewport);
            MeshGeometry3D geometry = model.Geometry as MeshGeometry3D;

            for (int i = 0; i < geometry.TriangleIndices.Count;)
            {
                Polygon p = new Polygon();
                p.Stroke = Brushes.Blue;
                p.StrokeThickness = 0.25;
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                canvas.Children.Add(p);
            }
        }
    }

Это также учитывает любые преобразования модели и т. Д.

Смотри также: http://blogs.msdn.com/wpf3d/archive/2009/05/13/transforming-bounds.aspx

2 голосов
/ 06 февраля 2009

Так как координаты Windows находятся на экране по оси z (x пересекают y), я бы использовал что-то вроде

screenY = viewPort.ActualHeight * (1 - screenY);

вместо

screenY = screenY * viewPort.ActualHeight;

чтобы исправить screenY для размещения Windows.

Либо вы можете использовать OpenGL. Когда вы устанавливаете диапазон просмотра x / y / z, вы можете оставить его в «родных» единицах и позволить OpenGL преобразовать в экранные координаты.

Edit: Так как ваше происхождение является центром. Я бы попробовал

screenX = viewPort.ActualWidth * (screenX + 1.0) / 2.0
screenY = viewPort.ActualHeight * (1.0 - ((screenY + 1.0) / 2.0))

Экран + 1,0 преобразуется из [-1,0, 1,0] в [0,0, 2,0]. В этот момент вы делите на 2,0, чтобы получить [0,0, 1,0] для умножения. Чтобы учесть, что Windows y переворачивается с декартовой y, вы конвертируете из [1.0, 0.0] (слева направо в нижний левый угол) в [0.0, 1.0] (сверху вниз), вычитая предыдущий экран из 1.0. Затем вы можете масштабировать до ActualHeight.

1 голос
/ 06 февраля 2009
  1. Непонятно, чего вы пытаетесь достичь с помощью коэффициента aspectRatio. Если точка находится на краю поля зрения, она должна быть на краю экрана, но если aspectRatio! = 1, это не так. Попробуйте установить aspectRatio = 1 и сделать окно квадратным. Координаты все еще неверны?

  2. ActualWidth и ActualHeight на самом деле составляют половину размера окна, поэтому screenX будет [-ActualWidth; ActualWidth], но не [0; ActualWidth]. Это то, что вы хотите?

0 голосов
/ 06 февраля 2009

Я не вижу исправления для того факта, что при рисовании с использованием Windows API источник находится в верхнем левом углу экрана. Я предполагаю, что ваша система координат

y
|
|
+------x

Кроме того, ваша система координат предполагает начало координат в центре, в зависимости от вопроса Скотта, или в левом нижнем углу?

Но API экрана Windows -

+-------x
|
|
|
y

Вам потребуется преобразование координат для перехода от классического декартового к Windows.

0 голосов
/ 06 февраля 2009

screenX и screenY должны вычисляться относительно центра экрана ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...