Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / MS / Internal / Ink / EllipticalNodeOperations.cs / 1 / EllipticalNodeOperations.cs
//------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Ink;
using System.Windows.Input;
using System.Diagnostics;
namespace MS.Internal.Ink
{
///
/// StrokeNodeOperations implementation for elliptical nodes
///
internal class EllipticalNodeOperations : StrokeNodeOperations
{
///
/// Constructor
///
///
internal EllipticalNodeOperations(StylusShape nodeShape)
: base(nodeShape)
{
System.Diagnostics.Debug.Assert((nodeShape != null) && nodeShape.IsEllipse);
_radii = new Size(nodeShape.Width * 0.5, nodeShape.Height * 0.5);
// All operations with ellipses become simple(r) if transfrom ellipses into circles.
// Use the max of the radii for the radius of the circle
_radius = Math.Max(_radii.Width, _radii.Height);
// Compute ellipse-to-circle and circle-to-elliipse transforms. The former is used
// in hit-testing operations while the latter is used when computing vertices of
// a quadrangle connecting two ellipses
_transform = nodeShape.Transform;
_nodeShapeToCircle = _transform;
Debug.Assert(_nodeShapeToCircle.HasInverse, "About to invert a non-invertible transform");
_nodeShapeToCircle.Invert();
if (DoubleUtil.AreClose(_radii.Width, _radii.Height))
{
_circleToNodeShape = _transform;
}
else
{
// Reverse the rotation
if (false == DoubleUtil.IsZero(nodeShape.Rotation))
{
_nodeShapeToCircle.Rotate(-nodeShape.Rotation);
Debug.Assert(_nodeShapeToCircle.HasInverse, "Just rotated an invertible transform and produced a non-invertible one");
}
// Scale to enlarge
double sx, sy;
if (_radii.Width > _radii.Height)
{
sx = 1;
sy = _radii.Width / _radii.Height;
}
else
{
sx = _radii.Height / _radii.Width;
sy = 1;
}
_nodeShapeToCircle.Scale(sx, sy);
Debug.Assert(_nodeShapeToCircle.HasInverse, "Just scaled an invertible transform and produced a non-invertible one");
_circleToNodeShape = _nodeShapeToCircle;
_circleToNodeShape.Invert();
}
}
///
/// This is probably not the best (design-wise) but the cheapest way to tell
/// EllipticalNodeOperations from all other implementations of node operations.
///
internal override bool IsNodeShapeEllipse { get { return true; } }
///
/// Finds connecting points for a pair of stroke nodes
///
/// a node to connect
/// another node, next to beginNode
/// connecting quadrangle
internal override Quad GetConnectingQuad(StrokeNodeData beginNode, StrokeNodeData endNode)
{
if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position))
{
return Quad.Empty;
}
// Get the vector between the node positions
Vector spine = endNode.Position - beginNode.Position;
if (_nodeShapeToCircle.IsIdentity == false)
{
spine = _nodeShapeToCircle.Transform(spine);
}
double beginRadius = _radius * beginNode.PressureFactor;
double endRadius = _radius * endNode.PressureFactor;
// Get the vector and the distance between the node positions
double distanceSquared = spine.LengthSquared;
double delta = endRadius - beginRadius;
double deltaSquared = DoubleUtil.IsZero(delta) ? 0 : (delta * delta);
if (DoubleUtil.LessThanOrClose(distanceSquared, deltaSquared))
{
// One circle is contained within the other
return Quad.Empty;
}
// Thus, at this point, distance > 0, which avoids the DivideByZero error
// Also, here, distanceSquared > deltaSquared
// Thus, 0 <= rSin < 1
// Get the components of the radius vectors
double distance = Math.Sqrt(distanceSquared);
spine /= distance;
Vector rad = spine;
// Turn left
double temp = rad.Y;
rad.Y = -rad.X;
rad.X = temp;
Vector vectorToLeftTangent, vectorToRightTangent;
double rSinSquared = deltaSquared / distanceSquared;
if (DoubleUtil.IsZero(rSinSquared))
{
vectorToLeftTangent = rad;
vectorToRightTangent = -rad;
}
else
{
rad *= Math.Sqrt(1 - rSinSquared);
spine *= Math.Sqrt(rSinSquared);
if (beginNode.PressureFactor < endNode.PressureFactor)
{
spine = -spine;
}
vectorToLeftTangent = spine + rad;
vectorToRightTangent = spine - rad;
}
// Get the common tangent points
if (_circleToNodeShape.IsIdentity == false)
{
vectorToLeftTangent = _circleToNodeShape.Transform(vectorToLeftTangent);
vectorToRightTangent = _circleToNodeShape.Transform(vectorToRightTangent);
}
return new Quad(beginNode.Position + (vectorToLeftTangent * beginRadius),
endNode.Position + (vectorToLeftTangent * endRadius),
endNode.Position + (vectorToRightTangent * endRadius),
beginNode.Position + (vectorToRightTangent * beginRadius));
}
///
///
///
///
///
///
internal override IEnumerable GetContourSegments(StrokeNodeData node, Quad quad)
{
System.Diagnostics.Debug.Assert(node.IsEmpty == false);
if (quad.IsEmpty)
{
Point point = node.Position;
point.X += _radius;
yield return new ContourSegment(point, point, node.Position);
}
else if (_nodeShapeToCircle.IsIdentity)
{
yield return new ContourSegment(quad.A, quad.B);
yield return new ContourSegment(quad.B, quad.C, node.Position);
yield return new ContourSegment(quad.C, quad.D);
yield return new ContourSegment(quad.D, quad.A);
}
}
///
/// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses
///
///
///
///
internal override IEnumerable GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode)
{
Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode);
return base.GetContourSegments(endNode, quad);
}
///
/// Hit-tests a stroke segment defined by two nodes against a linear segment.
///
/// Begin node of the stroke segment to hit-test. Can be empty (none)
/// End node of the stroke segment
/// Pre-computed quadrangle connecting the two nodes.
/// Can be empty if the begion node is empty or when one node is entirely inside the other
/// an end point of the hitting linear segment
/// an end point of the hitting linear segment
/// true if the hitting segment intersect the contour comprised of the two stroke nodes
internal override bool HitTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
{
StrokeNodeData bigNode, smallNode;
if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
{
// Need to test one node only
bigNode = endNode;
smallNode = StrokeNodeData.Empty;
}
else
{
// In this case the size doesn't matter.
bigNode = beginNode;
smallNode = endNode;
}
// Compute the positions of the involved points relative to bigNode.
Vector hitBegin = hitBeginPoint - bigNode.Position;
Vector hitEnd = hitEndPoint - bigNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
bool isHit = false;
// Hit-test the big node
double bigRadius = _radius * bigNode.PressureFactor;
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= (bigRadius * bigRadius))
{
isHit = true;
}
else if (quad.IsEmpty == false)
{
// Hit-test the other node
Vector spineVector = smallNode.Position - bigNode.Position;
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
double smallRadius = _radius * smallNode.PressureFactor;
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if ((nearest.LengthSquared <= (smallRadius * smallRadius)) || HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint))
{
isHit = true;
}
}
return isHit;
}
///
/// Hit-tests a stroke segment defined by two nodes against another stroke segment.
///
/// Begin node of the stroke segment to hit-test. Can be empty (none)
/// End node of the stroke segment
/// Pre-computed quadrangle connecting the two nodes.
/// Can be empty if the begion node is empty or when one node is entirely inside the other
/// a collection of basic segments outlining the hitting contour
/// true if the contours intersect or overlap
internal override bool HitTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable hitContour)
{
StrokeNodeData bigNode, smallNode;
double bigRadiusSquared, smallRadiusSquared = 0;
Vector spineVector;
if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
{
// Need to test one node only
bigNode = endNode;
smallNode = StrokeNodeData.Empty;
spineVector = new Vector();
}
else
{
// In this case the size doesn't matter.
bigNode = beginNode;
smallNode = endNode;
smallRadiusSquared = _radius * smallNode.PressureFactor;
smallRadiusSquared *= smallRadiusSquared;
// Find position of smallNode relative to the bigNode.
spineVector = smallNode.Position - bigNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
}
bigRadiusSquared = _radius * bigNode.PressureFactor;
bigRadiusSquared *= bigRadiusSquared;
bool isHit = false;
// When hit-testing a contour against another contour, like in this case,
// the default implementation checks whether any edge (segment) of the hitting
// contour intersects with the contour of the ink segment. But this doesn't cover
// the case when the ink segment is entirely inside of the hitting segment.
// The bool variable isInside is used here to track that case. It answers the question
// 'Is ink contour inside if the hitting contour?'. It's initialized to 'true"
// and then verified for each edge of the hitting contour until there's a hit or
// until it's false.
bool isInside = true;
foreach (ContourSegment hitSegment in hitContour)
{
if (hitSegment.IsArc)
{
// ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
// and currently disabled in ErasingStroke
}
else
{
// Find position of the hitting segment relative to bigNode transformed to circle.
Vector hitBegin = hitSegment.Begin - bigNode.Position;
Vector hitEnd = hitBegin + hitSegment.Vector;
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
// Hit-test the big node
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= bigRadiusSquared)
{
isHit = true;
break;
}
// Hit-test the other node
if (quad.IsEmpty == false)
{
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if ((nearest.LengthSquared <= smallRadiusSquared) ||
HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End))
{
isHit = true;
break;
}
}
// While there's still a chance to find the both nodes inside the hitting contour,
// continue checking on position of the endNode relative to the edges of the hitting contour.
if (isInside &&
(WhereIsVectorAboutVector(endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
{
isInside = false;
}
}
}
return (isHit || isInside);
}
///
/// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment
///
/// Begin node of the ink segment
/// End node of the ink segment
/// Pre-computed quadrangle connecting the two ink nodes
/// egin point of the hitting segment
/// End point of the hitting segment
/// Exact location to cut at represented by StrokeFIndices
internal override StrokeFIndices CutTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
{
// Compute the positions of the involved points relative to the endNode.
Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
Vector hitBegin = hitBeginPoint - endNode.Position;
Vector hitEnd = hitEndPoint - endNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
StrokeFIndices result = StrokeFIndices.Empty;
// Hit-test the end node
double beginRadius = 0, endRadius = _radius * endNode.PressureFactor;
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= (endRadius * endRadius))
{
result.EndFIndex = StrokeFIndices.AfterLast;
result.BeginFIndex = beginNode.IsEmpty ? StrokeFIndices.BeforeFirst : 1;
}
if (beginNode.IsEmpty == false)
{
// Hit-test the first node
beginRadius = _radius * beginNode.PressureFactor;
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if (nearest.LengthSquared <= (beginRadius * beginRadius))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = 0;
}
}
}
// If both nodes are hit or nothing is hit at all, return.
if (result.IsFull || quad.IsEmpty
|| (result.IsEmpty && (HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint) == false)))
{
return result;
}
// Find out whether the {begin, end} segment intersects with the contour
// of the stroke segment {_lastNode, _thisNode}, and find the lower index
// of the fragment to cut out.
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
result.BeginFIndex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin - spineVector, hitEnd - spineVector);
}
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
}
if (IsInvalidCutTestResult(result))
{
return StrokeFIndices.Empty;
}
return result;
}
///
/// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour
/// (represented by an enumerator of Contoursegments).
///
/// The begin StrokeNodeData
/// The end StrokeNodeData
/// Connecing quadrangle between the begin and end inking node
/// The hitting ContourSegments
/// StrokeFIndices representing the location for cutting
internal override StrokeFIndices CutTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable hitContour)
{
// Compute the positions of the beginNode relative to the endNode.
Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
double beginRadius = 0, endRadius;
double beginRadiusSquared = 0, endRadiusSquared;
endRadius = _radius * endNode.PressureFactor;
endRadiusSquared = endRadius * endRadius;
if (beginNode.IsEmpty == false)
{
beginRadius = _radius * beginNode.PressureFactor;
beginRadiusSquared = beginRadius * beginRadius;
}
bool isInside = true;
StrokeFIndices result = StrokeFIndices.Empty;
foreach (ContourSegment hitSegment in hitContour)
{
if (hitSegment.IsArc)
{
// ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
// and currently disabled in ErasingStroke
}
else
{
Vector hitBegin = hitSegment.Begin - endNode.Position;
Vector hitEnd = hitBegin + hitSegment.Vector;
// If the node shape is an ellipse, transform the scene to turn
// the shape into circle.
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
bool isHit = false;
// Hit-test the end node
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared < endRadiusSquared)
{
isHit = true;
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = StrokeFIndices.AfterLast;
if (beginNode.IsEmpty)
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
break;
}
if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
break;
}
}
}
if ((beginNode.IsEmpty == false) && (!isHit || !DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)))
{
// Hit-test the first node
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if (nearest.LengthSquared < beginRadiusSquared)
{
isHit = true;
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
break;
}
}
}
}
// If both nodes are hit or nothing is hit at all, return.
if (beginNode.IsEmpty || (!isHit && (quad.IsEmpty ||
(HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End) == false))))
{
if (isInside && (WhereIsVectorAboutVector(
endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
{
isInside = false;
}
continue;
}
isInside = false;
// NTRAID#Window OS bug-1029694-2004/10/18-xiaotu, refactor the code to make it a method
// to increase the maintainability of the program. FxCop bug.
// Calculate the exact locations to cut.
CalculateCutLocations(spineVector, hitBegin, hitEnd, endRadius, beginRadius, ref result);
if (result.IsFull)
{
break;
}
}
}
//
if (!result.IsFull)
{
if (isInside == true)
{
System.Diagnostics.Debug.Assert(result.IsEmpty);
result = StrokeFIndices.Full;
}
else if ((DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)) && (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast)))
{
result.EndFIndex = StrokeFIndices.AfterLast;
}
else if ((DoubleUtil.AreClose(result.BeginFIndex,StrokeFIndices.AfterLast)) && (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
}
}
if (IsInvalidCutTestResult(result))
{
return StrokeFIndices.Empty;
}
return result;
}
///
/// Clip-Testing a circluar inking segment against a linear segment.
/// See http://tabletpc/longhorn/Specs/Rendering%20and%20Hit-Testing%20Ink%20in%20Avalon%20M11.doc section
/// 5.4.4.14 Clip-Testing a Circular Inking Segment against a Linear Segment for details of the algorithm
///
/// Represent the spine of the inking segment pointing from the beginNode to endNode
/// Radius of the beginNode
/// Radius of the endNode
/// Hitting segment start point
/// Hitting segment end point
/// A double which represents the location for cutting
private static double ClipTest(Vector spineVector, double beginRadius, double endRadius, Vector hitBegin, Vector hitEnd)
{
// First handle the special case when the spineVector is (0,0). In other words, this is the case
// when the stylus stays at the the location but pressure changes.
if (DoubleUtil.IsZero(spineVector.X) && DoubleUtil.IsZero(spineVector.Y))
{
System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(beginRadius, endRadius) == false);
Vector nearest = GetNearest(hitBegin, hitEnd);
double radius;
if (nearest.X == 0)
{
radius = Math.Abs(nearest.Y);
}
else if (nearest.Y == 0)
{
radius = Math.Abs(nearest.X);
}
else
{
radius = nearest.Length;
}
return AdjustFIndex((radius - beginRadius) / (endRadius - beginRadius));
}
// This change to ClipTest with a point if the two hitting segment are close enough.
if (DoubleUtil.AreClose(hitBegin, hitEnd))
{
return ClipTest(spineVector, beginRadius, endRadius, hitBegin);
}
double findex;
Vector hitVector = hitEnd - hitBegin;
if (DoubleUtil.IsZero(Vector.Determinant(spineVector, hitVector)))
{
// hitVector and spineVector are parallel
findex = ClipTest(spineVector, beginRadius, endRadius, GetNearest(hitBegin, hitEnd));
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
else
{
// On the line defined by the segment find point P1Xp, the nearest to the beginNode.Position
double x = GetProjectionFIndex(hitBegin, hitEnd);
Vector P1Xp = hitBegin + (hitVector * x);
if (P1Xp.LengthSquared < (beginRadius * beginRadius))
{
System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(x) == false);
findex = ClipTest(spineVector, beginRadius, endRadius, (0 > x) ? hitBegin : hitEnd);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
else
{
// Find the projection point P of endNode.Position to the line (beginNode.Position, B).
Vector P1P2p = spineVector + GetProjection(-spineVector, P1Xp - spineVector);
//System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(P1P2p.LengthSquared));
//System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length));
// There checks are here since if either fail no real solution can be caculated and we may
// as well bail out now and save the caculations that are below.
if (DoubleUtil.IsZero(P1P2p.LengthSquared) || DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length))
return 1d;
// Calculate the findex of the point to split the ink segment at.
findex = (P1Xp.Length - beginRadius) / (endRadius - beginRadius + P1P2p.Length);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
// Find the projection of the split point to the line of this segment.
Vector S = spineVector * findex;
double r = GetProjectionFIndex(hitBegin - S, hitEnd - S);
// If the nearest point misses the segment, then find the findex
// of the node nearest to the segment.
if (false == DoubleUtil.IsBetweenZeroAndOne(r))
{
findex = ClipTest(spineVector, beginRadius, endRadius, (0 > r) ? hitBegin : hitEnd);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
}
}
return AdjustFIndex(findex);
}
///
/// Clip-Testing a circular inking segment again a hitting point.
///
/// What need to find out a doulbe value s, which is between 0 and 1, such that
/// DistanceOf(hit - s*spine) = beginRadius + s * (endRadius - beginRadius)
/// That is
/// (hit.X-s*spine.X)^2 + (hit.Y-s*spine.Y)^2 = [beginRadius + s*(endRadius-beginRadius)]^2
/// Rearrange
/// A*s^2 + B*s + C = 0
/// where the value of A, B and C are described in the source code.
/// Solving for s:
/// s = (-B + sqrt(B^2-4A*C))/(2A) or s = (-B - sqrt(B^2-4A*C))/(2A)
/// The smaller value between 0 and 1 is the one we want and discard the other one.
///
/// Represent the spine of the inking segment pointing from the beginNode to endNode
/// Radius of the beginNode
/// Radius of the endNode
/// The hitting point
/// A double which represents the location for cutting
private static double ClipTest(Vector spine, double beginRadius, double endRadius, Vector hit)
{
double radDiff = endRadius - beginRadius;
double A = spine.X*spine.X + spine.Y*spine.Y - radDiff*radDiff;
double B = -2.0f*(hit.X*spine.X + hit.Y * spine.Y + beginRadius*radDiff);
double C = hit.X * hit.X + hit.Y * hit.Y - beginRadius * beginRadius;
// There checks are here since if either fail no real solution can be caculated and we may
// as well bail out now and save the caculations that are below.
if (DoubleUtil.IsZero(A) || !DoubleUtil.GreaterThanOrClose(B*B, 4.0f*A*C))
return 1d;
double tmp = Math.Sqrt(B*B-4.0f * A * C);
double s1 = (-B + tmp)/(2.0f * A);
double s2 = (-B - tmp)/(2.0f * A);
double findex;
if (DoubleUtil.IsBetweenZeroAndOne(s1) && DoubleUtil.IsBetweenZeroAndOne(s1))
{
findex = Math.Min(s1, s2);
}
else if (DoubleUtil.IsBetweenZeroAndOne(s1))
{
findex = s1;
}
else if (DoubleUtil.IsBetweenZeroAndOne(s2))
{
findex = s2;
}
else
{
// There is still possiblity that value like 1.0000000000000402 is not considered
// as "IsOne" by DoubleUtil class. We should be at either one of the following two cases:
// 1. s1/s2 around 0 but not close enough (say -0.0000000000001)
// 2. s1/s2 around 1 but not close enought (say 1.0000000000000402)
if (s1 > 1d && s2 > 1d)
{
findex = 1d;
}
else if (s1 < 0d && s2 < 0d)
{
findex = 0d;
}
else
{
findex = Math.Abs(Math.Min(s1, s2) - 0d) < Math.Abs(Math.Max(s1, s2) - 1d) ? 0d : 1d;
}
}
return AdjustFIndex(findex);
}
///
/// Helper function to find out the relative location of a segment {segBegin, segEnd} against
/// a strokeNode (spine).
///
/// the spineVector of the StrokeNode
/// Start position of the line segment
/// End position of the line segment
/// HitResult
private static HitResult WhereIsNodeAboutSegment(Vector spine, Vector segBegin, Vector segEnd)
{
HitResult whereabout = HitResult.Right;
Vector segVector = segEnd - segBegin;
if ((WhereIsVectorAboutVector(-segBegin, segVector) == HitResult.Left)
&& !DoubleUtil.IsZero(Vector.Determinant(spine, segVector)))
{
whereabout = HitResult.Left;
}
return whereabout;
}
///
/// Helper method to calculate the exact location to cut
///
/// Vector the relative location of the two inking nodes
/// the begin point of the hitting segment
/// the end point of the hitting segment
/// endNode radius
/// beginNode radius
/// StrokeFIndices representing the location for cutting
private void CalculateCutLocations(
Vector spineVector, Vector hitBegin, Vector hitEnd, double endRadius, double beginRadius, ref StrokeFIndices result)
{
// Find out whether the {hitBegin, hitEnd} segment intersects with the contour
// of the stroke segment, and find the lower index of the fragment to cut out.
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
if (WhereIsNodeAboutSegment(spineVector, hitBegin, hitEnd) == HitResult.Left)
{
double findex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
if (findex > result.EndFIndex)
{
result.EndFIndex = findex;
}
}
}
// Find out whether the {hitBegin, hitEnd} segment intersects with the contour
// of the stroke segment, and find the higher index of the fragment to cut out.
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
hitBegin -= spineVector;
hitEnd -= spineVector;
if (WhereIsNodeAboutSegment(-spineVector, hitBegin, hitEnd) == HitResult.Left)
{
double findex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin, hitEnd);
if (findex < result.BeginFIndex)
{
result.BeginFIndex = findex;
}
}
}
}
private double _radius = 0;
private Size _radii;
private Matrix _transform;
private Matrix _nodeShapeToCircle;
private Matrix _circleToNodeShape;
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Ink;
using System.Windows.Input;
using System.Diagnostics;
namespace MS.Internal.Ink
{
///
/// StrokeNodeOperations implementation for elliptical nodes
///
internal class EllipticalNodeOperations : StrokeNodeOperations
{
///
/// Constructor
///
///
internal EllipticalNodeOperations(StylusShape nodeShape)
: base(nodeShape)
{
System.Diagnostics.Debug.Assert((nodeShape != null) && nodeShape.IsEllipse);
_radii = new Size(nodeShape.Width * 0.5, nodeShape.Height * 0.5);
// All operations with ellipses become simple(r) if transfrom ellipses into circles.
// Use the max of the radii for the radius of the circle
_radius = Math.Max(_radii.Width, _radii.Height);
// Compute ellipse-to-circle and circle-to-elliipse transforms. The former is used
// in hit-testing operations while the latter is used when computing vertices of
// a quadrangle connecting two ellipses
_transform = nodeShape.Transform;
_nodeShapeToCircle = _transform;
Debug.Assert(_nodeShapeToCircle.HasInverse, "About to invert a non-invertible transform");
_nodeShapeToCircle.Invert();
if (DoubleUtil.AreClose(_radii.Width, _radii.Height))
{
_circleToNodeShape = _transform;
}
else
{
// Reverse the rotation
if (false == DoubleUtil.IsZero(nodeShape.Rotation))
{
_nodeShapeToCircle.Rotate(-nodeShape.Rotation);
Debug.Assert(_nodeShapeToCircle.HasInverse, "Just rotated an invertible transform and produced a non-invertible one");
}
// Scale to enlarge
double sx, sy;
if (_radii.Width > _radii.Height)
{
sx = 1;
sy = _radii.Width / _radii.Height;
}
else
{
sx = _radii.Height / _radii.Width;
sy = 1;
}
_nodeShapeToCircle.Scale(sx, sy);
Debug.Assert(_nodeShapeToCircle.HasInverse, "Just scaled an invertible transform and produced a non-invertible one");
_circleToNodeShape = _nodeShapeToCircle;
_circleToNodeShape.Invert();
}
}
///
/// This is probably not the best (design-wise) but the cheapest way to tell
/// EllipticalNodeOperations from all other implementations of node operations.
///
internal override bool IsNodeShapeEllipse { get { return true; } }
///
/// Finds connecting points for a pair of stroke nodes
///
/// a node to connect
/// another node, next to beginNode
/// connecting quadrangle
internal override Quad GetConnectingQuad(StrokeNodeData beginNode, StrokeNodeData endNode)
{
if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position))
{
return Quad.Empty;
}
// Get the vector between the node positions
Vector spine = endNode.Position - beginNode.Position;
if (_nodeShapeToCircle.IsIdentity == false)
{
spine = _nodeShapeToCircle.Transform(spine);
}
double beginRadius = _radius * beginNode.PressureFactor;
double endRadius = _radius * endNode.PressureFactor;
// Get the vector and the distance between the node positions
double distanceSquared = spine.LengthSquared;
double delta = endRadius - beginRadius;
double deltaSquared = DoubleUtil.IsZero(delta) ? 0 : (delta * delta);
if (DoubleUtil.LessThanOrClose(distanceSquared, deltaSquared))
{
// One circle is contained within the other
return Quad.Empty;
}
// Thus, at this point, distance > 0, which avoids the DivideByZero error
// Also, here, distanceSquared > deltaSquared
// Thus, 0 <= rSin < 1
// Get the components of the radius vectors
double distance = Math.Sqrt(distanceSquared);
spine /= distance;
Vector rad = spine;
// Turn left
double temp = rad.Y;
rad.Y = -rad.X;
rad.X = temp;
Vector vectorToLeftTangent, vectorToRightTangent;
double rSinSquared = deltaSquared / distanceSquared;
if (DoubleUtil.IsZero(rSinSquared))
{
vectorToLeftTangent = rad;
vectorToRightTangent = -rad;
}
else
{
rad *= Math.Sqrt(1 - rSinSquared);
spine *= Math.Sqrt(rSinSquared);
if (beginNode.PressureFactor < endNode.PressureFactor)
{
spine = -spine;
}
vectorToLeftTangent = spine + rad;
vectorToRightTangent = spine - rad;
}
// Get the common tangent points
if (_circleToNodeShape.IsIdentity == false)
{
vectorToLeftTangent = _circleToNodeShape.Transform(vectorToLeftTangent);
vectorToRightTangent = _circleToNodeShape.Transform(vectorToRightTangent);
}
return new Quad(beginNode.Position + (vectorToLeftTangent * beginRadius),
endNode.Position + (vectorToLeftTangent * endRadius),
endNode.Position + (vectorToRightTangent * endRadius),
beginNode.Position + (vectorToRightTangent * beginRadius));
}
///
///
///
///
///
///
internal override IEnumerable GetContourSegments(StrokeNodeData node, Quad quad)
{
System.Diagnostics.Debug.Assert(node.IsEmpty == false);
if (quad.IsEmpty)
{
Point point = node.Position;
point.X += _radius;
yield return new ContourSegment(point, point, node.Position);
}
else if (_nodeShapeToCircle.IsIdentity)
{
yield return new ContourSegment(quad.A, quad.B);
yield return new ContourSegment(quad.B, quad.C, node.Position);
yield return new ContourSegment(quad.C, quad.D);
yield return new ContourSegment(quad.D, quad.A);
}
}
///
/// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses
///
///
///
///
internal override IEnumerable GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode)
{
Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode);
return base.GetContourSegments(endNode, quad);
}
///
/// Hit-tests a stroke segment defined by two nodes against a linear segment.
///
/// Begin node of the stroke segment to hit-test. Can be empty (none)
/// End node of the stroke segment
/// Pre-computed quadrangle connecting the two nodes.
/// Can be empty if the begion node is empty or when one node is entirely inside the other
/// an end point of the hitting linear segment
/// an end point of the hitting linear segment
/// true if the hitting segment intersect the contour comprised of the two stroke nodes
internal override bool HitTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
{
StrokeNodeData bigNode, smallNode;
if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
{
// Need to test one node only
bigNode = endNode;
smallNode = StrokeNodeData.Empty;
}
else
{
// In this case the size doesn't matter.
bigNode = beginNode;
smallNode = endNode;
}
// Compute the positions of the involved points relative to bigNode.
Vector hitBegin = hitBeginPoint - bigNode.Position;
Vector hitEnd = hitEndPoint - bigNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
bool isHit = false;
// Hit-test the big node
double bigRadius = _radius * bigNode.PressureFactor;
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= (bigRadius * bigRadius))
{
isHit = true;
}
else if (quad.IsEmpty == false)
{
// Hit-test the other node
Vector spineVector = smallNode.Position - bigNode.Position;
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
double smallRadius = _radius * smallNode.PressureFactor;
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if ((nearest.LengthSquared <= (smallRadius * smallRadius)) || HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint))
{
isHit = true;
}
}
return isHit;
}
///
/// Hit-tests a stroke segment defined by two nodes against another stroke segment.
///
/// Begin node of the stroke segment to hit-test. Can be empty (none)
/// End node of the stroke segment
/// Pre-computed quadrangle connecting the two nodes.
/// Can be empty if the begion node is empty or when one node is entirely inside the other
/// a collection of basic segments outlining the hitting contour
/// true if the contours intersect or overlap
internal override bool HitTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable hitContour)
{
StrokeNodeData bigNode, smallNode;
double bigRadiusSquared, smallRadiusSquared = 0;
Vector spineVector;
if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
{
// Need to test one node only
bigNode = endNode;
smallNode = StrokeNodeData.Empty;
spineVector = new Vector();
}
else
{
// In this case the size doesn't matter.
bigNode = beginNode;
smallNode = endNode;
smallRadiusSquared = _radius * smallNode.PressureFactor;
smallRadiusSquared *= smallRadiusSquared;
// Find position of smallNode relative to the bigNode.
spineVector = smallNode.Position - bigNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
}
bigRadiusSquared = _radius * bigNode.PressureFactor;
bigRadiusSquared *= bigRadiusSquared;
bool isHit = false;
// When hit-testing a contour against another contour, like in this case,
// the default implementation checks whether any edge (segment) of the hitting
// contour intersects with the contour of the ink segment. But this doesn't cover
// the case when the ink segment is entirely inside of the hitting segment.
// The bool variable isInside is used here to track that case. It answers the question
// 'Is ink contour inside if the hitting contour?'. It's initialized to 'true"
// and then verified for each edge of the hitting contour until there's a hit or
// until it's false.
bool isInside = true;
foreach (ContourSegment hitSegment in hitContour)
{
if (hitSegment.IsArc)
{
// ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
// and currently disabled in ErasingStroke
}
else
{
// Find position of the hitting segment relative to bigNode transformed to circle.
Vector hitBegin = hitSegment.Begin - bigNode.Position;
Vector hitEnd = hitBegin + hitSegment.Vector;
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
// Hit-test the big node
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= bigRadiusSquared)
{
isHit = true;
break;
}
// Hit-test the other node
if (quad.IsEmpty == false)
{
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if ((nearest.LengthSquared <= smallRadiusSquared) ||
HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End))
{
isHit = true;
break;
}
}
// While there's still a chance to find the both nodes inside the hitting contour,
// continue checking on position of the endNode relative to the edges of the hitting contour.
if (isInside &&
(WhereIsVectorAboutVector(endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
{
isInside = false;
}
}
}
return (isHit || isInside);
}
///
/// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment
///
/// Begin node of the ink segment
/// End node of the ink segment
/// Pre-computed quadrangle connecting the two ink nodes
/// egin point of the hitting segment
/// End point of the hitting segment
/// Exact location to cut at represented by StrokeFIndices
internal override StrokeFIndices CutTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
{
// Compute the positions of the involved points relative to the endNode.
Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
Vector hitBegin = hitBeginPoint - endNode.Position;
Vector hitEnd = hitEndPoint - endNode.Position;
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
StrokeFIndices result = StrokeFIndices.Empty;
// Hit-test the end node
double beginRadius = 0, endRadius = _radius * endNode.PressureFactor;
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared <= (endRadius * endRadius))
{
result.EndFIndex = StrokeFIndices.AfterLast;
result.BeginFIndex = beginNode.IsEmpty ? StrokeFIndices.BeforeFirst : 1;
}
if (beginNode.IsEmpty == false)
{
// Hit-test the first node
beginRadius = _radius * beginNode.PressureFactor;
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if (nearest.LengthSquared <= (beginRadius * beginRadius))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = 0;
}
}
}
// If both nodes are hit or nothing is hit at all, return.
if (result.IsFull || quad.IsEmpty
|| (result.IsEmpty && (HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint) == false)))
{
return result;
}
// Find out whether the {begin, end} segment intersects with the contour
// of the stroke segment {_lastNode, _thisNode}, and find the lower index
// of the fragment to cut out.
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
result.BeginFIndex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin - spineVector, hitEnd - spineVector);
}
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
}
if (IsInvalidCutTestResult(result))
{
return StrokeFIndices.Empty;
}
return result;
}
///
/// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour
/// (represented by an enumerator of Contoursegments).
///
/// The begin StrokeNodeData
/// The end StrokeNodeData
/// Connecing quadrangle between the begin and end inking node
/// The hitting ContourSegments
/// StrokeFIndices representing the location for cutting
internal override StrokeFIndices CutTest(
StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable hitContour)
{
// Compute the positions of the beginNode relative to the endNode.
Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
// If the node shape is an ellipse, transform the scene to turn the shape to a circle
if (_nodeShapeToCircle.IsIdentity == false)
{
spineVector = _nodeShapeToCircle.Transform(spineVector);
}
double beginRadius = 0, endRadius;
double beginRadiusSquared = 0, endRadiusSquared;
endRadius = _radius * endNode.PressureFactor;
endRadiusSquared = endRadius * endRadius;
if (beginNode.IsEmpty == false)
{
beginRadius = _radius * beginNode.PressureFactor;
beginRadiusSquared = beginRadius * beginRadius;
}
bool isInside = true;
StrokeFIndices result = StrokeFIndices.Empty;
foreach (ContourSegment hitSegment in hitContour)
{
if (hitSegment.IsArc)
{
// ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
// and currently disabled in ErasingStroke
}
else
{
Vector hitBegin = hitSegment.Begin - endNode.Position;
Vector hitEnd = hitBegin + hitSegment.Vector;
// If the node shape is an ellipse, transform the scene to turn
// the shape into circle.
if (_nodeShapeToCircle.IsIdentity == false)
{
hitBegin = _nodeShapeToCircle.Transform(hitBegin);
hitEnd = _nodeShapeToCircle.Transform(hitEnd);
}
bool isHit = false;
// Hit-test the end node
Vector nearest = GetNearest(hitBegin, hitEnd);
if (nearest.LengthSquared < endRadiusSquared)
{
isHit = true;
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
result.EndFIndex = StrokeFIndices.AfterLast;
if (beginNode.IsEmpty)
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
break;
}
if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
break;
}
}
}
if ((beginNode.IsEmpty == false) && (!isHit || !DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)))
{
// Hit-test the first node
nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
if (nearest.LengthSquared < beginRadiusSquared)
{
isHit = true;
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
break;
}
}
}
}
// If both nodes are hit or nothing is hit at all, return.
if (beginNode.IsEmpty || (!isHit && (quad.IsEmpty ||
(HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End) == false))))
{
if (isInside && (WhereIsVectorAboutVector(
endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
{
isInside = false;
}
continue;
}
isInside = false;
// NTRAID#Window OS bug-1029694-2004/10/18-xiaotu, refactor the code to make it a method
// to increase the maintainability of the program. FxCop bug.
// Calculate the exact locations to cut.
CalculateCutLocations(spineVector, hitBegin, hitEnd, endRadius, beginRadius, ref result);
if (result.IsFull)
{
break;
}
}
}
//
if (!result.IsFull)
{
if (isInside == true)
{
System.Diagnostics.Debug.Assert(result.IsEmpty);
result = StrokeFIndices.Full;
}
else if ((DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)) && (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast)))
{
result.EndFIndex = StrokeFIndices.AfterLast;
}
else if ((DoubleUtil.AreClose(result.BeginFIndex,StrokeFIndices.AfterLast)) && (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)))
{
result.BeginFIndex = StrokeFIndices.BeforeFirst;
}
}
if (IsInvalidCutTestResult(result))
{
return StrokeFIndices.Empty;
}
return result;
}
///
/// Clip-Testing a circluar inking segment against a linear segment.
/// See http://tabletpc/longhorn/Specs/Rendering%20and%20Hit-Testing%20Ink%20in%20Avalon%20M11.doc section
/// 5.4.4.14 Clip-Testing a Circular Inking Segment against a Linear Segment for details of the algorithm
///
/// Represent the spine of the inking segment pointing from the beginNode to endNode
/// Radius of the beginNode
/// Radius of the endNode
/// Hitting segment start point
/// Hitting segment end point
/// A double which represents the location for cutting
private static double ClipTest(Vector spineVector, double beginRadius, double endRadius, Vector hitBegin, Vector hitEnd)
{
// First handle the special case when the spineVector is (0,0). In other words, this is the case
// when the stylus stays at the the location but pressure changes.
if (DoubleUtil.IsZero(spineVector.X) && DoubleUtil.IsZero(spineVector.Y))
{
System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(beginRadius, endRadius) == false);
Vector nearest = GetNearest(hitBegin, hitEnd);
double radius;
if (nearest.X == 0)
{
radius = Math.Abs(nearest.Y);
}
else if (nearest.Y == 0)
{
radius = Math.Abs(nearest.X);
}
else
{
radius = nearest.Length;
}
return AdjustFIndex((radius - beginRadius) / (endRadius - beginRadius));
}
// This change to ClipTest with a point if the two hitting segment are close enough.
if (DoubleUtil.AreClose(hitBegin, hitEnd))
{
return ClipTest(spineVector, beginRadius, endRadius, hitBegin);
}
double findex;
Vector hitVector = hitEnd - hitBegin;
if (DoubleUtil.IsZero(Vector.Determinant(spineVector, hitVector)))
{
// hitVector and spineVector are parallel
findex = ClipTest(spineVector, beginRadius, endRadius, GetNearest(hitBegin, hitEnd));
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
else
{
// On the line defined by the segment find point P1Xp, the nearest to the beginNode.Position
double x = GetProjectionFIndex(hitBegin, hitEnd);
Vector P1Xp = hitBegin + (hitVector * x);
if (P1Xp.LengthSquared < (beginRadius * beginRadius))
{
System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(x) == false);
findex = ClipTest(spineVector, beginRadius, endRadius, (0 > x) ? hitBegin : hitEnd);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
else
{
// Find the projection point P of endNode.Position to the line (beginNode.Position, B).
Vector P1P2p = spineVector + GetProjection(-spineVector, P1Xp - spineVector);
//System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(P1P2p.LengthSquared));
//System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length));
// There checks are here since if either fail no real solution can be caculated and we may
// as well bail out now and save the caculations that are below.
if (DoubleUtil.IsZero(P1P2p.LengthSquared) || DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length))
return 1d;
// Calculate the findex of the point to split the ink segment at.
findex = (P1Xp.Length - beginRadius) / (endRadius - beginRadius + P1P2p.Length);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
// Find the projection of the split point to the line of this segment.
Vector S = spineVector * findex;
double r = GetProjectionFIndex(hitBegin - S, hitEnd - S);
// If the nearest point misses the segment, then find the findex
// of the node nearest to the segment.
if (false == DoubleUtil.IsBetweenZeroAndOne(r))
{
findex = ClipTest(spineVector, beginRadius, endRadius, (0 > r) ? hitBegin : hitEnd);
System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
}
}
}
return AdjustFIndex(findex);
}
///
/// Clip-Testing a circular inking segment again a hitting point.
///
/// What need to find out a doulbe value s, which is between 0 and 1, such that
/// DistanceOf(hit - s*spine) = beginRadius + s * (endRadius - beginRadius)
/// That is
/// (hit.X-s*spine.X)^2 + (hit.Y-s*spine.Y)^2 = [beginRadius + s*(endRadius-beginRadius)]^2
/// Rearrange
/// A*s^2 + B*s + C = 0
/// where the value of A, B and C are described in the source code.
/// Solving for s:
/// s = (-B + sqrt(B^2-4A*C))/(2A) or s = (-B - sqrt(B^2-4A*C))/(2A)
/// The smaller value between 0 and 1 is the one we want and discard the other one.
///
/// Represent the spine of the inking segment pointing from the beginNode to endNode
/// Radius of the beginNode
/// Radius of the endNode
/// The hitting point
/// A double which represents the location for cutting
private static double ClipTest(Vector spine, double beginRadius, double endRadius, Vector hit)
{
double radDiff = endRadius - beginRadius;
double A = spine.X*spine.X + spine.Y*spine.Y - radDiff*radDiff;
double B = -2.0f*(hit.X*spine.X + hit.Y * spine.Y + beginRadius*radDiff);
double C = hit.X * hit.X + hit.Y * hit.Y - beginRadius * beginRadius;
// There checks are here since if either fail no real solution can be caculated and we may
// as well bail out now and save the caculations that are below.
if (DoubleUtil.IsZero(A) || !DoubleUtil.GreaterThanOrClose(B*B, 4.0f*A*C))
return 1d;
double tmp = Math.Sqrt(B*B-4.0f * A * C);
double s1 = (-B + tmp)/(2.0f * A);
double s2 = (-B - tmp)/(2.0f * A);
double findex;
if (DoubleUtil.IsBetweenZeroAndOne(s1) && DoubleUtil.IsBetweenZeroAndOne(s1))
{
findex = Math.Min(s1, s2);
}
else if (DoubleUtil.IsBetweenZeroAndOne(s1))
{
findex = s1;
}
else if (DoubleUtil.IsBetweenZeroAndOne(s2))
{
findex = s2;
}
else
{
// There is still possiblity that value like 1.0000000000000402 is not considered
// as "IsOne" by DoubleUtil class. We should be at either one of the following two cases:
// 1. s1/s2 around 0 but not close enough (say -0.0000000000001)
// 2. s1/s2 around 1 but not close enought (say 1.0000000000000402)
if (s1 > 1d && s2 > 1d)
{
findex = 1d;
}
else if (s1 < 0d && s2 < 0d)
{
findex = 0d;
}
else
{
findex = Math.Abs(Math.Min(s1, s2) - 0d) < Math.Abs(Math.Max(s1, s2) - 1d) ? 0d : 1d;
}
}
return AdjustFIndex(findex);
}
///
/// Helper function to find out the relative location of a segment {segBegin, segEnd} against
/// a strokeNode (spine).
///
/// the spineVector of the StrokeNode
/// Start position of the line segment
/// End position of the line segment
/// HitResult
private static HitResult WhereIsNodeAboutSegment(Vector spine, Vector segBegin, Vector segEnd)
{
HitResult whereabout = HitResult.Right;
Vector segVector = segEnd - segBegin;
if ((WhereIsVectorAboutVector(-segBegin, segVector) == HitResult.Left)
&& !DoubleUtil.IsZero(Vector.Determinant(spine, segVector)))
{
whereabout = HitResult.Left;
}
return whereabout;
}
///
/// Helper method to calculate the exact location to cut
///
/// Vector the relative location of the two inking nodes
/// the begin point of the hitting segment
/// the end point of the hitting segment
/// endNode radius
/// beginNode radius
/// StrokeFIndices representing the location for cutting
private void CalculateCutLocations(
Vector spineVector, Vector hitBegin, Vector hitEnd, double endRadius, double beginRadius, ref StrokeFIndices result)
{
// Find out whether the {hitBegin, hitEnd} segment intersects with the contour
// of the stroke segment, and find the lower index of the fragment to cut out.
if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
{
if (WhereIsNodeAboutSegment(spineVector, hitBegin, hitEnd) == HitResult.Left)
{
double findex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
if (findex > result.EndFIndex)
{
result.EndFIndex = findex;
}
}
}
// Find out whether the {hitBegin, hitEnd} segment intersects with the contour
// of the stroke segment, and find the higher index of the fragment to cut out.
if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
{
hitBegin -= spineVector;
hitEnd -= spineVector;
if (WhereIsNodeAboutSegment(-spineVector, hitBegin, hitEnd) == HitResult.Left)
{
double findex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin, hitEnd);
if (findex < result.BeginFIndex)
{
result.BeginFIndex = findex;
}
}
}
}
private double _radius = 0;
private Size _radii;
private Matrix _transform;
private Matrix _nodeShapeToCircle;
private Matrix _circleToNodeShape;
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- KeyEvent.cs
- CmsInterop.cs
- DBDataPermission.cs
- MobileResource.cs
- RelatedEnd.cs
- EDesignUtil.cs
- EmbeddedMailObject.cs
- PkcsMisc.cs
- GlobalAllocSafeHandle.cs
- DataSourceCache.cs
- HttpCapabilitiesBase.cs
- WorkingDirectoryEditor.cs
- PackageDigitalSignatureManager.cs
- RIPEMD160.cs
- XamlPoint3DCollectionSerializer.cs
- RuntimeResourceSet.cs
- GridViewCancelEditEventArgs.cs
- SessionPageStateSection.cs
- ZoneIdentityPermission.cs
- MoveSizeWinEventHandler.cs
- FixedSOMImage.cs
- LocalBuilder.cs
- EntityDataSourceWizardForm.cs
- Screen.cs
- _UriSyntax.cs
- MulticastIPAddressInformationCollection.cs
- OracleParameterBinding.cs
- CornerRadius.cs
- StylusEditingBehavior.cs
- CapabilitiesSection.cs
- UnsignedPublishLicense.cs
- WebControl.cs
- InternalResources.cs
- PropertySegmentSerializer.cs
- hwndwrapper.cs
- FieldToken.cs
- SqlBulkCopy.cs
- XsltSettings.cs
- IconHelper.cs
- ThumbAutomationPeer.cs
- Cloud.cs
- Int32AnimationBase.cs
- XmlSchemaAnnotation.cs
- InstanceCreationEditor.cs
- SecurityDocument.cs
- WebPartExportVerb.cs
- Span.cs
- SrgsRule.cs
- Panel.cs
- ConfigurationElementProperty.cs
- ADConnectionHelper.cs
- PlainXmlWriter.cs
- UnsafeNativeMethods.cs
- DataGridViewSelectedRowCollection.cs
- ToolStripContainerDesigner.cs
- CroppedBitmap.cs
- EntityTypeBase.cs
- NavigateUrlConverter.cs
- SoapObjectWriter.cs
- CodePrimitiveExpression.cs
- CommentGlyph.cs
- Function.cs
- OdbcCommandBuilder.cs
- AnnotationMap.cs
- WindowsProgressbar.cs
- OutputCacheModule.cs
- RemoteWebConfigurationHostServer.cs
- SelectionItemPattern.cs
- StringUtil.cs
- DocumentSchemaValidator.cs
- FormViewPageEventArgs.cs
- ReliableMessagingVersion.cs
- CodeComment.cs
- InheritanceAttribute.cs
- EnumerationRangeValidationUtil.cs
- PeerTransportListenAddressConverter.cs
- ToolStripPanelRow.cs
- CachedRequestParams.cs
- CapacityStreamGeometryContext.cs
- XamlPoint3DCollectionSerializer.cs
- OracleMonthSpan.cs
- SQLUtility.cs
- DbParameterHelper.cs
- CapacityStreamGeometryContext.cs
- XDRSchema.cs
- DispatcherTimer.cs
- DbConvert.cs
- TextBox.cs
- SourceFileBuildProvider.cs
- XPathMessageFilterElementComparer.cs
- FixedDocumentSequencePaginator.cs
- PeerNearMe.cs
- MethodToken.cs
- FontFamilyIdentifier.cs
- InvokeGenerator.cs
- IndexedGlyphRun.cs
- XsltOutput.cs
- SortedDictionary.cs
- Selection.cs
- StringConverter.cs