File: MS\Internal\Media3D\GeneralTransform2DTo3DTo2D.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using MS.Internal;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Media.Composition;
using System.Windows.Markup;
 
using System.Windows.Media;
using System.Windows.Media.Media3D;
 
using MS.Internal.Media3D;
using SR = MS.Internal.PresentationCore.SR;
 
namespace MS.Internal.Media3D
{
    /// <summary>
    /// Helper class that encapsulates return data needed for the
    /// hit test capture methods.
    /// </summary>
    internal class HitTestEdge
    {
        /// <summary>
        /// Constructs a new hit test edge
        /// </summary>
        /// <param name="p1">First edge point</param>
        /// <param name="p2">Second edge point</param>
        /// <param name="uv1">Texture coordinate of first edge point</param>
        /// <param name="uv2">Texture coordinate of second edge point</param>
        public HitTestEdge(Point3D p1,
                           Point3D p2,
                           Point uv1,
                           Point uv2)
        {
            _p1 = p1;
            _p2 = p2;
 
            _uv1 = uv1;
            _uv2 = uv2;
        }
 
        /// <summary>
        /// Projects the stored 3D points in to 2D.
        /// </summary>
        /// <param name="objectToViewportTransform">The transformation matrix to use</param>
        public void Project(GeneralTransform3DTo2D objectToViewportTransform)
        {
            Point projPoint1 = objectToViewportTransform.Transform(_p1);
            Point projPoint2 = objectToViewportTransform.Transform(_p2);
 
            _p1Transformed = new Point(projPoint1.X, projPoint1.Y);
            _p2Transformed = new Point(projPoint2.X, projPoint2.Y);
        }
 
        internal Point3D _p1, _p2;
        internal Point _uv1, _uv2;
 
        // the transformed Point3D value
        internal Point _p1Transformed, _p2Transformed;
    }
    
    /// <summary>
    /// This transform allows one to go from 2D through 3D and back in to 2D
    /// </summary>
    internal class GeneralTransform2DTo3DTo2D : GeneralTransform
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="visual3D">The Visual3D that contains the 2D visual</param>
        /// <param name="fromVisual">The visual on the Visual3D</param>
        internal GeneralTransform2DTo3DTo2D(Viewport2DVisual3D visual3D, Visual fromVisual)
        {
            IsInverse = false;
 
            // get a copy of the geometry information - we store our own model to reuse hit
            // test code on the GeometryModel3D
            _geometry = new MeshGeometry3D();
            _geometry.Positions = visual3D.InternalPositionsCache;
            _geometry.TextureCoordinates = visual3D.InternalTextureCoordinatesCache;
            _geometry.TriangleIndices = visual3D.InternalTriangleIndicesCache;
            _geometry.Freeze();
 
            Visual visual3Dchild = visual3D.Visual;
 
            // Special case - Setting CacheMode on V2DV3D causes an internal switch from using a VisualBrush
            // to using a BitmapCacheBrush.  It also introduces an extra 2D Visual in the Visual tree above 
            // the V2DV3D.Visual, but this extra node has no effect on transforms and can safely be ignored.  
            // The transform returned will be identical to the one created for calling TransformTo* with
            // the V2DV3D.Visual itself.
            Visual descendentVisual = (fromVisual == visual3Dchild._parent) ? visual3Dchild : fromVisual;
 
            // get a copy of the size of the visual brush and the rect on the 
            // visual that the transform is going to/from
 
            _visualBrushBounds = visual3Dchild.CalculateSubgraphRenderBoundsOuterSpace();
            _visualBounds = descendentVisual.CalculateSubgraphRenderBoundsInnerSpace();
 
            // get the transform that will let us go from the fromVisual to its last 2D
            // parent before it reaches the 3D part of the graph (i.e. visual3D.Child)
            GeneralTransformGroup transformGroup = new GeneralTransformGroup();
            transformGroup.Children.Add(descendentVisual.TransformToAncestor(visual3Dchild));
            transformGroup.Children.Add(visual3Dchild.TransformToOuterSpace());
            transformGroup.Freeze();
            _transform2D = transformGroup;
 
            // store the inverse as well
            _transform2DInverse = (GeneralTransform)_transform2D.Inverse;
            if (_transform2DInverse != null)
            {
                _transform2DInverse.Freeze();
            }
 
            // make a copy of the camera and other values on the Viewport3D
            Viewport3DVisual viewport3D = (Viewport3DVisual)VisualTreeHelper.GetContainingVisual2D(visual3D);
 
            _camera = viewport3D.Camera;
            if (_camera != null)
            {
                _camera = (Camera)viewport3D.Camera.GetCurrentValueAsFrozen();
            }
            
            _viewSize = viewport3D.Viewport.Size;
            _boundingRect = viewport3D.ComputeSubgraphBounds3D();
            _objectToViewport = visual3D.TransformToAncestor(viewport3D);
 
            // if the transform was not possible, it could be null - check before freezing
            if (_objectToViewport != null)
            {
                _objectToViewport.Freeze();
            }
            
            // store the needed transformations for the various operations
            _worldTransformation = M3DUtil.GetWorldTransformationMatrix(visual3D);
 
            _validEdgesCache = null;            
        }
 
        internal GeneralTransform2DTo3DTo2D()
        {
        }
        
        /// <summary>
        /// Transforms a point
        /// </summary>
        /// <param name="inPoint">input point</param>
        /// <param name="result">output point</param>
        /// <returns>false if the point cannot be transformed</returns>
        public override bool TryTransform(Point inPoint, out Point result)
        {
            if (IsInverse)
            {
                return TryInverseTransform(inPoint, out result);
            }
            else
            {
                return TryRegularTransform(inPoint, out result);
            }
        }
 
