/// <summary>
        /// Computes the angle between two vectors.
        /// </summary>
        /// <param name="vector1">First vector.</param>
        /// <param name="vector2">Second vector.</param>
        /// <returns>
        /// Returns the angle required to rotate vector1 into vector2 in degrees.
        /// This will return a value between [0, 180] degrees.
        /// (Note that this is slightly different from the Vector member
        /// function of the same name.  Signed angles do not extend to 3D.)
        /// </returns>
        public static double AngleBetween(Vector3D vector1, Vector3D vector2)
        {
            vector1.Normalize();
            vector2.Normalize();

            double ratio = DotProduct(vector1, vector2);

            // The "straight forward" method of acos(u.v) has large precision
            // issues when the dot product is near +/-1.  This is due to the
            // steep slope of the acos function as we approach +/- 1.  Slight
            // precision errors in the dot product calculation cause large
            // variation in the output value.
            //
            //        |                   |
            //         \__                |
            //            ---___          |
            //                  ---___    |
            //                        ---_|_
            //                            | ---___
            //                            |       ---___
            //                            |             ---__
            //                            |                  \
            //                            |                   |
            //       -|-------------------+-------------------|-
            //       -1                   0                   1
            //
            //                         acos(x)
            //
            // To avoid this we use an alternative method which finds the
            // angle bisector by (u-v)/2:
            //
            //                            _>
            //                       u  _-  \ (u-v)/2
            //                        _-  __-v
            //                      _=__--
            //                    .=----------->
            //                            v
            //
            // Because u and v and unit vectors, (u-v)/2 forms a right angle
            // with the angle bisector.  The hypotenuse is 1, therefore
            // 2*asin(|u-v|/2) gives us the angle between u and v.
            //
            // The largest possible value of |u-v| occurs with perpendicular
            // vectors and is sqrt(2)/2 which is well away from extreme slope
            // at +/-1.
            //

            double theta;

            if (ratio < 0)
            {
                theta = Math.PI - 2.0 * Math.Asin((-vector1 - vector2).Length / 2.0);
            }
            else
            {
                theta = 2.0 * Math.Asin((vector1 - vector2).Length / 2.0);
            }

            return(M3DUtil.RadiansToDegrees(theta));
        }
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------

        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        internal Matrix3D GetProjectionMatrix(double aspectRatio, double zn, double zf)
        {
            double fov = M3DUtil.DegreesToRadians(FieldOfView);

            // Note: h and w are 1/2 of the inverse of the width/height ratios:
            //
            //  h = 1/(heightDepthRatio) * (1/2)
            //  w = 1/(widthDepthRatio) * (1/2)
            //
            // Computation for h is a bit different than what you will find in
            // D3DXMatrixPerspectiveFovRH because we have a horizontal rather
            // than vertical FoV.

            double halfWidthDepthRatio = Math.Tan(fov / 2);
            double h = aspectRatio / halfWidthDepthRatio;
            double w = 1 / halfWidthDepthRatio;

            double m22 = zf != Double.PositiveInfinity ? zf / (zn - zf) : -1;
            double m32 = zn * m22;

            return(new Matrix3D(
                       w, 0, 0, 0,
                       0, h, 0, 0,
                       0, 0, m22, -1,
                       0, 0, m32, 0));
        }
Esempio n. 3
0
        private void Debug_VerifyCachedBounds()
        {
            Rect3D actualBounds = M3DUtil.ComputeAxisAlignedBoundingBox(Positions);

            // The funny boolean logic below avoids asserts when the cached
            // bounds contain NaNs.  (NaN != NaN)
            bool areEqual =
                !(_cachedBounds.X <actualBounds.X || _cachedBounds.X> actualBounds.X) &&
                !(_cachedBounds.Y <actualBounds.Y || _cachedBounds.Y> actualBounds.Y) &&
                !(_cachedBounds.Z <actualBounds.Z || _cachedBounds.Z> actualBounds.Z) &&
                !(_cachedBounds.SizeX <actualBounds.SizeX || _cachedBounds.SizeX> actualBounds.SizeX) &&
                !(_cachedBounds.SizeY <actualBounds.SizeY || _cachedBounds.SizeY> actualBounds.SizeY) &&
                !(_cachedBounds.SizeZ <actualBounds.SizeZ || _cachedBounds.SizeZ> actualBounds.SizeZ);

            if (!areEqual)
            {
                if (_cachedBounds == Rect3D.Empty)
                {
                    Debug.Fail("Cached bounds are invalid. Caller needs to check for IsEmpty and call UpdateCachedBounds.");
                }
                else
                {
                    Debug.Fail("Cached bounds are invalid. We missed a call to SetCachedBoundsDirty.");
                }
            }
        }
