of 56
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
1/56
File: src\Core\CSharp\MS\Internal\Media3D\GeneralTransform2Dto3Dto2D.cs
Assembly: PresentationCore
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.Security.Permissions;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Media.Composition;
using System.Windows.Markup;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
2/56
using System.Windows.Media;
using System.Windows.Media.Media3D;
using MS.Internal.PresentationCore;
using MS.Internal.Media3D;
using SR = MS.Internal.PresentationCore.SR;
using SRID = MS.Internal.PresentationCore.SRID;
namespace MS.Internal.Media3D
{
///
/// Helper class that encapsulates return data needed for the
/// hit test capture methods.
///
internal class HitTestEdge
{
///
/// Constructs a new hit test edge
///
/// First edge point
/// Second edge point
/// Texture coordinate of first edge point
/// Texture coordinate of second edge point
public HitTestEdge(Point3D p1,
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
3/56
Point3D p2,
Point uv1,
Point uv2)
{
_p1 = p1;
_p2 = p2;
_uv1 = uv1;
_uv2 = uv2;
}
///
/// Projects the stored 3D points in to 2D.
///
/// The transformation matrix to use
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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
4/56
internal Point _uv1, _uv2;
// the transformed Point3D value
internal Point _p1Transformed, _p2Transformed;
}
///
/// This transform allows one to go from 2D through 3D and back in to 2D
///
internal class GeneralTransform2DTo3DTo2D : GeneralTransform
{
///
/// Constructor
///
/// The Visual3D that contains the 2D visual
/// The visual on the Visual3D
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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
5/56
_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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
6/56
// 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();
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
7/56
}
// store the needed transformations for the various operations
_worldTransformation = M3DUtil.GetWorldTransformationMatrix(visual3D);
_validEdgesCache = null;
}
internal GeneralTransform2DTo3DTo2D()
{
}
///
/// Transforms a point
///
/// input point
/// output point
/// false if the point cannot be transformed
public override bool TryTransform(Point inPoint, out Point result)
{
if (IsInverse)
{
return TryInverseTransform(inPoint, out result);
}
else
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
8/56
{
return TryRegularTransform(inPoint, out result);
}
}
///
/// Performs the transform that goes from the parent Viewport3DVisual down in to the 2D on 3D
/// contained within the 3D scene
///
/// input point
/// output point
/// false if the point cannot be transformed
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));
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
9/56
// 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);
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
10/56
// compute final point
result = Viewport2DVisual3D.TextureCoordsToVisualCoords(pointHit, _visualBrushBounds);
}
else
{
result = new Point();
}
return foundIntersection;
}
///
/// Function to deal with mouse capture when off the mesh.
///
/// The location of the mouse
/// output point
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));
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
11/56
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);
}
///
/// 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.
///
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
12/56
/// The points specifying the rectangle to search
for
/// The edges of that rectangle
private List GrabValidEdges(Point[] visualTexCoordBounds)
{
// our final edge list
List hitTestEdgeList = new List();
Dictionary adjInformation = new Dictionary();
// 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();
}
// 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
{
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
13/56
cameraToObjectTransform.Invert();
}
catch (InvalidOperationException)
{
return new List();
}
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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
14/56
// 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);
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
15/56
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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
16/56
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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
17/56
// 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);
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
18/56
}
// 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();
}
return hitTestEdgeList;
}
///
/// 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.
///
/// The triangle's vertices
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
19/56
/// The texture coordinates for those vertices
/// The texture coordinate edges to intersect
with
/// The edge list that results should be placed on
/// The adjacency information for the mesh
///
private void ProcessTriangle(Point3D[] p,
Point[] uv,
Point[] visualTexCoordBounds,
List edgeList,
Dictionary 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)
{
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
20/56
// 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);
}
}
}
///
/// 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
///
/// The vertices of the triangle
/// The texture coordinates for that triangle
/// The texture coordinate edges to be intersected
against
/// The list of edges any intersecte edges should be added to
private void ProcessVisualBoundsIntersections(Point3D[] p,
Point[] uv,
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
21/56
Point[] visualTexCoordBounds,
List edgeList)
{
Debug.Assert(uv.Length == p.Length, "vertices and texture coordinate sizes should match");
List pointList = new List();
List uvList = new List();
// 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];
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
22/56
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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
23/56
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;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
24/56
// 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,
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
25/56
visEdgeStart, visEdgeEnd));
}
}
}
}
}
///
/// Handles adding an edge when the two line segments are coincident.
///
/// The texture coordinates of the boundary edge
/// The texture coordinates of the boundary edge
/// The 3D coordinate of the triangle edge
/// The 3D coordinates of the triangle edge
/// The texture coordinates of the triangle edge
/// The texture coordinates of the triangle edge
/// The edge list to add to
private void HandleCoincidentLines(Point visUV1, Point visUV2,
Point3D tri3D1, Point3D tri3D2,
Point triUV1, Point triUV2,
List edgeList)
{
Point minVisUV, maxVisUV;
Point minTriUV, maxTriUV;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
26/56
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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
27/56
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);
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
28/56
// 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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
29/56
if (triUV1.Y
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
30/56
{
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));
}
///
/// Intersects a ray with the line specified by the passed in end points. The parameterized
coordinate along the ray of
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
31/56
/// intersection is returned.
///
/// The ray origin
/// The ray direction
/// First point of the line to intersect against
/// Second point of the line to intersect against
/// Whether the ray and line are coincident
///
/// The parameter along the ray of the point of intersection.
/// If the ray and line are parallel and not coincident, this will be -1.
///
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);
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
32/56
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
33/56
}
///
/// Helper structure to represent an edge
///
private struct Edge
{
public Edge(Point3D s, Point3D e)
{
_start = s;
_end = e;
}
public Point3D _start;
public Point3D _end;
}
///
/// 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.
///
private class EdgeInfo
{
public EdgeInfo()
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
34/56
{
_hasFrontFace = _hasBackFace = false;
_numSharing = 0;
}
public bool _hasFrontFace;
public bool _hasBackFace;
public Point _uv1;
public Point _uv2;
public int _numSharing;
}
///
/// 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.
///
/// The triangle's vertices
/// The texture coordinates for those vertices
/// The texture coordinate edges being searched
for
/// Which side the polygon is facing (greateer than 0 front, less than
0 back)
/// The list of edges comprosing the visual outline
/// The adjacency information structure
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
35/56
private void ProcessTriangleEdges(Point3D[] p,
Point[] uv,
Point[] visualTexCoordBounds,
PolygonSide polygonSide,
List edgeList,
Dictionary 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];
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
36/56
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)
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
37/56
{
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);
}
}
}
///
/// 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.
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
38/56
///
/// The texture coordinates of the edge
/// The texture coordinates of the edge
/// The 3D point of the edge
/// The 3D point of the edge
/// The texture coordinate bounds
/// The list of edges
private void HandleSilhouetteEdge(Point uv1, Point uv2,
Point3D p3D1, Point3D p3D2,
Point[] bounds,
List edgeList)
{
List pointList = new List();
List uvList = new List();
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)) ||
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
39/56
(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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
40/56
}
}
}
}
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));
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
41/56
}
}
else
{
if (IsPointInPolygon(bounds, uv1) &&
IsPointInPolygon(bounds, uv2))
{
edgeList.Add(new HitTestEdge(p3D1, p3D2,
uv1, uv2));
}
}
}
///
/// 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.
///
/// The polygon to test against
/// The point to be tested against
/// Whether the point is in the polygon
private bool IsPointInPolygon(Point[] polygon, Point p)
{
bool sign = false;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
42/56
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;
}
///
/// Finds the point in edges that is closest ot the mouse position. Updates closestIntersectionInfo
/// with the results of this calculation
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
43/56
///
/// The mouse position
/// The edges to test against
/// The final intersection point
/// The closest intersection point
private bool FindClosestIntersection(Point mousePos, List 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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
44/56
// 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)
{
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
45/56
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;
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
46/56
}
}
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.Bottom - 1) ptRelToCapture.Y += BUFFER_SIZE;
Point finalVisualPoint = _transform2D.Transform(ptRelToCapture);
finalPoint = Viewport2DVisual3D.VisualCoordsToTextureCoords(finalVisualPoint,
_visualBrushBounds);
success = true;
}
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
47/56
}
return success;
}
///
/// Performs the transform that goes from 2D on 3D content contained within the 3D scene
/// up to the containing Viewport3DVisual
///
/// input point
/// output point
/// false if the point cannot be transformed
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))
{
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
48/56
// convert from this 3D point up to the containing Viewport3D
return _objectToViewport.TryTransform(point3D, out result);
}
else
{
result = new Point();
return false;
}
}
///
/// Transform the rect bounds into the smallest axis alligned bounding box that
/// contains all the point in the original bounds.
///
///
///
public override Rect TransformBounds(Rect rect)
{
List 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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
49/56
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;
}
///
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
50/56
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
51/56
}
}
///
/// Returns true if the transform is an inverse
///
internal bool IsInverse
{
get { return _fInverse; }
set { _fInverse = value; }
}
///
/// Implementation of Freezable.CreateInstanceCore.
///
/// The new Freezable.
protected override Freezable CreateInstanceCore()
{
return new GeneralTransform2DTo3DTo2D();
}
///
/// Implementation of Freezable.CloneCore.
///
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
52/56
///
protected override void CloneCore(Freezable sourceFreezable)
{
GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
base.CloneCore(sourceFreezable);
CopyCommon(transform);
}
///
/// Implementation of Freezable.CloneCurrentValueCor
e.
///
///
protected override void CloneCurrentValueCore(Freezable sourceFreezable)
{
GeneralTransform2DTo3DTo2D transform = (GeneralTransform2DTo3DTo2D)sourceFreezable;
base.CloneCurrentValueCore(sourceFreezable);
CopyCommon(transform);
}
///
/// Implementation of Freezable.GetAsFrozenCore.
///
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
53/56
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
54/56
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
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
55/56
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 _validEdgesCache = null;
// the "ring" around the element with capture to use in the capture case
private const double BUFFER_SIZE = 2.0;
8/12/2019 Src Core CSharp MS Internal Media3D GeneralTransform2Dto3Dto2D.csharp
56/56
private enum PolygonSide { FRONT, BACK };
}
}