        /// <summary>
        /// Performs the transform that goes from the parent Viewport3DVisual down in to the 2D on 3D
        /// contained within the 3D scene
        /// </summary>
        /// <param name="inPoint">input point</param>
        /// <param name="result">output point</param>
        /// <returns>false if the point cannot be transformed</returns>
        private bool TryInverseTransform(Point inPoint, out Point result)
        {
            // set up the hit test parameters            
            double distanceAdjust;
            bool foundIntersection = false;
            
            if (_camera != null)
            {
                RayHitTestParameters rayHitTestParameters = _camera.RayFromViewportPoint(inPoint,
                                                                                         _viewSize,
                                                                                         _boundingRect,
                                                                                         out distanceAdjust);
                rayHitTestParameters.PushVisualTransform(new MatrixTransform3D(_worldTransformation));
 
                // perfrom the hit test
                // no back material so we only need to concern ourselves with the front faces
                Point pointHit = new Point();
                _geometry.RayHitTest(rayHitTestParameters, FaceType.Front);
                rayHitTestParameters.RaiseCallback(delegate (HitTestResult rawresult)
                                                   {
                                                       RayHitTestResult rayResult = rawresult as RayHitTestResult;
 
                                                       if (rayResult != null)
                                                       {
                                                           foundIntersection = Viewport2DVisual3D.GetIntersectionInfo(rayResult, out pointHit);
                                                       }
 
                                                       return HitTestResultBehavior.Stop;
                                                   },
                                                   null,
                                                   HitTestResultBehavior.Continue,
                                                   distanceAdjust);
 
                // perform capture positioning if we didn't hit anything and something has capture
                if (!foundIntersection)
                {
                    foundIntersection = HandleOffMesh(inPoint, out pointHit);                    
                }
                
                // compute final point
                result = Viewport2DVisual3D.TextureCoordsToVisualCoords(pointHit, _visualBrushBounds);        
            }
            else
            {
                result = new Point();
            }
 
            return foundIntersection;
        }
        
        /// <summary>
        /// Function to deal with mouse capture when off the mesh.
        /// </summary>
        /// <param name="mousePos">The location of the mouse</param>
        /// <param name="outPoint">output point</param>
        private bool HandleOffMesh(Point mousePos, out Point outPoint)
        {
            Point[] visCorners = new Point[4];
 
            if (_validEdgesCache == null)
            {
                // get the points relative to the parent
                visCorners[0] = _transform2D.Transform(new Point(_visualBounds.Left,  _visualBounds.Top));
                visCorners[1] = _transform2D.Transform(new Point(_visualBounds.Right, _visualBounds.Top));         
                visCorners[2] = _transform2D.Transform(new Point(_visualBounds.Right, _visualBounds.Bottom));
                visCorners[3] = _transform2D.Transform(new Point(_visualBounds.Left,  _visualBounds.Bottom));
 
                // get the u,v texture coordinate values of the above points
                Point[] texCoordsOfInterest = new Point[4];
                for (int i = 0; i < visCorners.Length; i++)
                {
                    texCoordsOfInterest[i] = Viewport2DVisual3D.VisualCoordsToTextureCoords(visCorners[i], _visualBrushBounds);
                }
 
                // get the edges that map to the given visual
                _validEdgesCache = GrabValidEdges(texCoordsOfInterest);
            }
            
            // find the closest intersection of the mouse position and the edge list
            return FindClosestIntersection(mousePos, _validEdgesCache, out outPoint);
        }
 
        /// <summary>
        /// Function takes the passed in list of texture coordinate points, and then finds the 
        /// visible outline of the rectangle specified by those points and returns it.
        /// </summary>
        /// <param name="visualTexCoordBounds">The points specifying the rectangle to search for</param>
        /// <returns>The edges of that rectangle</returns>
        private List<HitTestEdge> GrabValidEdges(Point[] visualTexCoordBounds)
        {
            // our final edge list
            List<HitTestEdge> hitTestEdgeList = new List<HitTestEdge>();
            Dictionary<Edge, EdgeInfo> adjInformation = new Dictionary<Edge, EdgeInfo>();
 
            // store some important info in local variables for easier access
            Point3DCollection positions = _geometry.Positions;
            PointCollection textureCoords = _geometry.TextureCoordinates;
            Int32Collection triIndices = _geometry.TriangleIndices;
 
            // if positions and texture coordinates are null, we can't really find what we need so return immediately
            if (positions == null || textureCoords == null)
            {
                return new List<HitTestEdge>();
            }
            
            // this call actually gets the object to camera transform, but we will invert it later, and because of that
            // the local variable is named cameraToObjecTransform.
            Matrix3D cameraToObjectTransform = _worldTransformation * _camera.GetViewMatrix();
            try
            {
                cameraToObjectTransform.Invert();
            }
            catch (InvalidOperationException)
            {
                return new List<HitTestEdge>();
            }
 
            Point3D camPosObjSpace = cameraToObjectTransform.Transform(new Point3D(0, 0, 0));
 
            // get the bounding box around the passed in texture coordinates to help
            // with early rejection tests
            Rect bbox = Rect.Empty;
            for (int i = 0; i < visualTexCoordBounds.Length; i++)
            {
                bbox.Union(visualTexCoordBounds[i]);
            }
 
            // walk through the triangles - and look for the triangles we care about
            Point3D[] triangleVertices = new Point3D[3];
            Point[] triangleTexCoords = new Point[3];
 
            // switch depending on if the mesh is indexed or not
            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)
                {
                    // get the triangle indices
                    Rect triBBox = Rect.Empty;
 
                    for (int j = 0; j < 3; j++)
                    {
                        triangleVertices[j] = positions[i + j];
 
                        if (i + j < texCoordCount)
                        {
                            triangleTexCoords[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
                            triangleTexCoords[j] = new Point(0,0);
                        }
 
                        triBBox.Union(triangleTexCoords[j]);
                    }
 
                    if (bbox.IntersectsWith(triBBox))
                    {
                        ProcessTriangle(triangleVertices, triangleTexCoords, visualTexCoordBounds, hitTestEdgeList, adjInformation, camPosObjSpace);
                    }
                }
            }
            else
            {
                // in this case we have an indexed mesh
                int count = triIndices.Count;
                int posLimit = positions.Count;
                int texCoordLimit = textureCoords.Count;
                int[] indices = new int[3];
            
                for (int i = 2; i < count; i += 3)
                {
                    // get the triangle indices
                    Rect triBBox = Rect.Empty;
 
                    bool validTextureCoordinates = true;
                    bool validPositions = true;
                    for (int j = 0; j < 3; j++)
                    {
                        // subtract 2 to take in to account we start i
                        // at the high range of indices
                        indices[j] = triIndices[(i-2) + j];
 
                        // if a point or texture coordinate is out of range, end early since this is an error
                        if (indices[j] < 0 || indices[j] >= posLimit)
                        {
                            validPositions = false;
                            break; 
                        }
                        if (indices[j] < 0 || indices[j] >= texCoordLimit)
                        {
                            validTextureCoordinates = false;
                            break;
                        }
            
                        triangleVertices[j] = positions[indices[j]];
                        triangleTexCoords[j] = textureCoords[indices[j]];
 
                        triBBox.Union(triangleTexCoords[j]);
                    }
 
                    // if the positions were ever invalid, we stop processing - see MeshGeometry3D RayHitTestIndexedList
                    // for reasoning
                    if (!validPositions)
                    {
                        break;
                    }
 
                    if (validTextureCoordinates && bbox.IntersectsWith(triBBox))
                    {
                        ProcessTriangle(triangleVertices, triangleTexCoords, visualTexCoordBounds, hitTestEdgeList, adjInformation, camPosObjSpace);
                    }
                }
            }
            
