/// <summary> /// Returns the intersection info for the given rayHitResult. Intersection info /// only exists for an InteractiveModelVisual3D, so if an InteractiveModelVisual3D /// is not hit, then the return value is null. /// </summary> /// <param name="rayHitResult"></param> /// <returns> /// Returns ClosestIntersectionInfo if an InteractiveModelVisual3D is hit, otherwise /// returns null. /// </returns> private ClosestIntersectionInfo GetIntersectionInfo(RayHitTestResult rayHitResult) { ClosestIntersectionInfo isectInfo = null; // try to cast to a RaymeshGeometry3DHitTestResult RayMeshGeometry3DHitTestResult rayMeshResult = rayHitResult as RayMeshGeometry3DHitTestResult; if (rayMeshResult != null) { // see if we hit an InteractiveVisual3D InteractiveVisual3D imv3D = rayMeshResult.VisualHit as InteractiveVisual3D; if (imv3D != null) { // we can now extract the mesh and visual for the object we hit MeshGeometry3D geom = rayMeshResult.MeshHit; UIElement uiElem = imv3D.InternalVisual; if (uiElem != null) { // pull the barycentric coordinates of the intersection point double vertexWeight1 = rayMeshResult.VertexWeight1; double vertexWeight2 = rayMeshResult.VertexWeight2; double vertexWeight3 = rayMeshResult.VertexWeight3; // the indices in to where the actual intersection occurred int index1 = rayMeshResult.VertexIndex1; int index2 = rayMeshResult.VertexIndex2; int index3 = rayMeshResult.VertexIndex3; // texture coordinates of the three vertices hit // in the case that no texture coordinates are supplied we will simply // treat it as if no intersection occurred if (geom.TextureCoordinates != null && index1 < geom.TextureCoordinates.Count && index2 < geom.TextureCoordinates.Count && index3 < geom.TextureCoordinates.Count) { Point texCoord1 = geom.TextureCoordinates[index1]; Point texCoord2 = geom.TextureCoordinates[index2]; Point texCoord3 = geom.TextureCoordinates[index3]; // get the final uv values based on the barycentric coordinates Point finalPoint = new Point(texCoord1.X * vertexWeight1 + texCoord2.X * vertexWeight2 + texCoord3.X * vertexWeight3, texCoord1.Y * vertexWeight1 + texCoord2.Y * vertexWeight2 + texCoord3.Y * vertexWeight3); // create and return a valid intersection info isectInfo = new ClosestIntersectionInfo(finalPoint, uiElem, imv3D); } } } } return(isectInfo); }
/// <summary> /// This function sets the passed in uiElem as the hidden visual, and aligns /// it so that the point the uv coordinates map to on the visual are located /// at the same location as mousePos. /// </summary> /// <param name="uiElem">The UIElement that should be the hidden visual</param> /// <param name="uv">The uv coordinates on that UIElement that should be aligned with mousePos</param> /// <param name="mousePos">The mouse location</param> /// <param name="scaleHiddenVisual">Whether to scale the visual in addition to moving it</param> /// <returns></returns> private bool UpdateHiddenVisual(ClosestIntersectionInfo isectInfo, Point mousePos, bool scaleHiddenVisual) { bool needsMouseReSync = false; double newOffsetX, newOffsetY; // compute positioning information if (isectInfo != null) { UIElement uiElem = isectInfo.UIElementHit; // set our UIElement to be the one passed in if (_hiddenVisual.Child != uiElem) { // we need to replace the old one with this new one UIElement prevVisual = _hiddenVisual.Child; // clear out uiElem from any of our hidden visuals if (_oldHiddenVisual.Child == uiElem) { _oldHiddenVisual.Child = null; } if (_oldKeyboardFocusVisual.Child == uiElem) { _oldKeyboardFocusVisual.Child = null; } // also clear out prevVisual if (_oldHiddenVisual.Child == prevVisual) { _oldHiddenVisual.Child = null; } if (_oldKeyboardFocusVisual.Child == prevVisual) { _oldKeyboardFocusVisual.Child = null; } // depending on whether or not it has focus, do two different things, either // use the _oldKeyboardFocusVisual or the _oldHiddenVisual Decorator _oldVisToUse = null; if (prevVisual != null && prevVisual.IsKeyboardFocusWithin) { _oldVisToUse = _oldKeyboardFocusVisual; } else { _oldVisToUse = _oldHiddenVisual; } // now safely link everything up _hiddenVisual.Child = uiElem; _oldVisToUse.Child = prevVisual; needsMouseReSync = true; } Point ptOnVisual = TextureCoordsToVisualCoords(isectInfo.PointHit, _hiddenVisual); newOffsetX = mousePos.X - ptOnVisual.X; newOffsetY = mousePos.Y - ptOnVisual.Y; } else { // because we didn't interesect with anything, we need to move the hidden visual off // screen so that it can no longer be interacted with newOffsetX = ActualWidth + 1; newOffsetY = ActualHeight + 1; } // compute the scale needed double newScale; if (scaleHiddenVisual) { newScale = Math.Max(Viewport3D.RenderSize.Width, Viewport3D.RenderSize.Height); } else { newScale = 1.0; } // do the actual positioning needsMouseReSync |= PositionHiddenVisual(newOffsetX, newOffsetY, newScale, mousePos); return(needsMouseReSync); }
/// <summary> /// Returns the intersection info for the given rayHitResult. Intersection info /// only exists for an InteractiveModelVisual3D, so if an InteractiveModelVisual3D /// is not hit, then the return value is null. /// </summary> /// <param name="rayHitResult"></param> /// <returns> /// Returns ClosestIntersectionInfo if an InteractiveModelVisual3D is hit, otherwise /// returns null. /// </returns> private ClosestIntersectionInfo GetIntersectionInfo(RayHitTestResult rayHitResult) { ClosestIntersectionInfo isectInfo = null; // try to cast to a RaymeshGeometry3DHitTestResult RayMeshGeometry3DHitTestResult rayMeshResult = rayHitResult as RayMeshGeometry3DHitTestResult; if (rayMeshResult != null) { // see if we hit an InteractiveVisual3D InteractiveVisual3D imv3D = rayMeshResult.VisualHit as InteractiveVisual3D; if (imv3D != null) { // we can now extract the mesh and visual for the object we hit MeshGeometry3D geom = rayMeshResult.MeshHit; UIElement uiElem = imv3D.InternalVisual; if (uiElem != null) { // pull the barycentric coordinates of the intersection point double vertexWeight1 = rayMeshResult.VertexWeight1; double vertexWeight2 = rayMeshResult.VertexWeight2; double vertexWeight3 = rayMeshResult.VertexWeight3; // the indices in to where the actual intersection occurred int index1 = rayMeshResult.VertexIndex1; int index2 = rayMeshResult.VertexIndex2; int index3 = rayMeshResult.VertexIndex3; // texture coordinates of the three vertices hit // in the case that no texture coordinates are supplied we will simply // treat it as if no intersection occurred if (geom.TextureCoordinates != null && index1 < geom.TextureCoordinates.Count && index2 < geom.TextureCoordinates.Count && index3 < geom.TextureCoordinates.Count) { Point texCoord1 = geom.TextureCoordinates[index1]; Point texCoord2 = geom.TextureCoordinates[index2]; Point texCoord3 = geom.TextureCoordinates[index3]; // get the final uv values based on the barycentric coordinates Point finalPoint = new Point(texCoord1.X * vertexWeight1 + texCoord2.X * vertexWeight2 + texCoord3.X * vertexWeight3, texCoord1.Y * vertexWeight1 + texCoord2.Y * vertexWeight2 + texCoord3.Y * vertexWeight3); // create and return a valid intersection info isectInfo = new ClosestIntersectionInfo(finalPoint, uiElem, imv3D); } } } } return isectInfo; }
/// <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="imv3DHit">The model that has the visual on it with capture</param> private void FindClosestIntersection(Point mousePos, List <HitTestEdge> edges, InteractiveVisual3D imv3DHit) { double closestDistance = Double.MaxValue; Point closestIntersection = new Point(); // the uv of the closest intersection // Find the closest point to the mouse position for (int i = 0; i < edges.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) { UIElement uiElemWCapture = (UIElement)Mouse.Captured; UIElement uiElemOnMesh = imv3DHit.InternalVisual; Rect contBounds = VisualTreeHelper.GetDescendantBounds(uiElemWCapture); Point ptOnVisual = TextureCoordsToVisualCoords(closestIntersection, uiElemOnMesh); Point ptRelToCapture = uiElemOnMesh.TransformToDescendant(uiElemWCapture).Transform(ptOnVisual); // we want to "ring" around the outside so things like buttons are not pressed // 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 <= contBounds.Left + 1) { ptRelToCapture.X -= BUFFER_SIZE; } if (ptRelToCapture.Y <= contBounds.Top + 1) { ptRelToCapture.Y -= BUFFER_SIZE; } if (ptRelToCapture.X >= contBounds.Right - 1) { ptRelToCapture.X += BUFFER_SIZE; } if (ptRelToCapture.Y >= contBounds.Bottom - 1) { ptRelToCapture.Y += BUFFER_SIZE; } Point finalVisualPoint = uiElemWCapture.TransformToAncestor(uiElemOnMesh).Transform(ptRelToCapture); _closestIntersectInfo = new ClosestIntersectionInfo(VisualCoordsToTextureCoords(finalVisualPoint, uiElemOnMesh), imv3DHit.InternalVisual, imv3DHit); } }
/// <summary> /// This function sets the passed in uiElem as the hidden visual, and aligns /// it so that the point the uv coordinates map to on the visual are located /// at the same location as mousePos. /// </summary> /// <param name="uiElem">The UIElement that should be the hidden visual</param> /// <param name="uv">The uv coordinates on that UIElement that should be aligned with mousePos</param> /// <param name="mousePos">The mouse location</param> /// <param name="scaleHiddenVisual">Whether to scale the visual in addition to moving it</param> /// <returns></returns> private bool UpdateHiddenVisual(ClosestIntersectionInfo isectInfo, Point mousePos, bool scaleHiddenVisual) { bool needsMouseReSync = false; double newOffsetX, newOffsetY; // compute positioning information if (isectInfo != null) { UIElement uiElem = isectInfo.UIElementHit; // set our UIElement to be the one passed in if (_hiddenVisual.Child != uiElem) { // we need to replace the old one with this new one UIElement prevVisual = _hiddenVisual.Child; // clear out uiElem from any of our hidden visuals if (_oldHiddenVisual.Child == uiElem) _oldHiddenVisual.Child = null; if (_oldKeyboardFocusVisual.Child == uiElem) _oldKeyboardFocusVisual.Child = null; // also clear out prevVisual if (_oldHiddenVisual.Child == prevVisual) _oldHiddenVisual.Child = null; if (_oldKeyboardFocusVisual.Child == prevVisual) _oldKeyboardFocusVisual.Child = null; // depending on whether or not it has focus, do two different things, either // use the _oldKeyboardFocusVisual or the _oldHiddenVisual Decorator _oldVisToUse = null; if (prevVisual != null && prevVisual.IsKeyboardFocusWithin) { _oldVisToUse = _oldKeyboardFocusVisual; } else { _oldVisToUse = _oldHiddenVisual; } // now safely link everything up _hiddenVisual.Child = uiElem; _oldVisToUse.Child = prevVisual; needsMouseReSync = true; } Point ptOnVisual = TextureCoordsToVisualCoords(isectInfo.PointHit, _hiddenVisual); newOffsetX = mousePos.X - ptOnVisual.X; newOffsetY = mousePos.Y - ptOnVisual.Y; } else { // because we didn't interesect with anything, we need to move the hidden visual off // screen so that it can no longer be interacted with newOffsetX = ActualWidth + 1; newOffsetY = ActualHeight + 1; } // compute the scale needed double newScale; if (scaleHiddenVisual) { newScale = Math.Max(Viewport3D.RenderSize.Width, Viewport3D.RenderSize.Height); } else { newScale = 1.0; } // do the actual positioning needsMouseReSync |= PositionHiddenVisual(newOffsetX, newOffsetY, newScale, mousePos); return needsMouseReSync; }
/// <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="imv3DHit">The model that has the visual on it with capture</param> private void FindClosestIntersection(Point mousePos, List<HitTestEdge> edges, InteractiveVisual3D imv3DHit) { double closestDistance = Double.MaxValue; Point closestIntersection = new Point(); // the uv of the closest intersection // Find the closest point to the mouse position for (int i=0; i<edges.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) { UIElement uiElemWCapture = (UIElement)Mouse.Captured; UIElement uiElemOnMesh = imv3DHit.InternalVisual; Rect contBounds = VisualTreeHelper.GetDescendantBounds(uiElemWCapture); Point ptOnVisual = TextureCoordsToVisualCoords(closestIntersection, uiElemOnMesh); Point ptRelToCapture = uiElemOnMesh.TransformToDescendant(uiElemWCapture).Transform(ptOnVisual); // we want to "ring" around the outside so things like buttons are not pressed // 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 <= contBounds.Left + 1) ptRelToCapture.X -= BUFFER_SIZE; if (ptRelToCapture.Y <= contBounds.Top + 1) ptRelToCapture.Y -= BUFFER_SIZE; if (ptRelToCapture.X >= contBounds.Right - 1) ptRelToCapture.X += BUFFER_SIZE; if (ptRelToCapture.Y >= contBounds.Bottom - 1) ptRelToCapture.Y += BUFFER_SIZE; Point finalVisualPoint = uiElemWCapture.TransformToAncestor(uiElemOnMesh).Transform(ptRelToCapture); _closestIntersectInfo = new ClosestIntersectionInfo(VisualCoordsToTextureCoords(finalVisualPoint, uiElemOnMesh), imv3DHit.InternalVisual, imv3DHit); } }
/// <summary> /// Constructs the InteractiveViewport3D /// </summary> public Interactive3DDecorator() : base() { // keep everything within our bounds so that the hidden visuals are only // accessable over the Viewport3D ClipToBounds = true; // the offset of the hidden visual and the transform associated with it _offsetX = _offsetY = 0.0; _scale = 1; // set up the hidden visual transforms _hiddenVisTranslate = new TranslateTransform(_offsetX, _offsetY); _hiddenVisScale = new ScaleTransform(_scale, _scale); _hiddenVisTransform = new TransformGroup(); _hiddenVisTransform.Children.Add(_hiddenVisScale); _hiddenVisTransform.Children.Add(_hiddenVisTranslate); // the layer that contains our moving visual _hiddenVisual = new Decorator(); _hiddenVisual.Opacity = 0.0; _hiddenVisual.RenderTransform = _hiddenVisTransform; // where we store the previous hidden visual so that it can be in the tree // after it is removed so any state (i.e. mouse over) can be updated. _oldHiddenVisual = new Decorator(); _oldHiddenVisual.Opacity = 0.0; // the keyboard focus visual _oldKeyboardFocusVisual = new Decorator(); _oldKeyboardFocusVisual.Opacity = 0.0; // add all of the hidden visuals PostViewportChildren.Add(_oldHiddenVisual); PostViewportChildren.Add(_oldKeyboardFocusVisual); PostViewportChildren.Add(_hiddenVisual); // initialize other member variables to null _closestIntersectInfo = null; _lastValidClosestIntersectInfo = null; AllowDrop = true; }
/// <summary> /// The HTResult function simply takes the intersection closest to the origin and /// and stores the intersection info for that closest intersection point. /// </summary> /// <param name="rawresult"></param> /// <returns></returns> private HitTestResultBehavior HTResult(System.Windows.Media.HitTestResult rawresult) { RayHitTestResult rayResult = rawresult as RayHitTestResult; HitTestResultBehavior hitTestResultBehavior = HitTestResultBehavior.Continue; // since we're hit testing a viewport3D we should be getting the ray hit test result back if (rayResult != null) { _closestIntersectInfo = GetIntersectionInfo(rayResult); hitTestResultBehavior = HitTestResultBehavior.Stop; } return hitTestResultBehavior; }
/// <summary> /// Arranges the hidden visuals so that interactivity is achieved. /// </summary> /// <param name="mouseposition">The location of the mouse</param> /// <returns>Whether a mouse resynch is necessary</returns> private bool ArrangeHiddenVisual(Point mouseposition, bool scaleHiddenVisual) { bool needMouseResync = false; // get the underlying viewport3D we're enhancing Viewport3D viewport3D = Viewport3D; // if the viewport3D exists - perform a hit test operation on the underlying visuals if (viewport3D != null) { // set up the hit test parameters PointHitTestParameters pointparams = new PointHitTestParameters(mouseposition); _closestIntersectInfo = null; _mouseCaptureInHiddenVisual = _hiddenVisual.IsMouseCaptureWithin; // first hit test - this one attempts to hit a visible mesh if possible VisualTreeHelper.HitTest(viewport3D, InteractiveMV3DFilter, HTResult, pointparams); // perform capture positioning if we didn't hit anything and something has capture if (_closestIntersectInfo == null && _mouseCaptureInHiddenVisual && _lastValidClosestIntersectInfo != null) { HandleMouseCaptureButOffMesh(_lastValidClosestIntersectInfo.InteractiveModelVisual3DHit, mouseposition); } else if (_closestIntersectInfo != null) { // save it for if we walk off the mesh and something has capture _lastValidClosestIntersectInfo = _closestIntersectInfo; } // update the location if we have positioning information needMouseResync = UpdateHiddenVisual(_closestIntersectInfo, mouseposition, scaleHiddenVisual); } return needMouseResync; }