Esempio n. 4
0
        // Updates the _cachedBounds member to the current bounds of the mesh.
        // This method must be called before accessing _cachedBounds if
        // _cachedBounds.IsEmpty is true.  Otherwise the _cachedBounds are
        // current and do not need to be recomputed.  See also Debug_VerifyCachedBounds.
        private void UpdateCachedBounds()
        {
            Debug.Assert(_cachedBounds.IsEmpty,
                         "PERF: Caller should verify that bounds are dirty before recomputing.");

            _cachedBounds = M3DUtil.ComputeAxisAlignedBoundingBox(Positions);
        }
Esempio n. 5
0
    public GameObject CreateBrick(int type)
    {
        GameObject brick = GameObject.Instantiate(m_BrickPrefab) as GameObject;
        GameObject child = CreateBrick_Child(type);

        if (child != null)
        {
            child.transform.parent = brick.transform;
            M3DUtil.InitLocalTf(child.transform);
        }
        return(brick);
    }
Esempio n. 6
0
 public void SetBrickChild(GameObject newChild)
 {
     if (this.transform.childCount != 0)
     {
         Transform child = this.transform.GetChild(0);
         GameObject.DestroyImmediate(child.gameObject);
         child = null;
     }
     if (newChild != null)
     {
         newChild.transform.parent = this.transform;
         M3DUtil.InitLocalTf(newChild.transform);
     }
 }
Esempio n. 7
0
        internal override Rect CalculateSubgraphBoundsInnerSpace(bool renderBounds)
        {
            Camera camera = Camera;

            if (camera == null)
            {
                return(Rect.Empty);
            }

            //
            // Cache the 3D bounding box for future use. Here we are relying on
            // the fact that this method is called by PrecomputeContent(),
            // which is called prior to all usages of _bboxChildrenSubgraph3D.
            //
            _bboxChildrenSubgraph3D = ComputeSubgraphBounds3D();

            if (_bboxChildrenSubgraph3D.IsEmpty)
            {
                // Attempting to project empty bounds will result in NaNs which
                // which will ruin descendant bounds for the 2D tree.  We handle
                // this explicitly and early exit with the correct answer.

                return(Rect.Empty);
            }

            Rect viewport = Viewport;

            // Common Case: Viewport3DVisual in a collasped UIElement.
            if (viewport.IsEmpty)
            {
                // Creating a 3D homogenous space to 2D viewport space transform
                // with an empty rectangle will result in NaNs which ruin the
                // descendant bounds for the 2D tree.  We handle this explicitly
                // and early exit with the correct answer.
                //
                // (See also Windows OS Bugs #1637618)

                return(Rect.Empty);
            }

            double   aspectRatio       = M3DUtil.GetAspectRatio(viewport.Size);
            Matrix3D viewProjMatrix    = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio);
            Rect     projectedBounds2D = MILUtilities.ProjectBounds(ref viewProjMatrix, ref _bboxChildrenSubgraph3D);
            Matrix   homoToLocal       = M3DUtil.GetHomogeneousToViewportTransform(viewport);

            MatrixUtil.TransformRect(ref projectedBounds2D, ref homoToLocal);

            return(projectedBounds2D);
        }
Esempio n. 8
0
        internal Point WorldToViewport(Point4D point)
        {
            double aspectRatio = M3DUtil.GetAspectRatio(Viewport.Size);
            Camera camera      = Camera;

            if (camera != null)
            {
                Matrix3D viewProjMatrix = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio);
                point *= viewProjMatrix;

                Point point2D = new Point(point.X / point.W, point.Y / point.W);
                point2D *= M3DUtil.GetHomogeneousToViewportTransform(Viewport);

                return(point2D);
            }
            else
            {
                return(new Point(0, 0));
            }
        }
        internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment)
        {
            // The camera may be animating.  Take a snapshot of the current value
            // and get the property values we need. (Window OS #992662)
            Point3D     position      = Position;
            Vector3D    lookDirection = LookDirection;
            Vector3D    upDirection   = UpDirection;
            Transform3D transform     = Transform;
            double      zn            = NearPlaneDistance;
            double      zf            = FarPlaneDistance;
            double      fov           = M3DUtil.DegreesToRadians(FieldOfView);

            //
            //  Compute rayParameters
            //

            // Find the point on the projection plane in post-projective space where
            // the viewport maps to a 2x2 square from (-1,1)-(1,-1).
            Point np = M3DUtil.GetNormalizedPoint(p, viewSize);

            // Note: h and w are 1/2 of the inverse of the width/height ratios:
            //
            //  h = 1/(heightDepthRatio) * (1/2)
            //  w = 1/(widthDepthRatio) * (1/2)
            //
            // Computation for h is a bit different than what you will find in
            // D3DXMatrixPerspectiveFovRH because we have a horizontal rather
            // than vertical FoV.
            double aspectRatio         = M3DUtil.GetAspectRatio(viewSize);
            double halfWidthDepthRatio = Math.Tan(fov / 2);
            double h = aspectRatio / halfWidthDepthRatio;
            double w = 1 / halfWidthDepthRatio;

            // To get from projective space to camera space we apply the
            // width/height ratios to find our normalized point at 1 unit
            // in front of the camera.  (1 is convenient, but has no other
            // special significance.) See note above about the construction
            // of w and h.
            Vector3D rayDirection = new Vector3D(np.X / w, np.Y / h, -1);

            // Apply the inverse of the view matrix to our rayDirection vector
            // to convert it from camera to world space.
            //
            // NOTE: Because our construction of the ray assumes that the
            //       viewMatrix translates the position to the origin we pass
            //       null for the Camera.Transform below and account for it
            //       later.

            Matrix3D viewMatrix = CreateViewMatrix(/* trasform = */ null, ref position, ref lookDirection, ref upDirection);
            Matrix3D invView    = viewMatrix;

            invView.Invert();
            invView.MultiplyVector(ref rayDirection);

            // The we have the ray direction, now we need the origin.  The camera's
            // position would work except that we would intersect geometry between
            // the camera plane and the near plane so instead we must find the
            // point on the project plane where the ray (position, rayDirection)
            // intersect (Windows OS #1005064):
            //
            //                     | _.>       p = camera position
            //                rd  _+"          ld = camera look direction
            //                 .-" |ro         pp = projection plane
            //             _.-"    |           rd = ray direction
            //         p +"--------+--->       ro = desired ray origin on pp
            //                ld   |
            //                     pp
            //
            // Above we constructed the direction such that it's length projects to
            // 1 unit on the lookDirection vector.
            //
            //
            //                rd  _.>
            //                 .-"        rd = unnormalized rayDirection
            //             _.-"           ld = normalized lookDirection (length = 1)
            //           -"--------->
            //                 ld
            //
            // So to find the desired rayOrigin on the projection plane we simply do:
            Point3D rayOrigin = position + zn * rayDirection;

            rayDirection.Normalize();

            // Account for the Camera.Transform we ignored during ray construction above.
            if (transform != null && transform != Transform3D.Identity)
            {
                Matrix3D m = transform.Value;
                m.MultiplyPoint(ref rayOrigin);
                m.MultiplyVector(ref rayDirection);

                PrependInverseTransform(m, ref viewMatrix);
            }

            RayHitTestParameters rayParameters = new RayHitTestParameters(rayOrigin, rayDirection);

            //
            //  Compute HitTestProjectionMatrix
            //

            Matrix3D projectionMatrix = GetProjectionMatrix(aspectRatio, zn, zf);

            // The projectionMatrix takes camera-space 3D points into normalized clip
            // space.

            // The viewportMatrix will take normalized clip space into
            // viewport coordinates, with an additional 2D translation
            // to put the ray at the rayOrigin.
            Matrix3D viewportMatrix = new Matrix3D();

            viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0));
            viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1));
            viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0));

            // `First world-to-camera, then camera's projection, then normalized clip space to viewport.
            rayParameters.HitTestProjectionMatrix =
                viewMatrix *
                projectionMatrix *
                viewportMatrix;

            //
            // Perspective camera doesn't allow negative NearPlanes, so there's
            // not much point in adjusting the ray origin. Hence, the
            // distanceAdjustment remains 0.
            //
            distanceAdjustment = 0.0;

            return(rayParameters);
        }
Esempio n. 10
0
        internal static bool Get3DPointFor2DCoordinate(Point point,
                                                       out Point3D point3D,
                                                       Point3DCollection positions,
                                                       PointCollection textureCoords,
                                                       Int32Collection triIndices)
        {
            point3D = new Point3D();

            // walk through the triangles - and look for the triangles we care about
            Point3D[] p  = new Point3D[3];
            Point[]   uv = new Point[3];

            if (positions != null && textureCoords != null)
            {
                if (triIndices == null || triIndices.Count == 0)
                {
                    int texCoordCount = textureCoords.Count;

                    // in this case we have a non-indexed mesh
                    int count = positions.Count;
                    count = count - (count % 3);

                    for (int i = 0; i < count; i += 3)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            p[j] = positions[i + j];

                            if (i + j < texCoordCount)
                            {
                                uv[j] = textureCoords[i + j];
                            }
                            else
                            {
                                // In the case you have less texture coordinates than positions, MIL will set
                                // missing ones to be 0,0.  We do the same to stay consistent.
                                // See CMILMesh3D::CopyTextureCoordinatesFromDoubles
                                uv[j] = new Point(0, 0);
                            }
                        }

                        if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D))
                        {
                            return(true);
                        }
                    }
                }
                else
                {
                    // in this case we have an indexed mesh
                    int posLimit      = positions.Count;
                    int texCoordLimit = textureCoords.Count;

                    for (int i = 2, count = triIndices.Count; i < count; i += 3)
                    {
                        bool validTextureCoordinates = true;
                        for (int j = 0; j < 3; j++)
                        {
                            // subtract 2 to take in to account we start i
                            // at the high range of indices
                            int index = triIndices[(i - 2) + j];

                            // if a point or texture coordinate is out of range, end early since this is an error
                            if (index < 0 || index >= posLimit)
                            {
                                // no need to look anymore - see MeshGeometry3D RayHitTestIndexedList for
                                // reasoning why we stop
                                return(false);
                            }
                            if (index < 0 || index >= texCoordLimit)
                            {
                                validTextureCoordinates = false;
                                break;
                            }

                            p[j]  = positions[index];
                            uv[j] = textureCoords[index];
                        }

                        if (validTextureCoordinates)
                        {
                            if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D))
                            {
                                return(true);
                            }
                        }
                    }
                }
            }

            return(false);
        }