            // also handle the case of an edge that doesn't also have a backface - i.e a single plane            
            foreach (Edge edge in adjInformation.Keys)
            {
                EdgeInfo ei = adjInformation[edge];
 
                if (ei._hasFrontFace && ei._numSharing == 1)
                {
                    HandleSilhouetteEdge(ei._uv1, ei._uv2,
                                         edge._start, edge._end,
                                         visualTexCoordBounds,
                                         hitTestEdgeList);
                }
            }
 
            // project all the edges to get at the 2D point of interest
            if (_objectToViewport != null)
 
            {
                for (int i = 0; i < hitTestEdgeList.Count; i++)
                {
                    hitTestEdgeList[i].Project(_objectToViewport);
                }
            }
            else
            {
                hitTestEdgeList = new List<HitTestEdge>();
            }
 
            return hitTestEdgeList;
        }
 
        /// <summary>
        /// Processes the passed in triangle by checking to see if it is facing the camera and if
        /// so searches to see if the texture coordinate edges intersect it.  It also looks
        /// to see if there are any silhouette edges and processes these as well.
        /// </summary>
        /// <param name="p">The triangle's vertices</param>
        /// <param name="uv">The texture coordinates for those vertices</param>   
        /// <param name="visualTexCoordBounds">The texture coordinate edges to intersect with</param>
        /// <param name="edgeList">The edge list that results should be placed on</param>
        /// <param name="adjInformation">The adjacency information for the mesh</param>
        /// <param name="camPosObjSpace"></param>
        private void ProcessTriangle(Point3D[] p,
                                     Point[] uv,
                                     Point[] visualTexCoordBounds,
                                     List<HitTestEdge> edgeList,
                                     Dictionary<Edge, EdgeInfo> adjInformation,
                                     Point3D camPosObjSpace)
        {
            // calculate the normal of the mesh and the vector from a point on the mesh to the camera 
            // for back face removal calculations.
            Vector3D normal = Vector3D.CrossProduct(p[1] - p[0], p[2] - p[0]);
            Vector3D dirToCamera = camPosObjSpace - p[0];
 
            // ignore any triangles that have a normal of (0,0,0)
            if (!(normal.X == 0 && normal.Y == 0 && normal.Z == 0))
            {
                double dotProd = Vector3D.DotProduct(normal, dirToCamera);
 
                // if the dot product is > 0 then the triangle is visible, otherwise invisible
                if (dotProd > 0.0)
                {
                    // loop over the triangle and update any edge information
                    ProcessTriangleEdges(p, uv, visualTexCoordBounds, PolygonSide.FRONT, edgeList, adjInformation);
 
                    // intersect the bounds of the visual with the triangle
                    ProcessVisualBoundsIntersections(p, uv, visualTexCoordBounds, edgeList);
                }
                else
                {
                    ProcessTriangleEdges(p, uv, visualTexCoordBounds, PolygonSide.BACK, edgeList, adjInformation);
                }
            }
        }
 
        /// <summary>
        /// Function intersects the edges specified by tc with the texture coordinates
        /// on the passed in triangle.  If there are any intersections, the edges
        /// of these intersections are added to the edgelist
        /// </summary>
        /// <param name="p">The vertices of the triangle</param>
        /// <param name="uv">The texture coordinates for that triangle</param>
        /// <param name="visualTexCoordBounds">The texture coordinate edges to be intersected against</param>
        /// <param name="edgeList">The list of edges any intersecte edges should be added to</param>
        private void ProcessVisualBoundsIntersections(Point3D[] p,
                                                      Point[] uv,
                                                      Point[] visualTexCoordBounds,
                                                      List<HitTestEdge> edgeList)
        {
            Debug.Assert(uv.Length == p.Length, "vertices and texture coordinate sizes should match");
            
            List<Point3D> pointList = new List<Point3D>();
            List<Point> uvList = new List<Point>();
 
            // loop over the visual's texture coordinate bounds
            for (int i = 0; i < visualTexCoordBounds.Length; i++)
            {
                Point visEdgeStart = visualTexCoordBounds[i];
                Point visEdgeEnd = visualTexCoordBounds[(i + 1) % visualTexCoordBounds.Length];
 
                // clear out anything that used to be there
                pointList.Clear();
                uvList.Clear();
 
                // loop over triangle edges
                bool skipListProcessing = false;
                for (int j = 0; j < uv.Length; j++)
                {
                    Point uv1 = uv[j];
                    Point uv2 = uv[(j + 1) % uv.Length];
                    Point3D p3D1 = p[j];
                    Point3D p3D2 = p[(j + 1) % p.Length];
 
                    // initial rejection processing
                    if (!((Math.Max(visEdgeStart.X, visEdgeEnd.X) < Math.Min(uv1.X, uv2.X)) ||
                          (Math.Min(visEdgeStart.X, visEdgeEnd.X) > Math.Max(uv1.X, uv2.X)) ||
                          (Math.Max(visEdgeStart.Y, visEdgeEnd.Y) < Math.Min(uv1.Y, uv2.Y)) ||
                          (Math.Min(visEdgeStart.Y, visEdgeEnd.Y) > Math.Max(uv1.Y, uv2.Y))))
                    {
                        // intersect the two lines
                        bool areCoincident = false;
                        Vector dir = uv2 - uv1;
                        double t = IntersectRayLine(uv1, dir, visEdgeStart, visEdgeEnd, out areCoincident);
 
                        // if they are coincident then we have two intersections and don't need to
                        // do anymore processing
                        if (areCoincident)
                        {
                            HandleCoincidentLines(visEdgeStart, visEdgeEnd,
                                                  p3D1, p3D2,
                                                  uv1, uv2, edgeList);
                            skipListProcessing = true;
                            break;
                        }
                        else if (t >= 0 && t <= 1)
                        {
                            Point intersUV = uv1 + dir * t;
                            Point3D intersPoint3D = p3D1 + (p3D2 - p3D1) * t;
 
                            double visEdgeDiff = (visEdgeStart - visEdgeEnd).Length;
 
                            if ((intersUV - visEdgeStart).Length < visEdgeDiff &&
                                (intersUV - visEdgeEnd).Length < visEdgeDiff)
                            {
                                pointList.Add(intersPoint3D);
                                uvList.Add(intersUV);
                            }
                        }
                    }
                }
 
                if (!skipListProcessing)
                {
                    if (pointList.Count >= 2)
                    {
                        edgeList.Add(new HitTestEdge(pointList[0], pointList[1],
                                                     uvList[0], uvList[1]));
                    }
                    else if (pointList.Count == 1)
                    {
                        Point3D outputPoint;
 
                        // To avoid an edge cases caused by generating a point extremely
                        // close to one of the bound points, we test if both points are inside
                        // the bounds to be on the safe side - in the worst case we do 
                        // extra work or generate a small edge
                        if (M3DUtil.IsPointInTriangle(visEdgeStart, uv, p, out outputPoint))
                        {
                            edgeList.Add(new HitTestEdge(pointList[0], outputPoint,
                                                         uvList[0], visEdgeStart));
                        }
 
                        if (M3DUtil.IsPointInTriangle(visEdgeEnd, uv, p, out outputPoint))
                        {
                            edgeList.Add(new HitTestEdge(pointList[0], outputPoint,
                                                         uvList[0], visEdgeEnd));
                        }
                    }
                    else
                    {
                        Point3D outputPoint1, outputPoint2;
 
                        if (M3DUtil.IsPointInTriangle(visEdgeStart, uv, p, out outputPoint1) &&
                            M3DUtil.IsPointInTriangle(visEdgeEnd, uv, p, out outputPoint2))
                        {
                            edgeList.Add(new HitTestEdge(outputPoint1, outputPoint2,
                                                         visEdgeStart, visEdgeEnd));
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Handles adding an edge when the two line segments are coincident.
        /// </summary>
        /// <param name="visUV1">The texture coordinates of the boundary edge</param>
        /// <param name="visUV2">The texture coordinates of the boundary edge</param>
        /// <param name="tri3D1">The 3D coordinate of the triangle edge</param>
        /// <param name="tri3D2">The 3D coordinates of the triangle edge</param>
        /// <param name="triUV1">The texture coordinates of the triangle edge</param>
        /// <param name="triUV2">The texture coordinates of the triangle edge</param>
        /// <param name="edgeList">The edge list to add to</param>
        private void HandleCoincidentLines(Point visUV1, Point visUV2,
                                           Point3D tri3D1, Point3D tri3D2,
                                           Point triUV1, Point triUV2,
                                           List<HitTestEdge> edgeList)
        {
            Point minVisUV, maxVisUV;
 
            Point minTriUV, maxTriUV;
            Point3D minTri3D, maxTri3D;
 
            // to be used in final edge creation
            Point uv1, uv2;
            Point3D p1, p2;
 
            // order the points and give refs to them for ease of use
            if (Math.Abs(visUV1.X - visUV2.X) > Math.Abs(visUV1.Y - visUV2.Y))
            {
                if (visUV1.X <= visUV2.X)
                {
                    minVisUV = visUV1;
                    maxVisUV = visUV2;
                }
                else
                {
                    minVisUV = visUV2;
                    maxVisUV = visUV1;
                }
 
                if (triUV1.X <= triUV2.X)
                {
                    minTriUV = triUV1;
                    minTri3D = tri3D1;
 
                    maxTriUV = triUV2;
                    maxTri3D = tri3D2;
                }
                else
                {
                    minTriUV = triUV2;
                    minTri3D = tri3D2;
 
                    maxTriUV = triUV1;
                    maxTri3D = tri3D1;
                }
 
                // now actually create the edge           
                // compute the minimum value
                if (minVisUV.X < minTriUV.X)
                {
                    uv1 = minTriUV;
                    p1 = minTri3D;
                }
                else
                {
                    uv1 = minVisUV;
                    p1 = minTri3D + (minVisUV.X - minTriUV.X) / (maxTriUV.X - minTriUV.X) * (maxTri3D - minTri3D);
                }
 
                // compute the maximum value
                if (maxVisUV.X > maxTriUV.X)
                {
                    uv2 = maxTriUV;
                    p2 = maxTri3D;
                }
                else
                {
                    uv2 = maxVisUV;
                    p2 = minTri3D + (maxVisUV.X - minTriUV.X) / (maxTriUV.X - minTriUV.X) * (maxTri3D - minTri3D);
                }
            }
            else
            {
                if (visUV1.Y <= visUV2.Y)
                {
                    minVisUV = visUV1;
                    maxVisUV = visUV2;
                }
                else
                {
                    minVisUV = visUV2;
                    maxVisUV = visUV1;
                }
 
                if (triUV1.Y <= triUV2.Y)
                {
                    minTriUV = triUV1;
                    minTri3D = tri3D1;
 
                    maxTriUV = triUV2;
                    maxTri3D = tri3D2;
                }
                else
                {
                    minTriUV = triUV2;
                    minTri3D = tri3D2;
 
                    maxTriUV = triUV1;
                    maxTri3D = tri3D1;
                }
 
                // now actually create the edge           
                // compute the minimum value
                if (minVisUV.Y < minTriUV.Y)
                {
                    uv1 = minTriUV;
                    p1 = minTri3D;
                }
                else
                {
                    uv1 = minVisUV;
                    p1 = minTri3D + (minVisUV.Y - minTriUV.Y) / (maxTriUV.Y - minTriUV.Y) * (maxTri3D - minTri3D);
                }
 
                // compute the maximum value
                if (maxVisUV.Y > maxTriUV.Y)
                {
                    uv2 = maxTriUV;
                    p2 = maxTri3D;
                }
                else
                {
                    uv2 = maxVisUV;
                    p2 = minTri3D + (maxVisUV.Y - minTriUV.Y) / (maxTriUV.Y - minTriUV.Y) * (maxTri3D - minTri3D);
                }
            }
 
            // add the edge
            edgeList.Add(new HitTestEdge(p1, p2, uv1, uv2));
        }
 
        /// <summary>
        /// Intersects a ray with the line specified by the passed in end points.  The parameterized coordinate along the ray of
        /// intersection is returned.  
        /// </summary>
        /// <param name="o">The ray origin</param>
        /// <param name="d">The ray direction</param>
        /// <param name="p1">First point of the line to intersect against</param>
        /// <param name="p2">Second point of the line to intersect against</param>
        /// <param name="coinc">Whether the ray and line are coincident</param>        
        /// <returns>
        /// The parameter along the ray of the point of intersection.
        /// If the ray and line are parallel and not coincident, this will be -1.
        /// </returns>
        private double IntersectRayLine(Point o, Vector d, Point p1, Point p2, out bool coinc)
        {
            coinc = false;
 
            // deltas
            double dy = p2.Y - p1.Y;
            double dx = p2.X - p1.X;
 
            // handle case of a vertical line
            if (dx == 0)
            {
                if (d.X == 0)
                {
                    coinc = (o.X == p1.X);
                    return -1;
                }
                else
                {
                    return (p2.X - o.X) / d.X;
                }
            }
 
            // now need to do more general intersection
            double numer = (o.X - p1.X) * dy / dx - o.Y + p1.Y;
            double denom = (d.Y - d.X * dy / dx);
 
            // if denominator is zero, then the lines are parallel
            if (denom == 0)
            {
                double b0 = -o.X * dy / dx + o.Y;
                double b1 = -p1.X * dy / dx + p1.Y;
 
                coinc = (b0 == b1);
                return -1;
            }
            else
            {
                return (numer / denom);
            }
        }
 
        /// <summary>
        /// Helper structure to represent an edge
        /// </summary>
        private struct Edge
        {
            public Edge(Point3D s, Point3D e)
            {
                _start = s;
                _end = e;
            }
 
            public Point3D _start;
            public Point3D _end;
        }
 
        /// <summary>
        /// Information about an edge such as whether it belongs to a front/back facing
        /// triangle, the texture coordinates for the edge, and how many polygons refer
        /// to that edge.
        /// </summary>
        private class EdgeInfo
        {
            public EdgeInfo()
            {
                _hasFrontFace = _hasBackFace = false;
                _numSharing = 0;
            }
 
            public bool _hasFrontFace;
            public bool _hasBackFace;
            public Point _uv1;
            public Point _uv2;
            public int _numSharing;
        }
 
        /// <summary>
        /// Processes the edges of the given triangle.  It does so by updating
        /// the adjacency information based on the direction the polygon is facing.
        /// If there is a silhouette edge found, then this edge is added to the list
        /// of edges if it is within the texture coordinate bounds passed to the function.
        /// </summary>
        /// <param name="p">The triangle's vertices</param>
        /// <param name="uv">The texture coordinates for those vertices</param>
        /// <param name="visualTexCoordBounds">The texture coordinate edges being searched for</param>
        /// <param name="polygonSide">Which side the polygon is facing (greateer than 0 front, less than 0 back)</param>
        /// <param name="edgeList">The list of edges comprosing the visual outline</param>
        /// <param name="adjInformation">The adjacency information structure</param>
        private void ProcessTriangleEdges(Point3D[] p,
                                          Point[] uv,
                                          Point[] visualTexCoordBounds,
                                          PolygonSide polygonSide,
                                          List<HitTestEdge> edgeList,
                                          Dictionary<Edge, EdgeInfo> adjInformation)
        {
            // loop over all the edges and add them to the adjacency list
            for (int i = 0; i < p.Length; i++)
            {
                Point uv1, uv2;
                Point3D p3D1 = p[i];
                Point3D p3D2 = p[(i + 1) % p.Length];
 
                Edge edge;
 
                // order the edge points so insertion in to adjInformation is consistent
                if (p3D1.X < p3D2.X ||
                   (p3D1.X == p3D2.X && p3D1.Y < p3D2.Y) ||
                   (p3D1.X == p3D2.X && p3D1.Y == p3D2.Y && p3D1.Z < p3D1.Z))
                {
                    edge = new Edge(p3D1, p3D2);
                    uv1 = uv[i];
                    uv2 = uv[(i + 1) % p.Length];
                }
                else
                {
                    edge = new Edge(p3D2, p3D1);
                    uv2 = uv[i];
                    uv1 = uv[(i + 1) % p.Length];
                }
 
                // look up the edge information
                EdgeInfo edgeInfo;
                if (adjInformation.ContainsKey(edge))
                {
                    edgeInfo = adjInformation[edge];
                }
                else
                {
                    edgeInfo = new EdgeInfo();
                    adjInformation[edge] = edgeInfo;
                }
                edgeInfo._numSharing++;
 
                // whether or not the edge has already been added to the edge list
                bool alreadyAdded = edgeInfo._hasBackFace && edgeInfo._hasFrontFace;
 
                // add the edge to the info list
                if (polygonSide == PolygonSide.FRONT)
                {
                    edgeInfo._hasFrontFace = true;
                    edgeInfo._uv1 = uv1;
                    edgeInfo._uv2 = uv2;
                }
                else
                {
                    edgeInfo._hasBackFace = true;
                }
 
                // if the sides are different we may need to add an edge
                if (!alreadyAdded && edgeInfo._hasBackFace && edgeInfo._hasFrontFace)
                {
                    HandleSilhouetteEdge(edgeInfo._uv1, edgeInfo._uv2,
                                        edge._start, edge._end,
                                        visualTexCoordBounds,
                                        edgeList);
                }
            }
        }
 
        /// <summary>
        /// Handles intersecting a silhouette edge against the passed in texture coordinate 
        /// bounds.  It behaves similarly to the case of intersection the bounds with a triangle 
        /// except the testing order is switched.
        /// </summary>
        /// <param name="uv1">The texture coordinates of the edge</param>
        /// <param name="uv2">The texture coordinates of the edge</param>
        /// <param name="p3D1">The 3D point of the edge</param>
        /// <param name="p3D2">The 3D point of the edge</param>
        /// <param name="bounds">The texture coordinate bounds</param>
        /// <param name="edgeList">The list of edges</param>
        private void HandleSilhouetteEdge(Point uv1, Point uv2,
                                          Point3D p3D1, Point3D p3D2,
                                          Point[] bounds,
                                          List<HitTestEdge> edgeList)
        {
            List<Point3D> pointList = new List<Point3D>();
            List<Point> uvList = new List<Point>();
            Vector dir = uv2 - uv1;
 
            // loop over object bounds
            for (int i = 0; i < bounds.Length; i++)
            {
                Point visEdgeStart = bounds[i];
                Point visEdgeEnd = bounds[(i + 1) % bounds.Length];
 
                // initial rejection processing
                if (!((Math.Max(visEdgeStart.X, visEdgeEnd.X) < Math.Min(uv1.X, uv2.X)) ||
                      (Math.Min(visEdgeStart.X, visEdgeEnd.X) > Math.Max(uv1.X, uv2.X)) ||
                      (Math.Max(visEdgeStart.Y, visEdgeEnd.Y) < Math.Min(uv1.Y, uv2.Y)) ||
                      (Math.Min(visEdgeStart.Y, visEdgeEnd.Y) > Math.Max(uv1.Y, uv2.Y))))
                {
                    // intersect the two lines
                    bool areCoincident = false;
                    double t = IntersectRayLine(uv1, dir, visEdgeStart, visEdgeEnd, out areCoincident);
 
                    // silhouette edge processing will only include non-coincident lines
                    if (areCoincident)
                    {
                        // if it's coincident, we'll let the normal processing handle this edge
                        return;
                    }
                    else if (t >= 0 && t <= 1)
                    {
                        Point intersUV = uv1 + dir * t;
                        Point3D intersPoint3D = p3D1 + (p3D2 - p3D1) * t;
 
                        double visEdgeDiff = (visEdgeStart - visEdgeEnd).Length;
 
                        if ((intersUV - visEdgeStart).Length < visEdgeDiff &&
                            (intersUV - visEdgeEnd).Length < visEdgeDiff)
                        {
                            pointList.Add(intersPoint3D);
                            uvList.Add(intersUV);
                        }
                    }
                }
            }
 
            if (pointList.Count >= 2)
            {
                edgeList.Add(new HitTestEdge(pointList[0], pointList[1],
                                             uvList[0], uvList[1]));
            }
            else if (pointList.Count == 1)
            {
                // for the case that uv1/2 is actually a point on or extremely close to the bounds
                // of the polygon, we do the pointinpolygon test on both to avoid any numerical
                // precision issues - in the worst case we end up with a very small edge and
                // the right edge
                if (IsPointInPolygon(bounds, uv1))
                {
                    edgeList.Add(new HitTestEdge(pointList[0], p3D1,
                                                 uvList[0], uv1));
                }
                if (IsPointInPolygon(bounds, uv2))
                {
                    edgeList.Add(new HitTestEdge(pointList[0], p3D2,
                                                 uvList[0], uv2));
                }
            }
            else
            {
                if (IsPointInPolygon(bounds, uv1) &&
                    IsPointInPolygon(bounds, uv2))
                {
                    edgeList.Add(new HitTestEdge(p3D1, p3D2,
                                                 uv1, uv2));
                }
            }
        }
 
        /// <summary>
        /// Function tests to see whether the point p is contained within the polygon
        /// specified by the list of points passed to the function.  p is considered within
        /// this polygon if it is on the same side of all the edges.  A point on any of
        /// the edges of the polygon is not considered within the polygon.
        /// </summary>
        /// <param name="polygon">The polygon to test against</param>
        /// <param name="p">The point to be tested against</param>
        /// <returns>Whether the point is in the polygon</returns>
        private bool IsPointInPolygon(Point[] polygon, Point p)
        {
            bool sign = false;
 
            for (int i = 0; i < polygon.Length; i++)
            {
                double crossProduct = Vector.CrossProduct(polygon[(i + 1) % polygon.Length] - polygon[i],
                                                          polygon[i] - p);
 
                bool currSign = crossProduct > 0;
 
                if (i == 0)
                {
                    sign = currSign;
                }
                else
                {
                    if (sign != currSign) return false;
                }
            }
 
            return true;
        }
 
 
        /// <summary>
        /// Finds the point in edges that is closest ot the mouse position.  Updates closestIntersectionInfo
        /// with the results of this calculation
        /// </summary>
        /// <param name="mousePos">The mouse position</param>
        /// <param name="edges">The edges to test against</param>
        /// <param name="finalPoint">The final intersection point</param>
        /// <returns>The closest intersection point</returns>
        private bool FindClosestIntersection(Point mousePos, List<HitTestEdge> edges, out Point finalPoint)
        {
            bool success = false;
            double closestDistance = Double.MaxValue;
            Point closestIntersection = new Point();  // the uv of the closest intersection
            finalPoint = new Point();
 
            // Find the closest point to the mouse position            
            for (int i=0, count = edges.Count; i < count; i++)
            {
                Vector v1 = mousePos - edges[i]._p1Transformed;
                Vector v2 = edges[i]._p2Transformed - edges[i]._p1Transformed;
                
                Point currClosest;
                double distance;
 
                // calculate the distance from the mouse position to this edge
                // The closest distance can be computed by projecting v1 on to v2.  If the
                // projectiong occurs between _p1Transformed and _p2Transformed, then this is the
                // closest point.  Otherwise, depending on which side it lies, it is either _p1Transformed
                // or _p2Transformed.  
                //
                // The projection equation is given as: (v1 DOT v2) / (v2 DOT v2) * v2.
                // v2 DOT v2 will always be positive.  Thus, if v1 DOT v2 is negative, we know the projection
                // will occur before _p1Transformed (and so it is the closest point).  If (v1 DOT v2) is greater
                // than (v2 DOT v2), then we have gone passed _p2Transformed and so it is the closest point.
                // Otherwise the projection gives us this value.
                //
                double denom = v2 * v2;
                if (denom == 0)
                {
                    currClosest = edges[i]._p1Transformed;
                    distance = v1.Length;
                }
                else
                {
                    double numer = v2 * v1;
                    if (numer < 0)
                    {
                        currClosest = edges[i]._p1Transformed;
                    }
                    else
                    {
                        if (numer > denom)
                        {
                            currClosest = edges[i]._p2Transformed;
                        }
                        else
                        {
                            currClosest = edges[i]._p1Transformed + (numer / denom) * v2;
                        }
                    }
 
                    distance = (mousePos - currClosest).Length; 
                }
 
                // see if we found a new closest distance
                if (distance < closestDistance)
                {
                    closestDistance = distance;
                    
                    if (denom != 0)
                    {
                        closestIntersection = ((currClosest - edges[i]._p1Transformed).Length / Math.Sqrt(denom) * 
                                               (edges[i]._uv2 - edges[i]._uv1)) + edges[i]._uv1;
                    }
                    else
                    {
                        closestIntersection = edges[i]._uv1;
                    }
                }                
            }
 
            if (closestDistance != Double.MaxValue)
            {
                Point ptOnVisual = Viewport2DVisual3D.TextureCoordsToVisualCoords(closestIntersection, _visualBrushBounds);
 
                if (_transform2DInverse != null)
                {
                    Point ptRelToCapture = _transform2DInverse.Transform(ptOnVisual);
                    
                    // we want to "ring" around the outside so things like buttons are not pressed when we move off the mesh
                    // this code here does that - the +BUFFER_SIZE and -BUFFER_SIZE are to give a bit of a 
                    // buffer for any numerical issues
                    if (ptRelToCapture.X <= _visualBounds.Left + 1)   ptRelToCapture.X -= BUFFER_SIZE;
                    if (ptRelToCapture.Y <= _visualBounds.Top + 1)    ptRelToCapture.Y -= BUFFER_SIZE;
                    if (ptRelToCapture.X >= _visualBounds.Right - 1)  ptRelToCapture.X += BUFFER_SIZE;
                    if (ptRelToCapture.Y >= _visualBounds.Bottom - 1) ptRelToCapture.Y += BUFFER_SIZE;
 
                    Point finalVisualPoint = _transform2D.Transform(ptRelToCapture);
                    finalPoint = Viewport2DVisual3D.VisualCoordsToTextureCoords(finalVisualPoint, _visualBrushBounds);      
 
                    success = true;
                }
            }
 
            return success;
        }
       
        /// <summary>
        /// Performs the transform that goes from 2D on 3D content contained within the 3D scene
        /// up to the containing Viewport3DVisual
        /// </summary>
        /// <param name="inPoint">input point</param>
        /// <param name="result">output point</param>
        /// <returns>false if the point cannot be transformed</returns>
        private bool TryRegularTransform(Point inPoint, out Point result)
        {
            Point texCoord = Viewport2DVisual3D.VisualCoordsToTextureCoords(inPoint, _visualBrushBounds);
 
            // need to walk the texture coordinates and look for where this point intersects one of them
            Point3D point3D;
            if (_objectToViewport != null &&
                Viewport2DVisual3D.Get3DPointFor2DCoordinate(texCoord, 
                                                           out point3D,
                                                           _geometry.Positions,
                                                           _geometry.TextureCoordinates,
                                                           _geometry.TriangleIndices))
            {
                // convert from this 3D point up to the containing Viewport3D
                return _objectToViewport.TryTransform(point3D, out result);
            }
            else
            {
                result = new Point();
                return false;
            }
        }        
 
        /// <summary>
        /// Transform the rect bounds into the smallest axis alligned bounding box that
        /// contains all the point in the original bounds.
        /// </summary>
        /// <param name="rect"></param>
        /// <returns></returns>
        public override Rect TransformBounds(Rect rect)
        {
            List<HitTestEdge> edges = null;
            
            // intersect the rect given to us with the bounds of the visual brush to guarantee the rect we are
            // searching for is within the visual brush
            rect.Intersect(_visualBrushBounds);
 
            // get the texture coordinate values for the rect's corners
            Point[] texCoordsOfInterest = new Point[4];
            texCoordsOfInterest[0] = Viewport2DVisual3D.VisualCoordsToTextureCoords(rect.TopLeft,     _visualBrushBounds);
            texCoordsOfInterest[1] = Viewport2DVisual3D.VisualCoordsToTextureCoords(rect.TopRight,    _visualBrushBounds);
            texCoordsOfInterest[2] = Viewport2DVisual3D.VisualCoordsToTextureCoords(rect.BottomRight, _visualBrushBounds);
            texCoordsOfInterest[3] = Viewport2DVisual3D.VisualCoordsToTextureCoords(rect.BottomLeft,  _visualBrushBounds);
                        
            // get the edges that map to the given rect
            edges = GrabValidEdges(texCoordsOfInterest);
 
            Rect result = Rect.Empty;
            if (edges != null)
            {
                for (int i = 0, count = edges.Count; i < count; i++)
                {
                    result.Union(edges[i]._p1Transformed);
                    result.Union(edges[i]._p2Transformed);
                }
            }
 
            return result;
        }
 
        /// <summary>
        /// Returns the inverse transform if there is one, null otherwise
        /// </summary>
        public override GeneralTransform Inverse
        {
            get
            {
                // internal class - no ReadPreamble needed
                // ReadPreamble();
 
                GeneralTransform2DTo3DTo2D inverseTransform = (GeneralTransform2DTo3DTo2D)Clone();
                inverseTransform.IsInverse = !IsInverse;
 
                return inverseTransform;
            }
        }
       
        /// <summary>
        /// Returns a best effort affine transform
        /// </summary>        
        internal override Transform AffineTransform
        {
            get
            {
                return null;
            }
        }
 
        /// <summary>
        /// Returns true if the transform is an inverse
        /// </summary>
        internal bool IsInverse
        {
            get { return _fInverse; }
            set { _fInverse = value; }
        }
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CreateInstanceCore">Freezable.CreateInstanceCore</see>.
        /// </summary>
        /// <returns>The new Freezable.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new GeneralTransform2DTo3DTo2D();
        }
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCore(Freezable)">Freezable.CloneCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void CloneCore(Freezable sourceFreezable)
        {
            GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
            base.CloneCore(sourceFreezable);
            CopyCommon(transform);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.CloneCurrentValueCore(Freezable)">Freezable.CloneCurrentValueCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void CloneCurrentValueCore(Freezable sourceFreezable)
        {
            GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
            base.CloneCurrentValueCore(sourceFreezable);
            CopyCommon(transform);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetAsFrozenCore(Freezable)">Freezable.GetAsFrozenCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void GetAsFrozenCore(Freezable sourceFreezable)
        {
            GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
            base.GetAsFrozenCore(sourceFreezable);
            CopyCommon(transform);
        }
 
 
        /// <summary>
        /// Implementation of <see cref="System.Windows.Freezable.GetCurrentValueAsFrozenCore(Freezable)">Freezable.GetCurrentValueAsFrozenCore</see>.
        /// </summary>
        /// <param name="sourceFreezable"></param>
        protected override void GetCurrentValueAsFrozenCore(Freezable sourceFreezable)
        {
            GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
            base.GetCurrentValueAsFrozenCore(sourceFreezable);
            CopyCommon(transform);
        }
        
        /// <summary>
        /// Clones values that do not have corresponding DPs
        /// </summary>
        /// <param name="transform"></param>
        private void CopyCommon(GeneralTransform2DTo3DTo2D transform)
        {
            _fInverse = transform._fInverse;
 
            _geometry = transform._geometry;
 
            _visualBounds = transform._visualBounds;
            _visualBrushBounds = transform._visualBrushBounds;
 
            _transform2D = transform._transform2D;
            _transform2DInverse = transform._transform2DInverse;
 
            _camera = transform._camera;
            _viewSize = transform._viewSize;
            _boundingRect = transform._boundingRect;
 
            _worldTransformation = transform._worldTransformation;
            _objectToViewport = transform._objectToViewport;
 
            _validEdgesCache = null;
        }
 
        private bool _fInverse;
        
        // the geometry of the 3D object
        private MeshGeometry3D _geometry;
 
        // the size of the visual brush and the visual on it we're interested in
        private Rect _visualBounds;
        private Rect _visualBrushBounds;
 
        // the transform to go in to and out of the coordinate space fo the visual we're
        // interested in
        private GeneralTransform _transform2D;
        private GeneralTransform _transform2DInverse;      
 
        // the camera being used on the 3D viewport
        private Camera _camera;
        private Size _viewSize;
        private Rect3D _boundingRect;
 
        // transformations through the 3D scene
        private Matrix3D _worldTransformation;
        private GeneralTransform3DTo2D _objectToViewport;
 
        // the cache of valid edges
        List<HitTestEdge> _validEdgesCache = null;
 
        // the "ring" around the element with capture to use in the capture case
        private const double BUFFER_SIZE = 2.0;                  
        private enum PolygonSide { FRONT, BACK };
    }
}