Click here to Skip to main content
15,885,771 members
Articles / Desktop Programming / WPF

D3dScenePresenter - How to present and manipulate a 3D scene using MDX

Rate me:
Please Sign up or sign in to vote.
4.57/5 (14 votes)
15 Feb 2013CPOL14 min read 45.1K   1.6K   25  
This article shows how we can present a 3D scene and, perform common operations (zoom, rotate, move, zoom to specific region, adjust the camera to view the whole of the scene, and pick a 3D shape on a specific region on the rendered surface) on it, using Managed DirectX.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MdxScene.Shapes;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MdxScene.Cameras
{
    public abstract class D3dCamera
    {
        protected D3dCamera()
        {
            CoordinateSystem = CameraCoordinateSystem.RightHanded;

            PositionX = 0;
            PositionY = 0;
            PositionZ = 1000;

            TargetX = TargetY = TargetZ = 0;

            UpX = 0;
            UpY = 1;
            UpZ = 0;

            ZNearPlane = 1;
            ZFarPlane = 10000;

            MaxMoveTriesForAdjustmentAlgorithm = 100;
        }

        public virtual void Render(Device d3dDevice)
        {
            if (null == d3dDevice)
            {
                return;
            }

            CurrentViewMatrix = ViewMatrix;
            CurrentProjectionMatrix = ProjectionMatrix;

            lock (d3dDevice)
            {
                d3dDevice.Transform.View = CurrentViewMatrix;
                d3dDevice.Transform.Projection = CurrentProjectionMatrix;
            }
        }

        #region Properties

        #region CoordinateSystem
        private CameraCoordinateSystem _coordinateSystem;
        public CameraCoordinateSystem CoordinateSystem
        {
            get { return _coordinateSystem; }
            set
            {
                lock (this)
                {
                    _coordinateSystem = value;
                    _isViewMatrixValid = false;
                    _isProjectionMatrixValid = false;
                }
            }
        }
        #endregion

        #region PositionX
        private float _positionX;
        public float PositionX
        {
            get { return _positionX; }
            set
            {
                lock (this)
                {
                    _positionX = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region PositionY
        private float _positionY;
        public float PositionY
        {
            get { return _positionY; }
            set
            {
                lock (this)
                {
                    _positionY = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region PositionZ
        private float _positionZ;
        public float PositionZ
        {
            get { return _positionZ; }
            set
            {
                lock (this)
                {
                    _positionZ = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region TargetX
        private float _targetX;
        public float TargetX
        {
            get { return _targetX; }
            set
            {
                lock (this)
                {
                    _targetX = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region TargetY
        private float _targetY;
        public float TargetY
        {
            get { return _targetY; }
            set
            {
                lock (this)
                {
                    _targetY = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region TargetZ
        private float _targetZ;
        public float TargetZ
        {
            get { return _targetZ; }
            set
            {
                lock (this)
                {
                    _targetZ = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region UpX
        private float _upX;
        public float UpX
        {
            get { return _upX; }
            set
            {
                lock (this)
                {
                    _upX = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region UpY
        private float _upY;
        public float UpY
        {
            get { return _upY; }
            set
            {
                lock (this)
                {
                    _upY = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region UpZ
        private float _upZ;
        public float UpZ
        {
            get { return _upZ; }
            set
            {
                lock (this)
                {
                    _upZ = value;
                    _isViewMatrixValid = false;
                }
            }
        }
        #endregion

        #region ZNearPlane
        private float _zNearPlane;
        public float ZNearPlane
        {
            get { return _zNearPlane; }
            set
            {
                lock (this)
                {
                    _zNearPlane = value;
                    _isProjectionMatrixValid = false;
                }
            }
        }
        #endregion

        #region ZFarPlane
        private float _zFarPlane;
        public float ZFarPlane
        {
            get { return _zFarPlane; }
            set
            {
                lock (this)
                {
                    _zFarPlane = value;
                    _isProjectionMatrixValid = false;
                }
            }
        }
        #endregion

        #region ViewMatrix
        private Matrix _viewMatrix;
        public Matrix ViewMatrix
        {
            get
            {
                lock (this)
                {
                    if (!_isViewMatrixValid)
                    {
                        _viewMatrix = GetViewMatrix();
                        _isViewMatrixValid = true;
                    }
                }

                return _viewMatrix;
            }
        }

        protected bool _isViewMatrixValid;
        #endregion

        #region ProjectionMatrix
        private Matrix _projectionMatrix;
        public Matrix ProjectionMatrix
        {
            get
            {
                lock (this)
                {
                    if (!_isProjectionMatrixValid)
                    {
                        _projectionMatrix = GetProjectionMatrix();
                        _isProjectionMatrixValid = true;
                    }
                }

                return _projectionMatrix;
            }
        }

        protected bool _isProjectionMatrixValid;
        #endregion

        public Matrix CurrentViewMatrix { get; protected set; }
        public Matrix CurrentProjectionMatrix { get; protected set; }

        public int MaxMoveTriesForAdjustmentAlgorithm { get; set; }

        #endregion

        public virtual Matrix GetViewMatrix()
        {
            Matrix res;

            if (CameraCoordinateSystem.LeftHanded == CoordinateSystem)
            {
                res = Matrix.LookAtLH(new Vector3(PositionX, PositionY, PositionZ),
                                      new Vector3(TargetX, TargetY, TargetZ),
                                      new Vector3(UpX, UpY, UpZ));
            }
            else
            {
                // It's a right-handed coordinate system.

                res = Matrix.LookAtRH(new Vector3(PositionX, PositionY, PositionZ),
                                      new Vector3(TargetX, TargetY, TargetZ),
                                      new Vector3(UpX, UpY, UpZ));
            }

            return res;
        }

        public abstract Matrix GetProjectionMatrix();

        public abstract void Zoom(float scalingFactorX, float scalingFactorY);

        public virtual void RelativeZMove(float relativeDistance)
        {
            float projectionRegionDepth = ZFarPlane - ZNearPlane;
            Vector3 translationVector = GetRelativeZAxisDirection();
            translationVector.Multiply(projectionRegionDepth*relativeDistance);
            Matrix translation = Matrix.Translation(translationVector);

            TransformPosition(translation);
            TransformTarget(translation);
        }

        public abstract void RelativeXyMove(float relativeDistanceX, float relativeDistanceY);

        public virtual void RelativeMove(float relativeDistanceX, float relativeDistanceY, float relativeDistanceZ)
        {
            RelativeXyMove(relativeDistanceX, relativeDistanceY);
            RelativeZMove(relativeDistanceZ);
        }

        public void RelativeRotateX(float relativeArcLength,
                                    CameraTransformationCenterPosition centerPosition,
                                    CameraRotationDirection rotationDirection)
        {
            const float pi = (float) Math.PI;

            float rotation = relativeArcLength*(pi*2);

            if ((CameraCoordinateSystem.LeftHanded == CoordinateSystem &&
                 CameraRotationDirection.CounterClockwise == rotationDirection) ||
                (CameraCoordinateSystem.RightHanded == CoordinateSystem &&
                 CameraRotationDirection.Clockwise == rotationDirection))
            {
                rotation *= -1f;
            }

            Vector3 relativeXAxisDirection = GetRelativeXAxisDirection();
            Matrix rotationTransformation = Matrix.RotationAxis(relativeXAxisDirection, rotation);
            Rotate(rotationTransformation, centerPosition);
        }

        public void RelativeRotateY(float relativeArcLength,
                                    CameraTransformationCenterPosition centerPosition,
                                    CameraRotationDirection rotationDirection)
        {
            const float pi = (float) Math.PI;

            float rotation = relativeArcLength*(pi*2);

            if ((CameraCoordinateSystem.LeftHanded == CoordinateSystem &&
                 CameraRotationDirection.CounterClockwise == rotationDirection) ||
                (CameraCoordinateSystem.RightHanded == CoordinateSystem &&
                 CameraRotationDirection.Clockwise == rotationDirection))
            {
                rotation *= -1f;
            }

            Vector3 relativeYAxisDirection = GetRelativeYAxisDirection();
            Matrix rotationTransformation = Matrix.RotationAxis(relativeYAxisDirection, rotation);
            Rotate(rotationTransformation, centerPosition);
        }

        public void RelativeRotateZ(float relativeArcLength,
                                    CameraTransformationCenterPosition centerPosition,
                                    CameraRotationDirection rotationDirection)
        {
            const float pi = (float) Math.PI;

            float rotation = relativeArcLength*(pi*2);

            if ((CameraRotationDirection.Clockwise == rotationDirection &&
                 CameraTransformationCenterPosition.CameraPosition == centerPosition) ||
                (CameraRotationDirection.CounterClockwise == rotationDirection &&
                 CameraTransformationCenterPosition.TargetPosition == centerPosition))
            {
                if (CameraCoordinateSystem.RightHanded == CoordinateSystem)
                {
                    rotation *= -1f;
                }
            }
            else
            {
                if (CameraCoordinateSystem.LeftHanded == CoordinateSystem)
                {
                    rotation *= -1f;
                }
            }

            Vector3 relativeZAxisDirection = GetRelativeZAxisDirection();
            Matrix rotationTransformation = Matrix.RotationAxis(relativeZAxisDirection, rotation);
            Rotate(rotationTransformation, centerPosition);
        }

        public void Rotate(Matrix rotationTransformation, CameraTransformationCenterPosition centerPosition)
        {
            Matrix translationBefore;
            Matrix translationAfter;
            Matrix transformation;

            if (CameraTransformationCenterPosition.TargetPosition == centerPosition)
            {
                translationBefore = Matrix.Translation(-TargetX, -TargetY, -TargetZ);
                translationAfter = Matrix.Translation(TargetX, TargetY, TargetZ);
                transformation = translationBefore*rotationTransformation*translationAfter;

                TransformPosition(transformation);
            }
            else
            {
                // The transform center is the camera's position.

                translationBefore = Matrix.Translation(-PositionX, -PositionY, -PositionZ);
                translationAfter = Matrix.Translation(PositionX, PositionY, PositionZ);
                transformation = translationBefore*rotationTransformation*translationAfter;

                TransformTarget(transformation);
            }

            TransformUp(rotationTransformation);
        }

        public void TransformPosition(Matrix transformation)
        {
            Vector3 cameraPosition = new Vector3(PositionX, PositionY, PositionZ);
            cameraPosition.TransformCoordinate(transformation);
            PositionX = cameraPosition.X;
            PositionY = cameraPosition.Y;
            PositionZ = cameraPosition.Z;
        }

        public void TransformTarget(Matrix transformation)
        {
            Vector3 targetPosition = new Vector3(TargetX, TargetY, TargetZ);
            targetPosition.TransformCoordinate(transformation);
            TargetX = targetPosition.X;
            TargetY = targetPosition.Y;
            TargetZ = targetPosition.Z;
        }

        public void TransformUp(Matrix transformation)
        {
            Vector3 upDirection = new Vector3(UpX, UpY, UpZ);
            upDirection.TransformCoordinate(transformation);
            UpX = upDirection.X;
            UpY = upDirection.Y;
            UpZ = upDirection.Z;
        }

        public Vector3 GetRelativeXAxisDirection()
        {
            Vector3 relativeZAxisDirection = GetRelativeZAxisDirection();
            Vector3 relativeXAxisDirection = GetRelativeYAxisDirection();
            float rotationRadians = (CameraCoordinateSystem.RightHanded == CoordinateSystem)
                                        ? (float) Math.PI/2
                                        : (float) -Math.PI/2;
            relativeXAxisDirection.TransformNormal(Matrix.RotationAxis(relativeZAxisDirection, rotationRadians));

            return relativeXAxisDirection;
        }

        public Vector3 GetRelativeYAxisDirection()
        {
            Vector3 relativeYAxisDirection = new Vector3(UpX, UpY, UpZ);
            relativeYAxisDirection.Normalize();

            return relativeYAxisDirection;
        }

        public Vector3 GetRelativeZAxisDirection()
        {
            Vector3 cameraPosition = new Vector3(PositionX, PositionY, PositionZ);
            Vector3 targetPosition = new Vector3(TargetX, TargetY, TargetZ);
            Vector3 relativeZAxisDirection = targetPosition - cameraPosition;
            relativeZAxisDirection.Normalize();

            return relativeZAxisDirection;
        }

        public void NormalizeUpVector()
        {
            Vector3 upDirection = new Vector3(UpX, UpY, UpZ);
            upDirection.Normalize();
            UpX = upDirection.X;
            UpY = upDirection.Y;
            UpZ = upDirection.Z;
        }

        public void ZoomToProjectionRegion(float projectionRegionLeft, float projectionRegionTop,
            float projectionRegionRight, float projectionRegionBottom, bool keepAspectRatio = false)
        {
            // Move the camera to look at the center of the projection region.
            float centerX = (projectionRegionRight + projectionRegionLeft) / 2;
            float centerY = (projectionRegionBottom + projectionRegionTop) / 2;

            // Since the projection coordinates are between -1 and 1 (and we are in  the prjection  coordinates),
            // the relative distance is equal to the actual distance...
            RelativeXyMove(centerX, centerY);

            float projectionRegionWidth = Math.Abs(projectionRegionRight - projectionRegionLeft);
            float projectionRegionHeight = Math.Abs(projectionRegionTop - projectionRegionBottom);

            if (keepAspectRatio)
            {
                float scalingFactor = Math.Max(projectionRegionWidth, projectionRegionHeight) / 2;
                Zoom(scalingFactor, scalingFactor);
            }
            else
            {
                Zoom(projectionRegionWidth / 2, projectionRegionHeight / 2);
            }
        }

        public virtual void AdjustView(IEnumerable<D3dShape> shapes, bool keepAspectRatio = false)
        {
            if (null == shapes)
            {
                return;
            }

            D3dShape[] shapesArray = shapes.ToArray();

            float projectionRegionLeft;
            float projectionRegionTop;
            float projectionRegionFront;
            float projectionRegionRight;
            float projectionRegionBottom;
            float projectionRegionBack;

            int moveTriesCount = 0;
            float centerX;
            float centerY;
            const float epsilon = 0.000000000001f;
            do
            {
                // Get the shapes' projection region.
                GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
                                          out projectionRegionFront,
                                          out projectionRegionRight, out projectionRegionBottom,
                                          out projectionRegionBack);

                // Move the camera to look at the center of the projection region.
                centerX = (projectionRegionRight + projectionRegionLeft)/2;
                centerY = (projectionRegionBottom + projectionRegionTop)/2;

                // Since the projection coordinates are between -1 and 1 (and we are in  the prjection  coordinates),
                // the relative distance is equal to the actual distance...
                RelativeXyMove(centerX, centerY);

                // Get the shapes' projection region, after the move.
                GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
                                          out projectionRegionFront,
                                          out projectionRegionRight, out projectionRegionBottom,
                                          out projectionRegionBack);

                if (0 > projectionRegionFront || 1 < projectionRegionFront)
                {
                    float zMove = projectionRegionFront - ((projectionRegionBack - projectionRegionFront)*0.1f);
                    RelativeZMove(zMove);

                    // Get the shapes' projection region, after the move.
                    GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
                                              out projectionRegionFront,
                                              out projectionRegionRight, out projectionRegionBottom,
                                              out projectionRegionBack);

                }

                if (1 < projectionRegionBack)
                {
                    ZFarPlane = ZNearPlane + (ZFarPlane - ZNearPlane)*(projectionRegionBack + 0.2f);

                    // Get the shapes' projection region, after the far-plane change.
                    GetShapesProjectionRegion(shapesArray, out projectionRegionLeft, out projectionRegionTop,
                                              out projectionRegionFront,
                                              out projectionRegionRight, out projectionRegionBottom,
                                              out projectionRegionBack);
                }

                // Zoom to contain the projection region.
                ZoomToProjectionRegion(projectionRegionLeft, projectionRegionTop,
                                       projectionRegionRight, projectionRegionBottom, keepAspectRatio);

                ++moveTriesCount;

            } while (moveTriesCount < MaxMoveTriesForAdjustmentAlgorithm &&
                     (Math.Abs(0 - centerX) > epsilon || Math.Abs(0 - centerY) > epsilon));           
        }

        #region GetShapesProjectionBoundingBox

        public BoundingBox GetShapesProjectionBoundingBox(IEnumerable<D3dShape> shapes)
        {
            float projectionRegionLeft;
            float projectionRegionTop;
            float projectionRegionFront;
            float projectionRegionRight;
            float projectionRegionBottom;
            float projectionRegionBack;
            GetShapesProjectionRegion(shapes, out projectionRegionLeft, out projectionRegionTop, out projectionRegionFront,
                                      out projectionRegionRight, out projectionRegionBottom, out projectionRegionBack);

            return new BoundingBox(new Vector3(projectionRegionLeft, projectionRegionBottom, projectionRegionFront),
                                   new Vector3(projectionRegionRight, projectionRegionTop, projectionRegionBack));
        }

        protected virtual void GetShapesProjectionRegion(IEnumerable<D3dShape> shapes, out float projectionRegionLeft, out float projectionRegionTop,
                                      out float projectionRegionFront, out float projectionRegionRight,
                                      out float projectionRegionBottom, out float projectionRegionBack)
        {
            // Initialize the values to a full projection region boundaries.
            float minX = -1;
            float minY = -1;
            float minZ = 0;
            float maxX = 1;
            float maxY = 1;
            float maxZ = 1;

            if (null != shapes)
            {
                D3dShape[] shapesArray = shapes.ToArray();

                if (shapesArray.Any())
                {
                    D3dShape firstShape = shapesArray.First();
                    GetShapeProjectionRegion(firstShape, out minX, out maxY, out minZ, out maxX,
                                             out minY, out maxZ);

                    Parallel.ForEach(shapesArray, s =>
                                                      {
                                                          float currMinX;
                                                          float currMinY;
                                                          float currMinZ;
                                                          float currMaxX;
                                                          float currMaxY;
                                                          float currMaxZ;

                                                          GetShapeProjectionRegion(s, out currMinX, out currMaxY, out currMinZ, out currMaxX,
                                                                                   out currMinY, out currMaxZ);

                                                          lock (this)
                                                          {
                                                              minX = Math.Min(minX, currMinX);
                                                              minY = Math.Min(minY, currMinY);
                                                              minZ = Math.Min(minZ, currMinZ);
                                                              maxX = Math.Max(maxX, currMaxX);
                                                              maxY = Math.Max(maxY, currMaxY);
                                                              maxZ = Math.Max(maxZ, currMaxZ);
                                                          }
                                                      });
                }
            }

            projectionRegionLeft = minX;
            projectionRegionTop = maxY;
            projectionRegionFront = minZ;
            projectionRegionRight = maxX;
            projectionRegionBottom = minY;
            projectionRegionBack = maxZ;
        }

        public BoundingBox GetShapeProjectionBoundingBox(D3dShape shape)
        {
            float projectionRegionLeft;
            float projectionRegionTop;
            float projectionRegionFront;
            float projectionRegionRight;
            float projectionRegionBottom;
            float projectionRegionBack;
            GetShapeProjectionRegion(shape, out projectionRegionLeft, out projectionRegionTop, out projectionRegionFront,
                                      out projectionRegionRight, out projectionRegionBottom, out projectionRegionBack);

            return new BoundingBox(new Vector3(projectionRegionLeft, projectionRegionBottom, projectionRegionFront),
                                   new Vector3(projectionRegionRight, projectionRegionTop, projectionRegionBack));
        }

        protected virtual void GetShapeProjectionRegion(D3dShape shape, out float projectionRegionLeft, out float projectionRegionTop,
                                      out float projectionRegionFront, out float projectionRegionRight,
                                      out float projectionRegionBottom, out float projectionRegionBack)
        {
            // Initialize the values to a full projection region boundaries.
            float minX = -1;
            float minY = -1;
            float minZ = 0;
            float maxX = 1;
            float maxY = 1;
            float maxZ = 1;

            if (null != shape)
            {
                BoundingBox boundingBox = shape.GetBoundingBoxBeforeTransformation();

                boundingBox.TransformCoordinates(shape.GetActualWorldMatrix() * ViewMatrix);
                float projectionRegionDepth = ZFarPlane - ZNearPlane;

                minZ = boundingBox.GetMinimalZ();
                maxZ = boundingBox.GetMaximalZ();

                if (CameraCoordinateSystem.RightHanded == CoordinateSystem)
                {
                    minZ *= -1;
                    maxZ *= -1;
                }

                minZ -= ZNearPlane;
                maxZ -= ZNearPlane;

                minZ /= projectionRegionDepth;
                maxZ /= projectionRegionDepth;

                boundingBox.TransformCoordinates(ProjectionMatrix);

                minX = boundingBox.GetMinimalX();
                minY = boundingBox.GetMinimalY();
                maxX = boundingBox.GetMaximalX();
                maxY = boundingBox.GetMaximalY();
            }

            projectionRegionLeft = minX;
            projectionRegionTop = maxY;
            projectionRegionFront = (CameraCoordinateSystem.RightHanded == CoordinateSystem) ? maxZ : minZ;
            projectionRegionRight = maxX;
            projectionRegionBottom = minY;
            projectionRegionBack = (CameraCoordinateSystem.RightHanded == CoordinateSystem) ? minZ : maxZ;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions