Beispiel #1
0
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
        
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
       
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        // NOTE: Geometry3D hit testing takes the rayParams in the outer space of the
        //       Geometry3D.  That is, RayHitTest() will apply this geometry's 
        //       transform to the ray for the caller.
        //
        //       This is different than Visual hit testing which does not transform
        //       the hit testing parameters by the Visual's transform.
        internal void RayHitTest(RayHitTestParameters rayParams, FaceType facesToHit)
        {
            Debug.Assert(facesToHit != FaceType.None, 
                "Caller should make sure we're trying to hit something");
               
            Rect3D bounds = Bounds;

            if (bounds.IsEmpty)
            {
                return;
            }

            // 




            Point3D origin;
            Vector3D direction;

            rayParams.GetLocalLine(out origin, out direction);

            if (LineUtil.ComputeLineBoxIntersection(ref origin, ref direction, ref bounds, rayParams.IsRay))
            {
                RayHitTestCore(rayParams, facesToHit);
            }
            
            // 


        }
        internal override void RayHitTestCore(RayHitTestParameters rayParams)
        {
            Geometry3D geometry = Geometry;
            
            if (geometry != null)
            {
                // If our Geometry3D hit test intersects anything we should return "this" Model3D
                // as the HitTestResult.ModelHit.
                rayParams.CurrentModel = this;

                FaceType facesToHit = FaceType.None;

                if (Material != null)
                {
                    facesToHit |= FaceType.Front;
                }
                
                if (BackMaterial != null)
                {
                    facesToHit |= FaceType.Back;
                }

                if (facesToHit != FaceType.None)
                {
                    geometry.RayHitTest(rayParams, facesToHit);
                }
            }
        }
Beispiel #3
0
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

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

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

        #region Internal Methods

        // NOTE: Geometry3D hit testing takes the rayParams in the outer space of the
        //       Geometry3D.  That is, RayHitTest() will apply this geometry's
        //       transform to the ray for the caller.
        //
        //       This is different than Visual hit testing which does not transform
        //       the hit testing parameters by the Visual's transform.
        internal void RayHitTest(RayHitTestParameters rayParams, FaceType facesToHit)
        {
            Debug.Assert(facesToHit != FaceType.None,
                         "Caller should make sure we're trying to hit something");

            Rect3D bounds = Bounds;

            if (bounds.IsEmpty)
            {
                return;
            }

            //



            Point3D  origin;
            Vector3D direction;

            rayParams.GetLocalLine(out origin, out direction);

            if (LineUtil.ComputeLineBoxIntersection(ref origin, ref direction, ref bounds, rayParams.IsRay))
            {
                RayHitTestCore(rayParams, facesToHit);
            }

            //
        }
Beispiel #4
0
        internal override void RayHitTestCore(RayHitTestParameters rayParams)
        {
            Geometry3D geometry = Geometry;

            if (geometry != null)
            {
                // If our Geometry3D hit test intersects anything we should return "this" Model3D
                // as the HitTestResult.ModelHit.
                rayParams.CurrentModel = this;

                FaceType facesToHit = FaceType.None;

                if (Material != null)
                {
                    facesToHit |= FaceType.Front;
                }

                if (BackMaterial != null)
                {
                    facesToHit |= FaceType.Back;
                }

                if (facesToHit != FaceType.None)
                {
                    geometry.RayHitTest(rayParams, facesToHit);
                }
            }
        }
Beispiel #5
0
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

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

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

        #region Internal Methods

        // NOTE: Geometry3D hit testing takes the rayParams in the outer space of the
        //       Geometry3D.  That is, RayHitTest() will apply this geometry's
        //       transform to the ray for the caller.
        //
        //       This is different than Visual hit testing which does not transform
        //       the hit testing parameters by the Visual's transform.
        internal void RayHitTest(RayHitTestParameters rayParams, FaceType facesToHit)
        {
            Debug.Assert(facesToHit != FaceType.None,
                         "Caller should make sure we're trying to hit something");

            Rect3D bounds = Bounds;

            if (bounds.IsEmpty)
            {
                return;
            }

            // Geometry3D's do not yet support a Transform property
            //
            // Transform3D transform = Transform;
            // rayParams.PushTransform(transform);


            Point3D  origin;
            Vector3D direction;

            rayParams.GetLocalLine(out origin, out direction);

            if (LineUtil.ComputeLineBoxIntersection(ref origin, ref direction, ref bounds, rayParams.IsRay))
            {
                RayHitTestCore(rayParams, facesToHit);
            }

            // Geometry3D's do not yet support a Transform property
            //
            // rayParams.PopTransform(transform);
        }
Beispiel #6
0
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        // NOTE: Model3D hit testing takes the rayParams in the outer space of the
        //       Model3D.  That is, RayHitTest() will apply this model's transform
        //       to the ray for the caller.
        //
        //       This is different than Visual hit testing which does not transform
        //       the hit testing parameters by the Visual's transform.
        internal void RayHitTest(RayHitTestParameters rayParams)
        {
            Transform3D transform = Transform;

            rayParams.PushModelTransform(transform);
            RayHitTestCore(rayParams);
            rayParams.PopTransform(transform);
        }
        public void HitTest(object sender, System.Windows.Input.MouseButtonEventArgs args)
        {
            Point mouseposition = args.GetPosition(myViewport);
            Point3D testpoint3D = new Point3D(mouseposition.X, mouseposition.Y, 0);
            Vector3D testdirection = new Vector3D(mouseposition.X, mouseposition.Y, 10);
            PointHitTestParameters pointparams = new PointHitTestParameters(mouseposition);
            RayHitTestParameters rayparams = new RayHitTestParameters(testpoint3D, testdirection);

            //test for a result in the Viewport3D
            VisualTreeHelper.HitTest(myViewport, null, HTResult, pointparams);
            UpdateTestPointInfo(testpoint3D, testdirection);
        }
Beispiel #8
0
 public Point3D? HitTest(Visual3D reference, Point3D point, Vector3D direction)
 {
     if (reference != null)
     {
         var hitParams = new RayHitTestParameters(point, direction);
         hit = false;
         VisualTreeHelper.HitTest(reference, null, HitTestCallback, hitParams);
         if (hit)
         {
             return hitTestValue;
         }
     }
     return null;
 }
Beispiel #9
0
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        internal override void RayHitTestCore(
            RayHitTestParameters rayParams)
        {
            Model3DCollection children = Children;

            if (children == null)
            {
                return;
            }

            for (int i = children.Count - 1; i >= 0; i--)
            {
                Model3D child = children.Internal_GetItem(i);

                // Perform the hit-test against the child.
                child.RayHitTest(rayParams);
            }
        }
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        internal override void RayHitTestCore(
            RayHitTestParameters rayParams)
        {    
            Model3DCollection children = Children;

            if (children == null)
            {
                return;
            }
            
            for (int i = children.Count - 1; i >= 0; i--)
            {
                Model3D child = children.Internal_GetItem(i);

                // Perform the hit-test against the child.
                child.RayHitTest(rayParams);
            }
        }
Beispiel #11
0
        internal override HitTestResultBehavior HitTestPointInternal(
            HitTestFilterCallback filterCallback,
            HitTestResultCallback resultCallback,
            PointHitTestParameters hitTestParameters)
        {
            if (_children.Count != 0)
            {
                double distanceAdjustment;

                RayHitTestParameters rayParams =
                    Camera.RayFromViewportPoint(
                        hitTestParameters.HitPoint,
                        Viewport.Size,
                        BBoxSubgraph,
                        out distanceAdjustment);

                HitTestResultBehavior result = Visual3D.HitTestChildren(filterCallback, rayParams, this);

                return(rayParams.RaiseCallback(resultCallback, filterCallback, result, distanceAdjustment));
            }

            return(HitTestResultBehavior.Continue);
        }
        /// <summary>
        /// This overload is for actually dragging a part around
        /// </summary>
        /// <remarks>
        /// The centerRay is the ray that goes through the middle of the part being dragged around.  The return point is where
        /// the center of the part will go, and is calculated as an offset from the click ray
        /// </remarks>
        public Point3D? CastRay(RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            Point3D? retVal = null;

            switch (_shape)
            {
                case ShapeType.Line:
                    double dummy1;
                    retVal = CastRay_Line(out dummy1, _point, _direction, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Lines:
                    retVal = CastRay_LinesCircles(_lines, null, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Plane:
                    retVal = CastRay_Plane(_plane, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Circle:
                    double dummy2;
                    retVal = CastRay_Circle(out dummy2, _plane, _point, _radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Circles:
                    retVal = CastRay_LinesCircles(null, _circles, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.LinesCircles:
                    retVal = CastRay_LinesCircles(_lines, _circles, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Cylinder:
                    retVal = CastRay_Cylinder(_point, _direction, _radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Sphere:
                    retVal = CastRay_Sphere(_point, _radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    break;

                case ShapeType.Mesh:
                    throw new ApplicationException("finish this");

                case ShapeType.None:
                    retVal = null;
                    break;

                default:
                    throw new ApplicationException("Unknown ShapeType: " + _shape.ToString());
            }

            return retVal;
        }
        /// <summary>
        /// This overload is a straight ray cast, nothing extra
        /// </summary>
        public Point3D? CastRay(RayHitTestParameters ray)
        {
            Point3D? retVal = null;

            switch (_shape)
            {
                case ShapeType.Line:
                    #region Line

                    Point3D? point1, point2;
                    if (Math3D.GetClosestPoints_Line_Line(out point1, out point2, _point, _direction, ray.Origin, ray.Direction))
                    {
                        retVal = point1.Value;
                    }

                    #endregion
                    break;

                case ShapeType.Lines:
                    #region Lines

                    retVal = CastRay_LinesCircles(_lines, null, ray);

                    #endregion
                    break;

                case ShapeType.Plane:
                    #region Plane

                    retVal = Math3D.GetIntersection_Plane_Line(_plane, ray.Origin, ray.Direction);

                    #endregion
                    break;

                case ShapeType.Circle:
                    #region Circle

                    Point3D[] nearestCirclePoints, nearestLinePoints;
                    if (Math3D.GetClosestPoints_Circle_Line(out nearestCirclePoints, out nearestLinePoints, _plane, _point, _radius, ray.Origin, ray.Direction, Math3D.RayCastReturn.ClosestToRay))
                    {
                        retVal = nearestCirclePoints[0];
                    }

                    #endregion
                    break;

                case ShapeType.Circles:
                    #region Circles

                    retVal = CastRay_LinesCircles(null, _circles, ray);

                    #endregion
                    break;

                case ShapeType.LinesCircles:
                    #region LinesCircles

                    retVal = CastRay_LinesCircles(_lines, _circles, ray);

                    #endregion
                    break;

                case ShapeType.Cylinder:
                    #region Cylinder

                    Point3D[] nearestCylinderPoints, nearestLinePoints2;
                    if (Math3D.GetClosestPoints_Cylinder_Line(out nearestCylinderPoints, out nearestLinePoints2, _point, _direction, _radius, ray.Origin, ray.Direction, Math3D.RayCastReturn.ClosestToRay))
                    {
                        retVal = nearestCylinderPoints[0];
                    }

                    #endregion
                    break;

                case ShapeType.Sphere:
                    #region Sphere

                    Point3D[] nearestSpherePoints, nearestLinePoints3;
                    Math3D.GetClosestPoints_Sphere_Line(out nearestSpherePoints, out nearestLinePoints3, _point, _radius, ray.Origin, ray.Direction, Math3D.RayCastReturn.ClosestToRay);
                    {
                        retVal = nearestSpherePoints[0];
                    }

                    #endregion
                    break;

                case ShapeType.Mesh:
                    throw new ApplicationException("finish this");

                case ShapeType.None:
                    retVal = null;
                    break;

                default:
                    throw new ApplicationException("Unknown ShapeType: " + _shape.ToString());
            }

            return retVal;
        }
Beispiel #14
0
        //
        // Hits the ray against the mesh
        //
        internal override void RayHitTestCore(
            RayHitTestParameters rayParams,
            FaceType hitTestableFaces)
        {
            Debug.Assert(hitTestableFaces != FaceType.None,
                         "Caller should make sure we're trying to hit something");

            Point3DCollection positions = Positions;

            if (positions == null)
            {
                return;
            }

            Point3D  origin;
            Vector3D direction;

            rayParams.GetLocalLine(out origin, out direction);

            Int32Collection indices = TriangleIndices;

            // In the line case, we want to hit test all faces because we don't
            // have a direction. This may differ from what faces we want to
            // accept.
            FaceType facesToHit;

            if (rayParams.IsRay)
            {
                facesToHit = hitTestableFaces;
            }
            else
            {
                facesToHit = FaceType.Front | FaceType.Back;
            }


            //
            // This code duplication is unfortunate but necessary. Breaking it down into methods
            // further significantly impacts performance. About 5% improvement could be made
            // by unrolling this code below even more.
            //
            // If futher perf investigation is done with this code, be sure to test NGEN assemblies only
            // as JIT produces different, faster code than NGEN.
            //

            if (indices == null || indices.Count == 0)
            {
                FrugalStructList <Point3D> ps = positions._collection;
                int count = ps.Count - (ps.Count % 3);

                for (int i = count - 1; i >= 2; i -= 3)
                {
                    int i0 = i - 2;
                    int i1 = i - 1;
                    int i2 = i;

                    Point3D v0 = ps[i0];
                    Point3D v1 = ps[i1];
                    Point3D v2 = ps[i2];

                    double hitTime;
                    Point  barycentric;

                    // The line hit test is equivalent to a double sided
                    // triangle hit because it doesn't cull triangles based
                    // on winding
                    if (LineUtil.ComputeLineTriangleIntersection(
                            facesToHit,
                            ref origin,
                            ref direction,
                            ref v0,
                            ref v1,
                            ref v2,
                            out barycentric,
                            out hitTime
                            )
                        )
                    {
                        if (rayParams.IsRay)
                        {
                            ValidateRayHit(
                                rayParams,
                                ref origin,
                                ref direction,
                                hitTime,
                                i0,
                                i1,
                                i2,
                                ref barycentric
                                );
                        }
                        else
                        {
                            ValidateLineHit(
                                rayParams,
                                hitTestableFaces,
                                i0,
                                i1,
                                i2,
                                ref v0,
                                ref v1,
                                ref v2,
                                ref barycentric
                                );
                        }
                    }
                }
            }
            else // indexed mesh
            {
                FrugalStructList <Point3D> ps   = positions._collection;
                FrugalStructList <int>     idcs = indices._collection;

                int count = idcs.Count;
                int limit = ps.Count;

                for (int i = 2; i < count; i += 3)
                {
                    int i0 = idcs[i - 2];
                    int i1 = idcs[i - 1];
                    int i2 = idcs[i];

                    // Quit if we encounter an index out of range.
                    // This is okay because the triangles we ignore are not rendered.
                    //  (see: CMilMeshGeometry3DDuce::Realize)
                    if ((0 > i0 || i0 >= limit) ||
                        (0 > i1 || i1 >= limit) ||
                        (0 > i2 || i2 >= limit))
                    {
                        break;
                    }

                    Point3D v0 = ps[i0];
                    Point3D v1 = ps[i1];
                    Point3D v2 = ps[i2];

                    double hitTime;
                    Point  barycentric;

                    if (LineUtil.ComputeLineTriangleIntersection(
                            facesToHit,
                            ref origin,
                            ref direction,
                            ref v0,
                            ref v1,
                            ref v2,
                            out barycentric,
                            out hitTime
                            )
                        )
                    {
                        if (rayParams.IsRay)
                        {
                            ValidateRayHit(
                                rayParams,
                                ref origin,
                                ref direction,
                                hitTime,
                                i0,
                                i1,
                                i2,
                                ref barycentric
                                );
                        }
                        else
                        {
                            ValidateLineHit(
                                rayParams,
                                hitTestableFaces,
                                i0,
                                i1,
                                i2,
                                ref v0,
                                ref v1,
                                ref v2,
                                ref barycentric
                                );
                        }
                    }
                }
            }
        }
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        
        #region Private Methods

        //
        // Processes a ray-triangle intersection to see if it's a valid hit. Unnecessary faces
        // have already been culled by the ray-triange intersection routines.
        // 
        // Shares some code with ValidateLineHit
        //
        private void ValidateRayHit(
            RayHitTestParameters rayParams, 
            ref Point3D origin, 
            ref Vector3D direction, 
            double hitTime,
            int i0,
            int i1,
            int i2,
            ref Point barycentric
            )
        {
            if (hitTime > 0)
            {
                Matrix3D worldTransformMatrix = rayParams.HasWorldTransformMatrix ? rayParams.WorldTransformMatrix : Matrix3D.Identity;

                Point3D pointHit = origin + hitTime * direction;
                
                Point3D worldPointHit = pointHit;
                worldTransformMatrix.MultiplyPoint(ref worldPointHit);
                
                // 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.");
                }

                double dist = (worldPointHit - rayParams.Origin).Length;
                Debug.Assert(dist > 0, "Distance is negative: " + dist);

                if (rayParams.HasModelTransformMatrix)
                {
                    rayParams.ModelTransformMatrix.MultiplyPoint(ref pointHit);
                }
                
                rayParams.ReportResult(this, pointHit, dist, i0, i1, i2, barycentric);
            }
        }
Beispiel #16
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);
        }
        private static Point3D? CastRay_Cylinder(Point3D point, Vector3D direction, double radius, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            // Get points on the cylinder
            Point3D? mouseDownClickPoint = null;
            Point3D? mouseDownCenterPoint = null;
            Point3D? currentClickPoint = null;

            Point3D[] nearestCylinderPoints, nearestLinePoints;
            if (Math3D.GetClosestPoints_Cylinder_Line(out nearestCylinderPoints, out nearestLinePoints, point, direction, radius, currentClickRay.Origin, currentClickRay.Direction, Math3D.RayCastReturn.ClosestToRay))
            {
                currentClickPoint = nearestCylinderPoints[0];

                if (Math3D.GetClosestPoints_Cylinder_Line(out nearestCylinderPoints, out nearestLinePoints, point, direction, radius, mouseDownClickRay.Origin, mouseDownClickRay.Direction, Math3D.RayCastReturn.ClosestToRay))
                {
                    mouseDownClickPoint = nearestCylinderPoints[0];

                    if (Math3D.GetClosestPoints_Cylinder_Line(out nearestCylinderPoints, out nearestLinePoints, point, direction, radius, mouseDownCenterRay.Origin, mouseDownCenterRay.Direction, Math3D.RayCastReturn.ClosestToRay))
                    {
                        mouseDownCenterPoint = nearestCylinderPoints[0];
                    }
                }
            }

            if (mouseDownCenterPoint == null || mouseDownClickPoint == null || currentClickPoint == null)
            {
                return currentClickPoint;		// it doesn't matter if this one is null or not, the offset can't be determined, so just return the raw click value
            }

            // Circle only cared about an offset angle, but cylinder needs two things:
            //		Offset line (the part of the offset that is parallel to the cylinder's axis)
            //		Offset angle (the part of the offset that is perpendicular to the cylinder's axis)
            Vector3D offsetLinear = (mouseDownCenterPoint.Value - mouseDownClickPoint.Value).GetProjectedVector(direction);
            Quaternion offsetRadial = GetRotation_Circle(point, direction, mouseDownClickPoint.Value, mouseDownCenterPoint.Value);





            //TODO: Get the radial offset working as well (sphere is also messed up, the same fix should work for both)







            //TODO: See if this is the most effiecient way or not
            Transform3DGroup transform = new Transform3DGroup();
            //transform.Children.Add(new RotateTransform3D(new QuaternionRotation3D(offsetRadial)));
            transform.Children.Add(new TranslateTransform3D(offsetLinear));

            //TODO: Bring currentClickPoint into local coords, do the transform, then put it back into global coords

            // Find the point along the cylinder's axis that is nearest to the current click.  This will become the center of the model coords
            Point3D modelCenter = Math3D.GetClosestPoint_Line_Point(point, direction, currentClickPoint.Value);

            // Shift the click point into model coords
            Vector3D modelClick = currentClickPoint.Value - modelCenter;

            // Adjust by the offset transform (needed to put into model coords, because there is a rotation)
            modelClick = transform.Transform(modelClick);

            // Now put back into world coords
            return modelCenter + modelClick;
        }
        private static Point3D? CastRay_Plane(ITriangle plane, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            // This is the offset along the drag plane from the center to mouse down click
            Vector3D offset;

            Point3D? mouseDownClickPoint = Math3D.GetIntersection_Plane_Line(plane, mouseDownClickRay.Origin, mouseDownClickRay.Direction);
            Point3D? mouseDownCenterPoint = Math3D.GetIntersection_Plane_Line(plane, mouseDownCenterRay.Origin, mouseDownCenterRay.Direction);

            if (mouseDownClickPoint != null && mouseDownCenterPoint != null)
            {
                offset = mouseDownCenterPoint.Value - mouseDownClickPoint.Value;
            }
            else
            {
                // The click ray is parallel to the drag plane.  This should be extremely rare
                offset = new Vector3D(0, 0, 0);
            }

            // Now that the offset is known, project the current click ray onto the drag plane
            Point3D? retVal = Math3D.GetIntersection_Plane_Line(plane, currentClickRay.Origin, currentClickRay.Direction);

            if (retVal != null)
            {
                return retVal.Value + offset;
            }
            else
            {
                return null;
            }
        }
Beispiel #19
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);
                }
            }
        }
        private static Point3D? CastRay_PlaneLimited(ITriangle plane, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay, RayHitTestParameters cameraLook)
        {
            // They are looking along the plane, so snap to a line instead of a plane
            RayHitTestParameters snapLine = CastRay_PlaneLimitedSprtGetSnapLine(plane, cameraLook.Direction);		// the returned vector is used like 3 bools, with only one axis set to true
            if (snapLine == null)
            {
                return null;
            }

            double dummy1;
            return CastRay_Line(out dummy1, snapLine.Origin, snapLine.Direction, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
        }
Beispiel #21
0
 internal abstract void RayHitTestCore(RayHitTestParameters rayParams, FaceType hitTestableFaces);
        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);
        }
Beispiel #23
0
 internal abstract void RayHitTestCore(RayHitTestParameters rayParams);
        /// <summary>
        /// This overload is the same as the previous, but if the camera is looking along the plane/cylinder wall, then the output will be
        /// constrained to a line/circle
        /// NOTE: Sphere and circle will never be constrained, the lesser overload will be used instead
        /// </summary>
        public Point3D? CastRay(RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay, PerspectiveCamera camera, Viewport3D viewport)
        {
            if (_shape == ShapeType.None)
            {
                return null;
            }
            else if (_shape == ShapeType.Sphere || _shape == ShapeType.Circle || _shape == ShapeType.Circles)
            {
                return CastRay(mouseDownClickRay, mouseDownCenterRay, currentClickRay);
            }

            #region Get dot product

            //NOTE: _camera.LookDirection and _camera.UpDirection are really screwed up (I think that the trackball messed them up), so fire a ray instead
            // I'm not using the mouse click point, because that can change as they drag, and the inconsistency would be jarring
            RayHitTestParameters cameraLook = UtilityWPF.RayFromViewportPoint(camera, viewport, new Point(viewport.ActualWidth / 2d, viewport.ActualHeight / 2d));

            double dot = 0;
            double[] dots = null;

            Vector3D cameraLookUnit = cameraLook.Direction.ToUnit();

            switch (_shape)
            {
                case ShapeType.Plane:
                    dot = Vector3D.DotProduct(_plane.NormalUnit, cameraLookUnit);		// the dot is against the normal
                    break;

                case ShapeType.Line:
                case ShapeType.Cylinder:
                    //NOTE: They cylinder only limits movement if they are looking along the line.
                    dot = Vector3D.DotProduct(_direction.ToUnit(), cameraLookUnit);		// the dot is along the drag line
                    break;

                case ShapeType.Lines:
                case ShapeType.LinesCircles:		// I don't care about the dot product for the circles, they aren't limited
                    dots = _lines.Select(o => Vector3D.DotProduct(o.Direction.ToUnit(), cameraLookUnit)).ToArray();
                    break;

                default:
                    throw new ApplicationException("finish this");
            }

            #endregion

            Point3D? retVal = null;

            switch (_shape)
            {
                case ShapeType.Line:
                    #region Line

                    if (Math.Abs(dot) > _constrainMaxDotProduct)
                    {
                        retVal = null;
                    }
                    else
                    {
                        double dummy1;
                        retVal = CastRay_Line(out dummy1, _point, _direction, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    }

                    #endregion
                    break;

                case ShapeType.Lines:
                case ShapeType.Circles:
                case ShapeType.LinesCircles:
                    #region Lines/Circles

                    //NOTE: These have to look at _shape instead of null checks, because the values may be non null from a previous use

                    List<RayHitTestParameters> usableLines = new List<RayHitTestParameters>();
                    if (_shape == ShapeType.Lines || _shape == ShapeType.LinesCircles)
                    {
                        for (int cntr = 0; cntr < _lines.Length; cntr++)
                        {
                            if (Math.Abs(dots[cntr]) <= _constrainMaxDotProduct)
                            {
                                usableLines.Add(_lines[cntr]);
                            }
                        }
                    }

                    CircleDefinition[] usableCircles = null;
                    if (_shape == ShapeType.Circles || _shape == ShapeType.LinesCircles)
                    {
                        usableCircles = _circles;		// all circles are always used
                    }

                    retVal = CastRay_LinesCircles(usableLines, usableCircles, mouseDownClickRay, mouseDownCenterRay, currentClickRay);

                    #endregion
                    break;

                case ShapeType.Plane:
                    #region Plane

                    if (Math.Abs(dot) < 1d - _constrainMaxDotProduct)
                    {
                        retVal = CastRay_PlaneLimited(_plane, mouseDownClickRay, mouseDownCenterRay, currentClickRay, cameraLook);
                    }
                    else
                    {
                        retVal = CastRay_Plane(_plane, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    }

                    #endregion
                    break;

                case ShapeType.Cylinder:
                    #region Cylinder

                    if (Math.Abs(dot) > _constrainMaxDotProduct)
                    {
                        // Constrain to a circle
                        Vector3D circleVector1 = Math3D.GetArbitraryOrhonganal(_direction);
                        Vector3D circleVector2 = Vector3D.CrossProduct(circleVector1, _direction);
                        Triangle plane = new Triangle(_point, _point + circleVector1, _point + circleVector2);

                        double dummy1;
                        retVal = CastRay_Circle(out dummy1, plane, _point, _radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    }
                    else
                    {
                        retVal = CastRay_Cylinder(_point, _direction, _radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    }

                    #endregion
                    break;

                case ShapeType.Mesh:
                    throw new ApplicationException("finish this");

                case ShapeType.None:
                    retVal = null;
                    break;

                default:
                    throw new ApplicationException("Unknown ShapeType: " + _shape.ToString());
            }

            return retVal;
        }
Beispiel #25
0
        /// <summary>
        ///     Static helper used by ModelVisual3D and Viewport3DVisual to hit test
        ///     against their children collections.
        /// </summary>
        internal static HitTestResultBehavior HitTestChildren(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams,
            IVisual3DContainer container)
        {
            if (container != null)
            {
                int childrenCount = container.GetChildrenCount();
                
                for (int i = childrenCount - 1; i >= 0; i--)
                {
                    Visual3D child = container.GetChild(i);

                    // Visuall3D.RayHitTest does not apply the Visual3D's Transform.  We need to
                    // transform into the content's space before hit testing.
                    Transform3D transform = child.Transform;
                    rayParams.PushVisualTransform(transform);
                    
                    // Perform the hit-test against the child.
                    HitTestResultBehavior result = child.RayHitTest(filterCallback, rayParams);
            
                    rayParams.PopTransform(transform);
                    
                    if (result == HitTestResultBehavior.Stop)
                    {
                        return HitTestResultBehavior.Stop;
                    }
                }
            }

            return HitTestResultBehavior.Continue;
        }
        private static Point3D? CastRay_Line(out double clickDistanceSquared, Point3D point, Vector3D direction, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            // This is the offset along the drag line from the center to mouse down click
            Vector3D offset;

            Point3D? point1, point2, point3, point4;
            if (Math3D.GetClosestPoints_Line_Line(out point1, out point2, point, direction, mouseDownClickRay.Origin, mouseDownClickRay.Direction) &&
                Math3D.GetClosestPoints_Line_Line(out point3, out point4, point, direction, mouseDownCenterRay.Origin, mouseDownCenterRay.Direction))
            {
                offset = point3.Value - point1.Value;		// clickpoint on drag line minus centerpoint on drag line
            }
            else
            {
                // The click ray is parallel to the drag axis.  This should be extremely rare
                offset = new Vector3D(0, 0, 0);
            }

            // Now that the offset is known, project the current click ray onto the drag line
            if (Math3D.GetClosestPoints_Line_Line(out point1, out point2, point, direction, currentClickRay.Origin, currentClickRay.Direction))
            {
                clickDistanceSquared = (point2.Value - point1.Value).LengthSquared;
                return point1.Value + offset;
            }
            else
            {
                clickDistanceSquared = 0;
                return null;
            }
        }
        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);

            // 

            
            // 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)
            {
                // 




                // NTRAID#Longhorn-1180933-2004/07/30-danwo - 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;
        }
        private static Point3D? CastRay_Circle(out double clickDistanceSquared, ITriangle plane, Point3D center, double radius, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            clickDistanceSquared = 0d;		// this will get overwritten, but without setting it globally, the compiler will complain

            // Get points on the circle
            Point3D? mouseDownClickPoint = null;
            Point3D? mouseDownCenterPoint = null;
            Point3D? currentClickPoint = null;

            Point3D[] nearestCirclePoints, nearestLinePoints;

            if (Math3D.GetClosestPoints_Circle_Line(out nearestCirclePoints, out nearestLinePoints, plane, center, radius, currentClickRay.Origin, currentClickRay.Direction, Math3D.RayCastReturn.ClosestToRay))
            {
                clickDistanceSquared = (nearestLinePoints[0] - nearestCirclePoints[0]).LengthSquared;
                currentClickPoint = nearestCirclePoints[0];

                if (Math3D.GetClosestPoints_Circle_Line(out nearestCirclePoints, out nearestLinePoints, plane, center, radius, mouseDownClickRay.Origin, mouseDownClickRay.Direction, Math3D.RayCastReturn.ClosestToRay))
                {
                    mouseDownClickPoint = nearestCirclePoints[0];

                    if (Math3D.GetClosestPoints_Circle_Line(out nearestCirclePoints, out nearestLinePoints, plane, center, radius, mouseDownCenterRay.Origin, mouseDownCenterRay.Direction, Math3D.RayCastReturn.ClosestToRay))
                    {
                        mouseDownCenterPoint = nearestCirclePoints[0];
                    }
                }
            }

            if (mouseDownCenterPoint == null || mouseDownClickPoint == null || currentClickPoint == null)
            {
                clickDistanceSquared = 0d;
                return currentClickPoint;		// it doesn't matter if this one is null or not, the offset can't be determined, so just return the raw click value
            }

            // Get the offset
            Vector3D mouseDownClickLine = mouseDownClickPoint.Value - center;
            Vector3D mouseDownCenterLine = mouseDownCenterPoint.Value - center;

            Quaternion offset = Math3D.GetRotation(mouseDownClickLine, mouseDownCenterLine);

            // Convert to local, and rotate by offset
            Vector3D currentClickLine = currentClickPoint.Value - center;
            currentClickLine = new RotateTransform3D(new QuaternionRotation3D(offset)).Transform(currentClickLine);

            // Now convert back to world coords
            return center + currentClickLine;
        }
Beispiel #29
0
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------

        #region Private Methods

        //
        // Processes a ray-triangle intersection to see if it's a valid hit. Unnecessary faces
        // have already been culled by the ray-triange intersection routines.
        //
        // Shares some code with ValidateLineHit
        //
        private void ValidateRayHit(
            RayHitTestParameters rayParams,
            ref Point3D origin,
            ref Vector3D direction,
            double hitTime,
            int i0,
            int i1,
            int i2,
            ref Point barycentric
            )
        {
            if (hitTime > 0)
            {
                Matrix3D worldTransformMatrix = rayParams.HasWorldTransformMatrix ? rayParams.WorldTransformMatrix : Matrix3D.Identity;

                Point3D pointHit = origin + hitTime * direction;

                Point3D worldPointHit = pointHit;
                worldTransformMatrix.MultiplyPoint(ref worldPointHit);

                // 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.");
                }

                double dist = (worldPointHit - rayParams.Origin).Length;
                Debug.Assert(dist > 0, "Distance is negative: " + dist);

                if (rayParams.HasModelTransformMatrix)
                {
                    rayParams.ModelTransformMatrix.MultiplyPoint(ref pointHit);
                }

                rayParams.ReportResult(this, pointHit, dist, i0, i1, i2, barycentric);
            }
        }
        private static Point3D? CastRay_Sphere(Point3D center, double radius, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            // Get points on the sphere
            Point3D[] nearestSpherePoints, nearestLinePoints;
            Math3D.GetClosestPoints_Sphere_Line(out nearestSpherePoints, out nearestLinePoints, center, radius, currentClickRay.Origin, currentClickRay.Direction, Math3D.RayCastReturn.ClosestToRay);
            Point3D currentClickPoint = nearestSpherePoints[0];




            //TODO: The rest of this method is flawed, it should be fixed (I don't think it's as simplistic as I'm making it - or there is just a flaw in the math)
            // Actually, I think the flaw is taking it to world coords, rotating, and putting back?
            return currentClickPoint;




            //Math3D.GetClosestPointsBetweenLineSphere(out nearestSpherePoints, out nearestLinePoints, center, radius, mouseDownClickRay.Origin, mouseDownClickRay.Direction, Math3D.RayCastReturn.ClosestToRay);
            //Point3D mouseDownClickPoint = nearestSpherePoints[0];

            //Math3D.GetClosestPointsBetweenLineSphere(out nearestSpherePoints, out nearestLinePoints, center, radius, mouseDownCenterRay.Origin, mouseDownCenterRay.Direction, Math3D.RayCastReturn.ClosestToRay);
            //Point3D mouseDownCenterPoint = nearestSpherePoints[0];

            //// Get the offset from mouse down click to center
            //Vector3D mouseDownClickLine = mouseDownClickPoint - center;
            //Vector3D mouseDownCenterLine = mouseDownCenterPoint - center;

            //Quaternion offset = Math3D.GetRotation(mouseDownClickLine, mouseDownCenterLine);

            //// Convert to local coords, rotate
            //Vector3D currentClickLine = currentClickPoint - center;
            //currentClickLine = new RotateTransform3D(new QuaternionRotation3D(offset)).Transform(currentClickLine);

            //// Now convert back to world coords
            //return center + currentClickLine;
        }
        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);
        }
Beispiel #32
0
        internal HitTestResultBehavior RayHitTest(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams)
        {
            if (DoesRayHitSubgraphBounds(rayParams))
            {
                //
                // Determine if there is a special filter behavior defined for this
                // Visual.
                //

                HitTestFilterBehavior behavior = HitTestFilterBehavior.Continue;

                if (filterCallback != null)
                {
                    behavior = filterCallback(this);

                    if (HTFBInterpreter.SkipSubgraph(behavior)) return HitTestResultBehavior.Continue;
                    if (HTFBInterpreter.Stop(behavior)) return HitTestResultBehavior.Stop;
                }

                //
                // Hit test against the children.
                //

                if (HTFBInterpreter.IncludeChildren(behavior))
                {
                    HitTestResultBehavior result = HitTestChildren(filterCallback, rayParams);

                    if (result == HitTestResultBehavior.Stop) return HitTestResultBehavior.Stop;
                }

                //
                // Hit test against the content of this Visual.
                //

                if (HTFBInterpreter.DoHitTest(behavior))
                {
                    RayHitTestInternal(filterCallback, rayParams);
                }
            }

            return HitTestResultBehavior.Continue;
        }
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------

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

        #region Public Properties

        internal override void RayHitTestCore(RayHitTestParameters rayParams)
        {
            // Lights are considered to be part of the model graph, but they
            // have no geometry and therefore can not be hit tested.
        }
Beispiel #34
0
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
        
        #region Internal Methods

        internal bool DoesRayHitSubgraphBounds(RayHitTestParameters rayParams)
        {
            Point3D origin;
            Vector3D direction;
            rayParams.GetLocalLine(out origin, out direction);

            Rect3D bboxSubgraph = VisualDescendantBounds;
            return LineUtil.ComputeLineBoxIntersection(ref origin, ref direction, ref bboxSubgraph, rayParams.IsRay);
        }
        private static Point3D? CastRay_LinesCircles(IEnumerable<RayHitTestParameters> lines, IEnumerable<CircleDefinition> circles, RayHitTestParameters ray)
        {
            Point3D? retVal = null;
            double distance = double.MaxValue;

            if (lines != null)
            {
                // Cast onto each line, and return the point that's closest to the ray's origin
                foreach (RayHitTestParameters line in lines)
                {
                    Point3D? point1, point2;
                    if (Math3D.GetClosestPoints_Line_Line(out point1, out point2, line.Origin, line.Direction, ray.Origin, ray.Direction))
                    {
                        double localDistance = (point2.Value - point1.Value).LengthSquared;
                        if (retVal == null || localDistance < distance)
                        {
                            retVal = point1.Value;
                            distance = localDistance;
                        }
                    }
                }
            }

            if (circles != null)
            {
                // Cast onto each circle, and return the point that's closest to the ray's origin
                foreach (CircleDefinition circle in circles)
                {
                    Point3D[] points1, points2;
                    if (Math3D.GetClosestPoints_Circle_Line(out points1, out points2, circle.Plane, circle.Center, circle.Radius, ray.Origin, ray.Direction, Math3D.RayCastReturn.ClosestToRay))
                    {
                        double localDistance = (points2[0] - points1[0]).LengthSquared;
                        if (retVal == null || localDistance < distance)
                        {
                            retVal = points1[0];
                            distance = localDistance;
                        }
                    }
                }
            }

            return retVal;
        }
Beispiel #36
0
 internal HitTestResultBehavior HitTestChildren(
     HitTestFilterCallback filterCallback,
     RayHitTestParameters rayParams)
 {
     return HitTestChildren(filterCallback, rayParams, this);
 }
        public void GetShape_Line(out RayHitTestParameters line)
        {
            if (_shape != ShapeType.Line)
            {
                throw new InvalidOperationException("This class isn't set up for a line: " + _shape.ToString());
            }

            line = new RayHitTestParameters(_point, _direction);
        }
Beispiel #38
0
        internal void RayHitTestInternal(
            HitTestFilterCallback filterCallback,
            RayHitTestParameters rayParams)
        {
            Model3D model = _visual3DModel;
            
            if (model != null)
            {
                // If our Model3D hit test intersects anything we should return "this" Visual3D
                // as the HitTestResult.VisualHit.
                rayParams.CurrentVisual = this;

                model.RayHitTest(rayParams);
            }
        }
        private static Point3D? CastRay_LinesCircles(IEnumerable<RayHitTestParameters> lines, IEnumerable<CircleDefinition> circles, RayHitTestParameters mouseDownClickRay, RayHitTestParameters mouseDownCenterRay, RayHitTestParameters currentClickRay)
        {
            Point3D? retVal = null;
            double distance = double.MaxValue;

            if (lines != null)
            {
                // Cast onto each line, and return the point that's closest to the ray's origin
                foreach (RayHitTestParameters line in lines)
                {
                    double localDistance;
                    Point3D? localPoint = CastRay_Line(out localDistance, line.Origin, line.Direction, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    if (localPoint != null)
                    {
                        if (retVal == null || localDistance < distance)
                        {
                            retVal = localPoint.Value;
                            distance = localDistance;
                        }
                    }
                }
            }

            if (circles != null)
            {
                // Cast onto each circle, and return the point that's closest to the ray's origin
                foreach (CircleDefinition circle in circles)
                {
                    double localDistance;
                    Point3D? localPoint = CastRay_Circle(out localDistance, circle.Plane, circle.Center, circle.Radius, mouseDownClickRay, mouseDownCenterRay, currentClickRay);
                    if (localPoint != null)
                    {
                        if (retVal == null || localDistance < distance)
                        {
                            retVal = localPoint.Value;
                            distance = localDistance;
                        }
                    }
                }
            }

            return retVal;
        }
Beispiel #40
0
 internal abstract void RayHitTestCore(RayHitTestParameters rayParams, FaceType hitTestableFaces);
        public void GetShape_Cylinder(out RayHitTestParameters axis, out double radius)
        {
            if (_shape != ShapeType.Cylinder)
            {
                throw new InvalidOperationException("This class isn't set up for a cylinder: " + _shape.ToString());
            }

            axis = new RayHitTestParameters(_point, _direction);
            radius = _radius;
        }
        //
        // Hits the ray against the mesh
        //
        internal override void RayHitTestCore(
            RayHitTestParameters rayParams,
            FaceType hitTestableFaces)
        {
            Debug.Assert(hitTestableFaces != FaceType.None, 
                "Caller should make sure we're trying to hit something");

            Point3DCollection positions = Positions;
            if (positions == null)
            {
                return;
            }

            Point3D origin;
            Vector3D direction;

            rayParams.GetLocalLine(out origin, out direction);

            Int32Collection indices = TriangleIndices;

            // In the line case, we want to hit test all faces because we don't
            // have a direction. This may differ from what faces we want to
            // accept.
            FaceType facesToHit;
            if (rayParams.IsRay)
            {
                facesToHit = hitTestableFaces;
            }
            else
            {
                facesToHit = FaceType.Front | FaceType.Back;
            }

            
            //
            // This code duplication is unfortunate but necessary. Breaking it down into methods 
            // further significantly impacts performance. About 5% improvement could be made
            // by unrolling this code below even more.
            //
            // If futher perf investigation is done with this code, be sure to test NGEN assemblies only
            // as JIT produces different, faster code than NGEN.
            //
            
            if (indices == null || indices.Count == 0)
            {
                FrugalStructList<Point3D> ps = positions._collection; 
                int count = ps.Count - (ps.Count % 3);
                
                for (int i = count - 1; i >= 2; i -= 3)
                {
                    int i0 = i - 2;
                    int i1 = i - 1;
                    int i2 = i;
                    
                    Point3D v0 = ps[i0];
                    Point3D v1 = ps[i1];
                    Point3D v2 = ps[i2];
                
                    double hitTime;
                    Point barycentric;

                    // The line hit test is equivalent to a double sided
                    // triangle hit because it doesn't cull triangles based
                    // on winding
                    if (LineUtil.ComputeLineTriangleIntersection(
                            facesToHit,
                            ref origin,
                            ref direction,
                            ref v0,
                            ref v1,
                            ref v2,
                            out barycentric,
                            out hitTime
                            )
                        )
                    {        
                        
                        if (rayParams.IsRay)
                        {                          
                            ValidateRayHit(
                                rayParams, 
                                ref origin, 
                                ref direction, 
                                hitTime,
                                i0,
                                i1,
                                i2,
                                ref barycentric
                                );
                        }
                        else
                        {
                            ValidateLineHit(
                                rayParams, 
                                hitTestableFaces, 
                                i0,
                                i1,
                                i2,
                                ref v0,
                                ref v1,
                                ref v2,
                                ref barycentric
                                );
                        }
                    }
                }

            }
            else // indexed mesh
            {
                FrugalStructList<Point3D> ps = positions._collection;
                FrugalStructList<int> idcs = indices._collection;
                
                int count = idcs.Count;
                int limit = ps.Count;
                                          
                for (int i = 2; i < count; i += 3)
                {
                    int i0 = idcs[i - 2];
                    int i1 = idcs[i - 1];
                    int i2 = idcs[i];
                
                    // Quit if we encounter an index out of range.
                    // This is okay because the triangles we ignore are not rendered.
                    //  (see: CMilMeshGeometry3DDuce::Realize)
                    if ((0 > i0 || i0 >= limit) ||
                        (0 > i1 || i1 >= limit) ||
                        (0 > i2 || i2 >= limit))
                    {
                        break;
                    }
                
                    Point3D v0 = ps[i0];
                    Point3D v1 = ps[i1];
                    Point3D v2 = ps[i2];
                
                    double hitTime;
                    Point barycentric;
                    
                    if (LineUtil.ComputeLineTriangleIntersection(
                            facesToHit,
                            ref origin,
                            ref direction,
                            ref v0,
                            ref v1,
                            ref v2,
                            out barycentric,
                            out hitTime
                            )
                        )
                    {        
                        if (rayParams.IsRay)
                        {   
                            ValidateRayHit(
                                rayParams, 
                                ref origin, 
                                ref direction, 
                                hitTime,
                                i0,
                                i1,
                                i2,
                                ref barycentric
                                );
                        }
                        else
                        {
                            ValidateLineHit(
                                rayParams, 
                                hitTestableFaces, 
                                i0,
                                i1,
                                i2,
                                ref v0,
                                ref v1,
                                ref v2,
                                ref barycentric
                                );
                        }
                    }
                }
            }
        }
 public void SetShape_Line(RayHitTestParameters line)
 {
     _shape = ShapeType.Line;
     _point = line.Origin;
     _direction = line.Direction;
 }
        //
        // 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);
                }
            }          
        }
 public void SetShape_Cylinder(RayHitTestParameters axis, double radius)
 {
     _shape = ShapeType.Cylinder;
     _point = axis.Origin;
     _direction = axis.Direction;
     _radius = radius;
 }