Esempio n. 11
0
        internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment)
        {
            //
            //  Compute rayParameters
            //

            // Find the point on the projection plane in post-projective space where
            // the viewport maps to a 2x2 square from (-1,1)-(1,-1).
            Point np = M3DUtil.GetNormalizedPoint(p, viewSize);

            // This ray is consistent with a left-handed camera
            // MatrixCamera should be right-handed and should be updated to be so.

            // So (conceptually) the user clicked on the point (np.X,
            // np.Y, 0) in post-projection clipping space and the ray
            // extends in the direction (0, 0, 1) because our ray
            // after projection looks down the positive z axis.  We
            // need to convert this ray and direction back to world
            // space.

            Matrix3D worldToCamera = GetViewMatrix() * ProjectionMatrix;
            Matrix3D cameraToWorld = worldToCamera;

            if (!cameraToWorld.HasInverse)
            {
                // When the following issue is addressed we should
                //   investigate if the custom code paths in Orthographic and PerspectiveCamera
                //   are worth keeping.  They may not be buying us anything aside from handling
                //   singular matrices.

                // Need to handle singular matrix cameras
                throw new NotSupportedException(SR.Get(SRID.HitTest_Singular));
            }

            cameraToWorld.Invert();

            Point4D origin4D = new Point4D(np.X, np.Y, 0, 1) * cameraToWorld;
            Point3D origin   = new Point3D(origin4D.X / origin4D.W,
                                           origin4D.Y / origin4D.W,
                                           origin4D.Z / origin4D.W);

            // To transform the direction we use the Jacobian of
            // cameraToWorld at the point np.X,np.Y,0 that we just
            // transformed.
            //
            // The Jacobian of the homogeneous matrix M is a 3x3 matrix.
            //
            // Let x be the point we are computing the Jacobian at, and y be the
            // result of transforming x by M, i.e.
            // (wy w) = (x 1) M
            // Where (wy w) is the homogeneous point representing y with w as its homogeneous coordinate
            // And (x 1) is the homogeneous point representing x with 1 as its homogeneous coordinate
            //
            // Then the i,j component of the Jacobian (at x) is
            // (M_ij - M_i4 y_j) / w
            //
            // Since we're only concerned with the direction of the
            // transformed vector and not its magnitude, we can scale
            // this matrix by a POSITIVE factor.  The computation
            // below computes the Jacobian scaled by 1/w and then
            // after we normalize the final vector we flip it around
            // if w is negative.
            //
            // To transform a vector we just right multiply it by this Jacobian matrix.
            //
            // Compute the Jacobian at np.X,np.Y,0 ignoring the constant factor of w.
            // Here's the pattern
            //
            // double Jij = cameraToWorld.Mij - cameraToWorld.Mi4 * origin.j
            //
            // but we only need J31,J32,&J33 because we're only
            // transforming the vector 0,0,1

            double J31 = cameraToWorld.M31 - cameraToWorld.M34 * origin.X;
            double J32 = cameraToWorld.M32 - cameraToWorld.M34 * origin.Y;
            double J33 = cameraToWorld.M33 - cameraToWorld.M34 * origin.Z;

            // Then multiply that matrix by (0, 0, 1) which is
            // the direction of the ray in post-projection space.
            Vector3D direction = new Vector3D(J31, J32, J33);

            direction.Normalize();

            // We multiplied by the Jacobian times W, so we need to
            // account for whether that flipped our result or not.
            if (origin4D.W < 0)
            {
                direction = -direction;
            }

            RayHitTestParameters rayParameters = new RayHitTestParameters(origin, direction);

            //
            //  Compute HitTestProjectionMatrix
            //

            // The viewportMatrix will take normalized clip space into
            // viewport coordinates, with an additional 2D translation
            // to put the ray at the origin.
            Matrix3D viewportMatrix = new Matrix3D();

            viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0));
            viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1));
            viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0));

            // `First world-to-camera, then camera's projection, then normalized clip space to viewport.
            rayParameters.HitTestProjectionMatrix =
                worldToCamera *
                viewportMatrix;

            //
            // MatrixCamera does not allow for Near/Far plane adjustment, so
            // the distanceAdjustment remains 0.
            //
            distanceAdjustment = 0.0;

            return(rayParameters);
        }
