//------------------------------------------------------ // // 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); } } }
//------------------------------------------------------ // // 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); }
//------------------------------------------------------ // // 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); }
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; }
//------------------------------------------------------ // // 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 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; }
// // 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); } }
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; } }
// // 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); }
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); }
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; }
/// <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; }
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); }
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. }
//------------------------------------------------------ // // 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; }
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); }
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; }
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; }
public void SetShape_Cylinder(RayHitTestParameters axis, double radius) { _shape = ShapeType.Cylinder; _point = axis.Origin; _direction = axis.Direction; _radius = radius; }