/// <summary> /// Computes the angle between two vectors. /// </summary> /// <param name="vector1">First vector.</param> /// <param name="vector2">Second vector.</param> /// <returns> /// Returns the angle required to rotate vector1 into vector2 in degrees. /// This will return a value between [0, 180] degrees. /// (Note that this is slightly different from the Vector member /// function of the same name. Signed angles do not extend to 3D.) /// </returns> public static double AngleBetween(Vector3D vector1, Vector3D vector2) { vector1.Normalize(); vector2.Normalize(); double ratio = DotProduct(vector1, vector2); // The "straight forward" method of acos(u.v) has large precision // issues when the dot product is near +/-1. This is due to the // steep slope of the acos function as we approach +/- 1. Slight // precision errors in the dot product calculation cause large // variation in the output value. // // | | // \__ | // ---___ | // ---___ | // ---_|_ // | ---___ // | ---___ // | ---__ // | \ // | | // -|-------------------+-------------------|- // -1 0 1 // // acos(x) // // To avoid this we use an alternative method which finds the // angle bisector by (u-v)/2: // // _> // u _- \ (u-v)/2 // _- __-v // _=__-- // .=-----------> // v // // Because u and v and unit vectors, (u-v)/2 forms a right angle // with the angle bisector. The hypotenuse is 1, therefore // 2*asin(|u-v|/2) gives us the angle between u and v. // // The largest possible value of |u-v| occurs with perpendicular // vectors and is sqrt(2)/2 which is well away from extreme slope // at +/-1. // double theta; if (ratio < 0) { theta = Math.PI - 2.0 * Math.Asin((-vector1 - vector2).Length / 2.0); } else { theta = 2.0 * Math.Asin((vector1 - vector2).Length / 2.0); } return(M3DUtil.RadiansToDegrees(theta)); }
//------------------------------------------------------ // // Public Methods // //------------------------------------------------------ //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ //------------------------------------------------------ // // Public Events // //------------------------------------------------------ //------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods internal Matrix3D GetProjectionMatrix(double aspectRatio, double zn, double zf) { double fov = M3DUtil.DegreesToRadians(FieldOfView); // Note: h and w are 1/2 of the inverse of the width/height ratios: // // h = 1/(heightDepthRatio) * (1/2) // w = 1/(widthDepthRatio) * (1/2) // // Computation for h is a bit different than what you will find in // D3DXMatrixPerspectiveFovRH because we have a horizontal rather // than vertical FoV. double halfWidthDepthRatio = Math.Tan(fov / 2); double h = aspectRatio / halfWidthDepthRatio; double w = 1 / halfWidthDepthRatio; double m22 = zf != Double.PositiveInfinity ? zf / (zn - zf) : -1; double m32 = zn * m22; return(new Matrix3D( w, 0, 0, 0, 0, h, 0, 0, 0, 0, m22, -1, 0, 0, m32, 0)); }
private void Debug_VerifyCachedBounds() { Rect3D actualBounds = M3DUtil.ComputeAxisAlignedBoundingBox(Positions); // The funny boolean logic below avoids asserts when the cached // bounds contain NaNs. (NaN != NaN) bool areEqual = !(_cachedBounds.X <actualBounds.X || _cachedBounds.X> actualBounds.X) && !(_cachedBounds.Y <actualBounds.Y || _cachedBounds.Y> actualBounds.Y) && !(_cachedBounds.Z <actualBounds.Z || _cachedBounds.Z> actualBounds.Z) && !(_cachedBounds.SizeX <actualBounds.SizeX || _cachedBounds.SizeX> actualBounds.SizeX) && !(_cachedBounds.SizeY <actualBounds.SizeY || _cachedBounds.SizeY> actualBounds.SizeY) && !(_cachedBounds.SizeZ <actualBounds.SizeZ || _cachedBounds.SizeZ> actualBounds.SizeZ); if (!areEqual) { if (_cachedBounds == Rect3D.Empty) { Debug.Fail("Cached bounds are invalid. Caller needs to check for IsEmpty and call UpdateCachedBounds."); } else { Debug.Fail("Cached bounds are invalid. We missed a call to SetCachedBoundsDirty."); } } }
// Updates the _cachedBounds member to the current bounds of the mesh. // This method must be called before accessing _cachedBounds if // _cachedBounds.IsEmpty is true. Otherwise the _cachedBounds are // current and do not need to be recomputed. See also Debug_VerifyCachedBounds. private void UpdateCachedBounds() { Debug.Assert(_cachedBounds.IsEmpty, "PERF: Caller should verify that bounds are dirty before recomputing."); _cachedBounds = M3DUtil.ComputeAxisAlignedBoundingBox(Positions); }
public GameObject CreateBrick(int type) { GameObject brick = GameObject.Instantiate(m_BrickPrefab) as GameObject; GameObject child = CreateBrick_Child(type); if (child != null) { child.transform.parent = brick.transform; M3DUtil.InitLocalTf(child.transform); } return(brick); }
public void SetBrickChild(GameObject newChild) { if (this.transform.childCount != 0) { Transform child = this.transform.GetChild(0); GameObject.DestroyImmediate(child.gameObject); child = null; } if (newChild != null) { newChild.transform.parent = this.transform; M3DUtil.InitLocalTf(newChild.transform); } }
internal override Rect CalculateSubgraphBoundsInnerSpace(bool renderBounds) { Camera camera = Camera; if (camera == null) { return(Rect.Empty); } // // Cache the 3D bounding box for future use. Here we are relying on // the fact that this method is called by PrecomputeContent(), // which is called prior to all usages of _bboxChildrenSubgraph3D. // _bboxChildrenSubgraph3D = ComputeSubgraphBounds3D(); if (_bboxChildrenSubgraph3D.IsEmpty) { // Attempting to project empty bounds will result in NaNs which // which will ruin descendant bounds for the 2D tree. We handle // this explicitly and early exit with the correct answer. return(Rect.Empty); } Rect viewport = Viewport; // Common Case: Viewport3DVisual in a collasped UIElement. if (viewport.IsEmpty) { // Creating a 3D homogenous space to 2D viewport space transform // with an empty rectangle will result in NaNs which ruin the // descendant bounds for the 2D tree. We handle this explicitly // and early exit with the correct answer. // // (See also Windows OS Bugs #1637618) return(Rect.Empty); } double aspectRatio = M3DUtil.GetAspectRatio(viewport.Size); Matrix3D viewProjMatrix = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio); Rect projectedBounds2D = MILUtilities.ProjectBounds(ref viewProjMatrix, ref _bboxChildrenSubgraph3D); Matrix homoToLocal = M3DUtil.GetHomogeneousToViewportTransform(viewport); MatrixUtil.TransformRect(ref projectedBounds2D, ref homoToLocal); return(projectedBounds2D); }
internal Point WorldToViewport(Point4D point) { double aspectRatio = M3DUtil.GetAspectRatio(Viewport.Size); Camera camera = Camera; if (camera != null) { Matrix3D viewProjMatrix = camera.GetViewMatrix() * camera.GetProjectionMatrix(aspectRatio); point *= viewProjMatrix; Point point2D = new Point(point.X / point.W, point.Y / point.W); point2D *= M3DUtil.GetHomogeneousToViewportTransform(Viewport); return(point2D); } else { return(new Point(0, 0)); } }
internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment) { // The camera may be animating. Take a snapshot of the current value // and get the property values we need. (Window OS #992662) Point3D position = Position; Vector3D lookDirection = LookDirection; Vector3D upDirection = UpDirection; Transform3D transform = Transform; double zn = NearPlaneDistance; double zf = FarPlaneDistance; double fov = M3DUtil.DegreesToRadians(FieldOfView); // // Compute rayParameters // // Find the point on the projection plane in post-projective space where // the viewport maps to a 2x2 square from (-1,1)-(1,-1). Point np = M3DUtil.GetNormalizedPoint(p, viewSize); // Note: h and w are 1/2 of the inverse of the width/height ratios: // // h = 1/(heightDepthRatio) * (1/2) // w = 1/(widthDepthRatio) * (1/2) // // Computation for h is a bit different than what you will find in // D3DXMatrixPerspectiveFovRH because we have a horizontal rather // than vertical FoV. double aspectRatio = M3DUtil.GetAspectRatio(viewSize); double halfWidthDepthRatio = Math.Tan(fov / 2); double h = aspectRatio / halfWidthDepthRatio; double w = 1 / halfWidthDepthRatio; // To get from projective space to camera space we apply the // width/height ratios to find our normalized point at 1 unit // in front of the camera. (1 is convenient, but has no other // special significance.) See note above about the construction // of w and h. Vector3D rayDirection = new Vector3D(np.X / w, np.Y / h, -1); // Apply the inverse of the view matrix to our rayDirection vector // to convert it from camera to world space. // // NOTE: Because our construction of the ray assumes that the // viewMatrix translates the position to the origin we pass // null for the Camera.Transform below and account for it // later. Matrix3D viewMatrix = CreateViewMatrix(/* trasform = */ null, ref position, ref lookDirection, ref upDirection); Matrix3D invView = viewMatrix; invView.Invert(); invView.MultiplyVector(ref rayDirection); // The we have the ray direction, now we need the origin. The camera's // position would work except that we would intersect geometry between // the camera plane and the near plane so instead we must find the // point on the project plane where the ray (position, rayDirection) // intersect (Windows OS #1005064): // // | _.> p = camera position // rd _+" ld = camera look direction // .-" |ro pp = projection plane // _.-" | rd = ray direction // p +"--------+---> ro = desired ray origin on pp // ld | // pp // // Above we constructed the direction such that it's length projects to // 1 unit on the lookDirection vector. // // // rd _.> // .-" rd = unnormalized rayDirection // _.-" ld = normalized lookDirection (length = 1) // -"---------> // ld // // So to find the desired rayOrigin on the projection plane we simply do: Point3D rayOrigin = position + zn * rayDirection; rayDirection.Normalize(); // Account for the Camera.Transform we ignored during ray construction above. if (transform != null && transform != Transform3D.Identity) { Matrix3D m = transform.Value; m.MultiplyPoint(ref rayOrigin); m.MultiplyVector(ref rayDirection); PrependInverseTransform(m, ref viewMatrix); } RayHitTestParameters rayParameters = new RayHitTestParameters(rayOrigin, rayDirection); // // Compute HitTestProjectionMatrix // Matrix3D projectionMatrix = GetProjectionMatrix(aspectRatio, zn, zf); // The projectionMatrix takes camera-space 3D points into normalized clip // space. // The viewportMatrix will take normalized clip space into // viewport coordinates, with an additional 2D translation // to put the ray at the rayOrigin. Matrix3D viewportMatrix = new Matrix3D(); viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0)); viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1)); viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0)); // `First world-to-camera, then camera's projection, then normalized clip space to viewport. rayParameters.HitTestProjectionMatrix = viewMatrix * projectionMatrix * viewportMatrix; // // Perspective camera doesn't allow negative NearPlanes, so there's // not much point in adjusting the ray origin. Hence, the // distanceAdjustment remains 0. // distanceAdjustment = 0.0; return(rayParameters); }
internal static bool Get3DPointFor2DCoordinate(Point point, out Point3D point3D, Point3DCollection positions, PointCollection textureCoords, Int32Collection triIndices) { point3D = new Point3D(); // walk through the triangles - and look for the triangles we care about Point3D[] p = new Point3D[3]; Point[] uv = new Point[3]; if (positions != null && textureCoords != null) { if (triIndices == null || triIndices.Count == 0) { int texCoordCount = textureCoords.Count; // in this case we have a non-indexed mesh int count = positions.Count; count = count - (count % 3); for (int i = 0; i < count; i += 3) { for (int j = 0; j < 3; j++) { p[j] = positions[i + j]; if (i + j < texCoordCount) { uv[j] = textureCoords[i + j]; } else { // In the case you have less texture coordinates than positions, MIL will set // missing ones to be 0,0. We do the same to stay consistent. // See CMILMesh3D::CopyTextureCoordinatesFromDoubles uv[j] = new Point(0, 0); } } if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D)) { return(true); } } } else { // in this case we have an indexed mesh int posLimit = positions.Count; int texCoordLimit = textureCoords.Count; for (int i = 2, count = triIndices.Count; i < count; i += 3) { bool validTextureCoordinates = true; for (int j = 0; j < 3; j++) { // subtract 2 to take in to account we start i // at the high range of indices int index = triIndices[(i - 2) + j]; // if a point or texture coordinate is out of range, end early since this is an error if (index < 0 || index >= posLimit) { // no need to look anymore - see MeshGeometry3D RayHitTestIndexedList for // reasoning why we stop return(false); } if (index < 0 || index >= texCoordLimit) { validTextureCoordinates = false; break; } p[j] = positions[index]; uv[j] = textureCoords[index]; } if (validTextureCoordinates) { if (M3DUtil.IsPointInTriangle(point, uv, p, out point3D)) { return(true); } } } } } return(false); }
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); }
/// <summary> /// Transforms the bounding box to the smallest axis aligned bounding box /// that contains all the points in the original bounding box /// </summary> /// <param name="rect">Bounding box</param> /// <returns>The transformed bounding box</returns> public override Rect3D TransformBounds(Rect3D rect) { return(M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref rect, this)); }
internal override RayHitTestParameters RayFromViewportPoint(Point p, Size viewSize, Rect3D boundingRect, out double distanceAdjustment) { // The camera may be animating. Take a snapshot of the current value // and get the property values we need. (Window OS #992662) Point3D position = Position; Vector3D lookDirection = LookDirection; Vector3D upDirection = UpDirection; double zn = NearPlaneDistance; double zf = FarPlaneDistance; double width = Width; // // Compute rayParameters // // Find the point on the projection plane in post-projective space where // the viewport maps to a 2x2 square from (-1,1)-(1,-1). Point np = M3DUtil.GetNormalizedPoint(p, viewSize); double aspectRatio = M3DUtil.GetAspectRatio(viewSize); double w = width; double h = w / aspectRatio; // Direction is always perpendicular to the viewing surface. Vector3D direction = new Vector3D(0, 0, -1); // Apply the inverse of the view matrix to our ray. Matrix3D viewMatrix = CreateViewMatrix(Transform, ref position, ref lookDirection, ref upDirection); Matrix3D invView = viewMatrix; invView.Invert(); // We construct our ray such that the origin resides on the near // plane. If our near plane is too far from our the bounding box // of our scene then the results will be inaccurate. (e.g., // OrthographicCameras permit negative near planes, so the near // plane could be at -Inf.) // // However, it is permissable to move the near plane nearer to // the scene bounds without changing what the ray intersects. // If the near plane is sufficiently far from the scene bounds // we make this adjustment below to increase precision. Rect3D transformedBoundingBox = M3DUtil.ComputeTransformedAxisAlignedBoundingBox( ref boundingRect, ref viewMatrix); // DANGER: The NearPlaneDistance property is specified as a // distance from the camera position along the // LookDirection with (Near < Far). // // However, when we transform our scene bounds so that // the camera is aligned with the negative Z-axis the // relationship inverts (Near > Far) as illustrated // below: // // NearPlane Y FarPlane // | ^ | // | | | // | | (rect.Z + rect.SizeZ) | // | | o____ | // | | | | | // | | | | | // | | ____o | // | | (rect.Z) | // | Camera -> | // +Z <----------+----------------------------> -Z // | 0 | // // It is surprising, but its the "far" side of the // transformed scene bounds that determines the near // plane distance. double zn2 = -AddEpsilon(transformedBoundingBox.Z + transformedBoundingBox.SizeZ); if (zn2 > zn) { // // Our near plane is far from our children. Construct a new // near plane that's closer. Note that this will modify our // distance computations, so we have to be sure to adjust our // distances appropriately. // distanceAdjustment = zn2 - zn; zn = zn2; } else { // // Our near plane is either close to or in front of our // children, so let's keep it -- no distance adjustment needed. // distanceAdjustment = 0.0; } // Our origin is the point normalized to the front of our viewing volume. // To find our origin's x/y we just need to scale the normalize point by our // width/height. In camera space we are looking down the negative Z axis // so we just set Z to be -zn which puts us on the projection plane // (Windows OS #1005064). Point3D origin = new Point3D(np.X * (w / 2), np.Y * (h / 2), -zn); invView.MultiplyPoint(ref origin); invView.MultiplyVector(ref direction); RayHitTestParameters rayParameters = new RayHitTestParameters(origin, direction); // // Compute HitTestProjectionMatrix // Matrix3D projectionMatrix = GetProjectionMatrix(aspectRatio, zn, zf); // The projectionMatrix takes camera-space 3D points into normalized clip // space. // The viewportMatrix will take normalized clip space into // viewport coordinates, with an additional 2D translation // to put the ray at the origin. Matrix3D viewportMatrix = new Matrix3D(); viewportMatrix.TranslatePrepend(new Vector3D(-p.X, viewSize.Height - p.Y, 0)); viewportMatrix.ScalePrepend(new Vector3D(viewSize.Width / 2, -viewSize.Height / 2, 1)); viewportMatrix.TranslatePrepend(new Vector3D(1, 1, 0)); // `First world-to-camera, then camera's projection, then normalized clip space to viewport. rayParameters.HitTestProjectionMatrix = viewMatrix * projectionMatrix * viewportMatrix; return(rayParameters); }
/// <summary> /// Returns the bounds of the Model3D subgraph rooted at this Model3D. /// /// Outer space refers to the space after this Model's Transform is /// applied -- or said another way, applying a transform to this Model /// affects it's outer bounds. (While its inner bounds remain unchanged.) /// </summary> internal Rect3D CalculateSubgraphBoundsOuterSpace() { Rect3D innerBounds = CalculateSubgraphBoundsInnerSpace(); return(M3DUtil.ComputeTransformedAxisAlignedBoundingBox(ref innerBounds, Transform)); }
// // 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); } } }