Esempio n. 12
0
 /// <summary>
 /// Transforms the bounding box to the smallest axis aligned bounding box
 /// that contains all the points in the original bounding box
 /// </summary>
 /// <param name="rect">Bounding box</param>
 /// <returns>The transformed bounding box</returns>
 public override Rect3D TransformBounds(Rect3D rect)
 {
     return(M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref rect, this));
 }
        internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment)
        {
            // The camera may be animating.  Take a snapshot of the current value
            // and get the property values we need. (Window OS #992662)
            Point3D  position      = Position;
            Vector3D lookDirection = LookDirection;
            Vector3D upDirection   = UpDirection;
            double   zn            = NearPlaneDistance;
            double   zf            = FarPlaneDistance;
            double   width         = Width;

            //
            //  Compute rayParameters
            //

            // Find the point on the projection plane in post-projective space where
            // the viewport maps to a 2x2 square from (-1,1)-(1,-1).
            Point np = M3DUtil.GetNormalizedPoint(p, viewSize);

            double aspectRatio = M3DUtil.GetAspectRatio(viewSize);
            double w           = width;
            double h           = w / aspectRatio;

            // Direction is always perpendicular to the viewing surface.
            Vector3D direction = new Vector3D(0, 0, -1);

            // Apply the inverse of the view matrix to our ray.
            Matrix3D viewMatrix = CreateViewMatrix(Transform, ref position, ref lookDirection, ref upDirection);
            Matrix3D invView    = viewMatrix;

            invView.Invert();

            // We construct our ray such that the origin resides on the near
            // plane.  If our near plane is too far from our the bounding box
            // of our scene then the results will be inaccurate.  (e.g.,
            // OrthographicCameras permit negative near planes, so the near
            // plane could be at -Inf.)
            //
            // However, it is permissable to move the near plane nearer to
            // the scene bounds without changing what the ray intersects.
            // If the near plane is sufficiently far from the scene bounds
            // we make this adjustment below to increase precision.

            Rect3D transformedBoundingBox =
                M3DUtil.ComputeTransformedAxisAlignedBoundingBox(
                    ref boundingRect,
                    ref viewMatrix);

            // DANGER:  The NearPlaneDistance property is specified as a
            //          distance from the camera position along the
            //          LookDirection with (Near < Far).
            //
            //          However, when we transform our scene bounds so that
            //          the camera is aligned with the negative Z-axis the
            //          relationship inverts (Near > Far) as illustrated
            //          below:
            //
            //            NearPlane    Y                      FarPlane
            //                |        ^                          |
            //                |        |                          |
            //                |        | (rect.Z + rect.SizeZ)    |
            //                |        |           o____          |
            //                |        |           |    |         |
            //                |        |           |    |         |
            //                |        |            ____o         |
            //                |        |             (rect.Z)     |
            //                |     Camera ->                     |
            //          +Z  <----------+----------------------------> -Z
            //                |        0                          |
            //
            //          It is surprising, but its the "far" side of the
            //          transformed scene bounds that determines the near
            //          plane distance.

            double zn2 = -AddEpsilon(transformedBoundingBox.Z + transformedBoundingBox.SizeZ);

            if (zn2 > zn)
            {
                //
                // Our near plane is far from our children. Construct a new
                // near plane that's closer. Note that this will modify our
                // distance computations, so we have to be sure to adjust our
                // distances appropriately.
                //
                distanceAdjustment = zn2 - zn;

                zn = zn2;
            }
            else
            {
                //
                // Our near plane is either close to or in front of our
                // children, so let's keep it -- no distance adjustment needed.
                //
                distanceAdjustment = 0.0;
            }

            // Our origin is the point normalized to the front of our viewing volume.
            // To find our origin's x/y we just need to scale the normalize point by our
            // width/height.  In camera space we are looking down the negative Z axis
            // so we just set Z to be -zn which puts us on the projection plane
            // (Windows OS #1005064).
            Point3D origin = new Point3D(np.X * (w / 2), np.Y * (h / 2), -zn);

            invView.MultiplyPoint(ref origin);
            invView.MultiplyVector(ref direction);

            RayHitTestParameters rayParameters = new RayHitTestParameters(origin, direction);

            //
            //  Compute HitTestProjectionMatrix
            //

            Matrix3D projectionMatrix = GetProjectionMatrix(aspectRatio, zn, zf);

            // The projectionMatrix takes camera-space 3D points into normalized clip
            // space.

            // The viewportMatrix will take normalized clip space into
            // viewport coordinates, with an additional 2D translation
            // to put the ray at the origin.
            Matrix3D viewportMatrix = new Matrix3D();

            viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0));
            viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1));
            viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0));

            // `First world-to-camera, then camera's projection, then normalized clip space to viewport.
            rayParameters.HitTestProjectionMatrix =
                viewMatrix *
                projectionMatrix *
                viewportMatrix;

            return(rayParameters);
        }
Esempio n. 14
0
        /// <summary>
        ///     Returns the bounds of the Model3D subgraph rooted at this Model3D.
        ///
        ///     Outer space refers to the space after this Model's Transform is
        ///     applied -- or said another way, applying a transform to this Model
        ///     affects it's outer bounds.  (While its inner bounds remain unchanged.)
        /// </summary>
        internal Rect3D CalculateSubgraphBoundsOuterSpace()
        {
            Rect3D innerBounds = CalculateSubgraphBoundsInnerSpace();

            return(M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref innerBounds, Transform));
        }
Esempio n. 15
0
        //
        // Processes a ray-line intersection to see if it's a valid hit.
        //
        // Shares some code with ValidateRayHit
        //
        private void ValidateLineHit(
            RayHitTestParameters rayParams,
            FaceType facesToHit,
            int i0,
            int i1,
            int i2,
            ref Point3D v0,
            ref Point3D v1,
            ref Point3D v2,
            ref Point barycentric
            )
        {
            Matrix3D worldTransformMatrix = rayParams.HasWorldTransformMatrix ? rayParams.WorldTransformMatrix : Matrix3D.Identity;

            // OK, we have an intersection with the LINE but that could be wrong on three
            // accounts:
            //   1. We could have hit the line on the wrong side of the ray's origin.
            //   2. We may need to cull the intersection if it's beyond the far clipping
            //      plane (only if the hit test originated from a Viewport3DVisual.)
            //   3. We could have hit a back-facing triangle
            // We will transform the hit point back into world space to check these
            // things & compute the correct distance from the origin to the hit point.

            // Hit point in model space
            Point3D pointHit = M3DUtil.Interpolate(ref v0, ref v1, ref v2, ref barycentric);

            Point3D worldPointHit = pointHit;

            worldTransformMatrix.MultiplyPoint(ref worldPointHit);

            // Vector from origin to hit point
            Vector3D hitVector               = worldPointHit - rayParams.Origin;
            Vector3D originalDirection       = rayParams.Direction;
            double   rayDistanceUnnormalized = Vector3D.DotProduct(originalDirection, hitVector);

            if (rayDistanceUnnormalized > 0)
            {
                // If we have a HitTestProjectionMatrix than this hit test originated
                // at a Viewport3DVisual.
                if (rayParams.HasHitTestProjectionMatrix)
                {
                    // To test if we are in front of the far clipping plane what we
                    // do conceptually is project our hit point in world space into
                    // homogenous space and verify that it is on the correct side of
                    // the Z=1 plane.
                    //
                    // To save some cycles we only bother computing Z and W of the
                    // projected point and use a simple Z/W > 1 test to see if we
                    // are past the far plane.
                    //
                    // NOTE: HitTestProjectionMatrix is not just the camera matrices.
                    //       It has an additional translation to move the ray to the
                    //       origin.  This extra translation does not effect this test.

                    Matrix3D m = rayParams.HitTestProjectionMatrix;

                    // We directly substitute 1 for p.W below:
                    double pz = worldPointHit.X * m.M13 + worldPointHit.Y * m.M23 + worldPointHit.Z * m.M33 + m.OffsetZ;
                    double pw = worldPointHit.X * m.M14 + worldPointHit.Y * m.M24 + worldPointHit.Z * m.M34 + m.M44;

                    // Early exit if pz/pw > 1.  The negated logic is to reject NaNs.
                    if (!(pz / pw <= 1))
                    {
                        return;
                    }

                    Debug.Assert(!double.IsInfinity(pz / pw) && !double.IsNaN(pz / pw),
                                 "Expected near/far tests to cull -Inf/+Inf and NaN.");
                }

                Point3D a = v0, b = v1, c = v2;

                worldTransformMatrix.MultiplyPoint(ref a);
                worldTransformMatrix.MultiplyPoint(ref b);
                worldTransformMatrix.MultiplyPoint(ref c);

                Vector3D normal = Vector3D.CrossProduct(b - a, c - a);

                double cullSign  = -Vector3D.DotProduct(normal, hitVector);
                double det       = worldTransformMatrix.Determinant;
                bool   frontFace = (cullSign > 0) == (det >= 0);

                if (((facesToHit & FaceType.Front) == FaceType.Front && frontFace) || ((facesToHit & FaceType.Back) == FaceType.Back && !frontFace))
                {
                    double dist = hitVector.Length;
                    if (rayParams.HasModelTransformMatrix)
                    {
                        rayParams.ModelTransformMatrix.MultiplyPoint(ref pointHit);
                    }

                    rayParams.ReportResult(this, pointHit, dist, i0, i1, i2, barycentric);
                }
            }
        }