/// <summary> /// Checks if (p.x+i,p.y+j) is in the same direction as the stroke is headed. /// </summary> /// <param name="s">the stroke</param> /// <param name="ind">the index of the point in the stroke</param> /// <param name="context">the number of points to use as context</param> /// <param name="i">x translation</param> /// <param name="j">y translation</param> /// <returns></returns> private bool checkDirection(Substroke s, int ind, int context, int i, int j) { double strokeDir = direction(s, ind, context); Sketch.Point p = s.PointsL[ind]; double connectionDir = direction(p.Y, p.Y + j, p.X, p.X + i); double diff = Math.Abs(strokeDir - connectionDir); return(diff < Math.PI / 6d /*&& (curvature(s,ind,1)-curvature(s, ind, 20)) < 1*/); }
private void readXY(string filename) { string name = ""; string strLine = ""; string[] strs = new string[0]; int numPoints = 0; Sketch.Point q = new Sketch.Point(); StreamReader reader = new StreamReader(filename); name = reader.ReadLine(); strLine = reader.ReadLine(); numPoints = Convert.ToInt32(strLine); List <Sketch.Point> points = new List <Sketch.Point>(numPoints); float X = 0; float Y = 0; ulong T = 0; for (int i = 0; i < numPoints; i++) { strLine = reader.ReadLine(); strs = strLine.Split(new char[] { ',' }, 3); X = (float)Convert.ToInt32(strs[0]); Y = (float)Convert.ToInt32(strs[1]); T = Convert.ToUInt64(strs[2]); Sketch.Point p = new Sketch.Point(X, Y, null, T); points.Add(p); } bool newClass = true; for (int i = 0; i < this.classes.Count; i++) { if (name == this.classes[i].Name) { newClass = false; } } if (newClass) { addAsNewClass(name, points); } else { addToClass(name, points); } reader.Close(); }
/// <summary> /// an attempt to find labels based on what didn't have connections in certain directions. /// didn't work very well. /// </summary> /// <param name="sketch"></param> /// <param name="graph"></param> private static void AddLabelShapes(ref Sketch.Sketch sketch, NeighborhoodMap graph) { foreach (Substroke s in graph.Substrokes) { Sketch.Point a = s.PointsL[0]; Sketch.Point b = s.PointsL[s.PointsL.Count - 1]; Sketch.Point m = s.PointsL[s.PointsL.Count / 2]; double dir = 0d; int x = 0; bool found = true; if (graph[s].Count > 1) { bool N, E, W, S; N = E = W = S = false; foreach (Neighbor n in graph[s]) { //dir = direction(m, n.neighbor.PointsL[n.neighbor.PointsL.Count/2]); dir = NeighborhoodMap.direction(m, n.dest); if (dir > -Math.PI / 3 && dir < Math.PI / 3) { E = true; } if (dir > Math.PI / 6 && dir < 5 * Math.PI / 6) { N = true; } if (dir > 2 * Math.PI / 3 || dir < -2 * Math.PI / 3) { W = true; } if (dir > -5 * Math.PI / 6 && dir < Math.PI / 6) { S = true; } } found = !((N && S) || (E && W)); } if (found) { Shape label = new Shape(new List <Substroke>(new Substroke[] { s }), new XmlStructs.XmlShapeAttrs(true)); label.XmlAttrs.Type = "Label"; label.XmlAttrs.Name = "shape"; label.XmlAttrs.Time = 1; sketch.AddShape(label); } } }
private double feature2(List <Sketch.Point> points) { double featureValue = 0.0; if (points.Count > 2) { Sketch.Point p1 = points[0]; int n = 2; Sketch.Point p2 = points[n]; while (p1.X == p2.X && p1.Y == p2.Y) { n++; if (n < points.Count) { p2 = points[n]; } else { break; } } double numer = (double)p2.Y - (double)p1.Y; double denom = Math.Sqrt(Math.Pow(p2.X - p1.X, 2.0) + Math.Pow(p2.Y - p1.Y, 2.0)); if (denom != 0) { featureValue = numer / denom; } else { featureValue = 0.0; } } return(featureValue); }
/// <summary> /// Takes an Ink.Stroke and outputs an internal representation that can /// then be output to an XML file /// </summary> /// <param name="inkStroke">Ink.Stroke which will have extracted from it the information necessary to store a Stroke in MIT XML.</param> /// <param name="transf">Transformation matrix to shift the stroke by</param> /// <param name="shift">Boolean for if we should shift by the transformation matrix</param> /// <returns>PointsAndShape object that stores the extracted information.</returns> public static Sketch.Stroke StripStrokeData(Microsoft.Ink.Stroke inkStroke, Matrix transf, bool shift) { int i; int length; // Get the timestamp for the function using an undocumented GUID (Microsoft.Ink.StrokeProperty.TimeID) to // get the time as a byte array, which we then convert to a long ulong theTime; if (inkStroke.ExtendedProperties.Contains(Microsoft.Ink.StrokeProperty.TimeID) && (inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID] != null)) { byte[] timeBits = inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID].Data as byte[]; // Filetime format ulong fileTime = BitConverter.ToUInt64(timeBits, 0); // MIT time format theTime = (fileTime - 116444736000000000) / 10000; //string time = theTime.ToString(); } else { //theTime = (ulong)System.DateTime.Now.Ticks; //theTime = ((ulong)System.DateTime.Now.Ticks - 116444736000000000) / 10000; theTime = ((ulong)DateTime.Now.ToFileTime() - 116444736000000000) / 10000; } // Grab X and Y int[] xData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.X); int[] yData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.Y); if (shift) { // Shift X and Y according to transformation matrix System.Drawing.Point[] pointData = new System.Drawing.Point[xData.Length]; length = xData.Length; for (i = 0; i < length; i++) { pointData[i] = new System.Drawing.Point(xData[i], yData[i]); } transf.TransformPoints(pointData); //Console.WriteLine("x: " + (pointData[0].X - xData[0]) + " y: " + (pointData[0].Y - yData[0])); for (i = 0; i < length; i++) { xData[i] = pointData[i].X; yData[i] = pointData[i].Y; } } Microsoft.Ink.DrawingAttributes drawingAttributes = inkStroke.DrawingAttributes; System.Drawing.Rectangle boundingBox = inkStroke.GetBoundingBox(); // Grab the color int color = drawingAttributes.Color.ToArgb(); // THIS IS DRAWING POINT (PENTIP) HEIGHT AND WIDTH... WE DONT HAVE ANYWHERE TO PUT IT... //string height = inkStroke.DrawingAttributes.Height.ToString(); //string width = inkStroke.DrawingAttributes.Width.ToString(); float penWidth = drawingAttributes.Width; float penHeight = drawingAttributes.Height; // Grab height and width of total stroke //float height = boundingBox.Height; //float width = boundingBox.Width; // Grab penTip string penTip = drawingAttributes.PenTip.ToString(); // Grab raster string raster = drawingAttributes.RasterOperation.ToString(); //float x = Convert.ToSingle(boundingBox.X); //float y = Convert.ToSingle(boundingBox.Y); //float leftx = Convert.ToSingle(boundingBox.Left); //float topy = Convert.ToSingle(boundingBox.Top); // If the pressure data is included take it, otherwise set to 255/2's // Not all pressure data is in the range of 0 - 255. Some tablets // register pressure in the range 0 - 1023, while others are 0 - 32768 int[] pressureData; if (ReadJnt.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.NormalPressure)) { pressureData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.NormalPressure); int max = 0; foreach (int p in pressureData) { max = Math.Max(max, p); } if (max > 1024) // Tablet reading in range 0 - 32767 { double conversion = 255.0 / 32767.0; List <int> temp = new List <int>(pressureData.Length); foreach (int p in pressureData) { temp.Add((int)(p * conversion)); } pressureData = temp.ToArray(); } else if (max > 255) // Tablet reading in range 0 - 1023 { double conversion = 255.0 / 1023.0; List <int> temp = new List <int>(pressureData.Length); foreach (int p in pressureData) { temp.Add((int)(p * conversion)); } pressureData = temp.ToArray(); } } else { pressureData = new int[xData.Length]; length = pressureData.Length; for (i = 0; i < length; ++i) { pressureData[i] = (255 - 0) / 2; } } // If the time data is included take it, otherwise set to the timestamp plus and increment ulong[] adjustedTime = new ulong[xData.Length]; int[] timerTick; if (ReadJnt.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.TimerTick)) { timerTick = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.TimerTick); length = timerTick.Length; for (i = 0; i < length; i++) { // This may be incorrect, we never encountered this because Microsoft Journal doesn't save TimerTick data. // We know that the timestamp of a stroke is made when the pen is lifted. // Add the time of the stroke to each offset adjustedTime[i] = theTime + (ulong)timerTick[i] * 10000; } } else { timerTick = new int[xData.Length]; length = timerTick.Length; for (i = 0; i < length; i++) { // We believe this to be the standard sample rate. The multiplication by 1,000 is to convert from // seconds to milliseconds. // // Our time is in the form of milliseconds since Jan 1, 1970 // // NOTE: The timestamp for the stroke is made WHEN THE PEN IS LIFTED adjustedTime[i] = theTime - (ulong)((1 / SAMPLE_RATE * 1000) * (length - i)); } } // Create the array of ID's as new Guid's Guid[] idData = new Guid[xData.Length]; length = idData.Length; for (i = 0; i < length; i++) { idData[i] = Guid.NewGuid(); } // Create the internal representation Sketch.Stroke converterStroke = new Sketch.Stroke(); converterStroke.Name = "stroke"; converterStroke.Time = (ulong)theTime; converterStroke.Source = "Converter"; // Add all of the points to the stroke length = inkStroke.PacketCount; List <Point> pointsToAdd = new List <Point>(); for (i = 0; i < length; i++) { float currX = Convert.ToSingle(xData[i]); float currY = Convert.ToSingle(yData[i]); XmlStructs.XmlPointAttrs attrs = new XmlStructs.XmlPointAttrs( currX, currY, Convert.ToUInt16(pressureData[i]), Convert.ToUInt64(adjustedTime[i]), "point", idData[i]); Sketch.Point toAdd = new Sketch.Point(attrs); } //NOTE: X, Y, WIDTH, HEIGHT done later... boundingbox seems to be off Sketch.Substroke substroke = new Sketch.Substroke(pointsToAdd); substroke.Name = "substroke"; substroke.Color = color; substroke.PenTip = penTip; substroke.PenWidth = penWidth; substroke.PenHeight = penHeight; substroke.Raster = raster; substroke.Source = "ConverterJnt"; substroke.Start = idData[0]; substroke.End = idData[idData.Length - 1]; converterStroke.AddSubstroke(substroke); return(converterStroke); }
public static Bitmap createFromShape(Shape shape, int width, int height, bool preserveAspect) { Rect bounds = shape.Bounds; int x = (int)bounds.X; int y = (int)bounds.Y; int w = (int)bounds.Width; int h = (int)bounds.Height; if (preserveAspect) { float nPercent = 0; float nPercentW = 0; float nPercentH = 0; nPercentW = ((float)width / (float)w); nPercentH = ((float)height / (float)h); if (nPercentH < nPercentW) { nPercent = nPercentH; } else { nPercent = nPercentW; } width = (int)(w * nPercent); height = (int)(h * nPercent); } float scaleX = (float)width / w; float scaleY = (float)height / h; Bitmap result = new Bitmap(width, height); Graphics g = Graphics.FromImage(result); // transparent background g.CompositingMode = CompositingMode.SourceCopy; Brush clear = new SolidBrush(Color.Transparent); g.FillRectangle(clear, 0, 0, w, h); // draw over transparent background g.CompositingMode = CompositingMode.SourceOver; Pen pen = new Pen(Color.Black, 3); foreach (Substroke stroke in shape.Substrokes) { int count = stroke.Points.Length; if (count <= 1) { continue; } Sketch.Point previous = stroke.Points[0]; for (int i = 1; i < count; i++) { Sketch.Point current = stroke.Points[i]; g.DrawLine( pen, (previous.X - x) * scaleX, (previous.Y - y) * scaleY, (current.X - x) * scaleX, (current.Y - y) * scaleY); previous = current; } } return(result); }
/// <summary> /// Generate the neighborhood graph. /// </summary> /// <param name="sketch">the sketch to generate the graph for</param> /// <param name="radius">the allowable distance one stroke to another</param> /// <param name="directed">if the graph should be directed</param> /// <param name="context">how many points of context should be used for direction</param> /// <returns>a populated NeighborhoodMap</returns> private void generateNeighborhood (Sketch.Sketch sketch, double radius, bool directed, int context) { long maxX = 0; // points indexed by Y*maxX+X Dictionary <long, List <Substroke> > intPoints = new Dictionary <long, List <Substroke> >(); map = new Dictionary <Substroke, NeighborList>(); points = new List <LinkedPoint>(); /* * First we find the maximum X value, which we use to index into * the points dictionary. */ foreach (Substroke s in sketch.Substrokes) { for (int i = 0; i < s.PointsL.Count; i++) { Sketch.Point p = s.PointsL[i]; if (p.X > maxX) { maxX = (long)p.X; } points.Add(new LinkedPoint(s, i)); } } maxX += 1; /* * Store the location of each point currently in the sketch */ foreach (Substroke s in sketch.Substrokes) { foreach (Sketch.Point p in s.PointsL) { long key = (long)p.Y * maxX; key += (long)p.X; if (!intPoints.ContainsKey(key)) { intPoints.Add(key, new List <Substroke>()); } intPoints[key].Add(s); } } /* * For each substroke, check if any of its points are close to * any other points from the sketch. */ foreach (Substroke s in sketch.Substrokes) { map.Add(s, new NeighborList()); for (int index = 0; index < s.PointsL.Count; index++) { if (index > 10 && index < s.PointsL.Count - 10) { continue; } if ((index == 0 || index == s.PointsL.Count - 1) && directed) { continue; } Sketch.Point p = s.PointsL[index]; if (directed && curvature(s, index, 1) - curvature(s, index, 20) > 1) { continue; } for (int i = 0; i < (int)radius; i++) { for (int j = 0; j < (int)radius && Math.Sqrt(i * i + j * j) < radius; j++) { if (i == 0 & j == 0) { continue; } /* * if p is the origin, we need to check the point translated by |i| * in the X-direction and |j| in the Y-direction in each of the 4 * quadrants. */ long key1 = (long)(p.Y + j) * maxX + (long)(p.X + i); long key2 = (long)(p.Y + j) * maxX + (long)(p.X - i); long key3 = (long)(p.Y - j) * maxX + (long)(p.X + i); long key4 = (long)(p.Y - j) * maxX + (long)(p.X - i); if (intPoints.ContainsKey(key1)) { if (!directed || checkDirection(s, index, context, i, j)) { foreach (Substroke test in intPoints[key1]) { if (test != s) { map[s].addPoint(test, new System.Drawing.Point((int)(p.X + i), (int)(p.Y + j)), new System.Drawing.Point((int)(p.X), (int)(p.Y))); } } } } if (intPoints.ContainsKey(key2)) { if (!directed || checkDirection(s, index, context, -i, j)) { foreach (Substroke test in intPoints[key2]) { if (test != s) { map[s].addPoint(test, new System.Drawing.Point((int)(p.X - i), (int)(p.Y + j)), new System.Drawing.Point((int)(p.X), (int)(p.Y))); } } } } if (intPoints.ContainsKey(key3)) { if (!directed || checkDirection(s, index, context, i, -j)) { foreach (Substroke test in intPoints[key3]) { if (test != s) { map[s].addPoint(test, new System.Drawing.Point((int)(p.X + i), (int)(p.Y - j)), new System.Drawing.Point((int)(p.X), (int)(p.Y))); } } } } if (intPoints.ContainsKey(key4)) { if (!directed || checkDirection(s, index, context, -i, -j)) { foreach (Substroke test in intPoints[key4]) { if (test != s) { map[s].addPoint(test, new System.Drawing.Point((int)(p.X - i), (int)(p.Y - j)), new System.Drawing.Point((int)(p.X), (int)(p.Y))); } } } } } } } } /* * Once we have constructed the Neighbor objects above, * the avg Point has the sum of all the points' X- and Y-values. * Now that we know the total, we can divide to find the true average. */ foreach (Substroke s in map.Keys) { for (int j = 0; j < map[s].Count; j++) { map[s][j].src.X /= map[s][j].num; map[s][j].src.Y /= map[s][j].num; map[s][j].dest.X /= map[s][j].num; map[s][j].dest.Y /= map[s][j].num; } } }
internal static double direction(Sketch.Point p1, System.Drawing.Point p2) { return(direction(p1.Y, p2.Y, p1.X, p2.X)); }
/// <summary> /// Takes an Ink.Stroke and outputs an internal representation that can /// then be output to an XML file /// </summary> /// <param name="inkStroke">Ink.Stroke which will have extracted from it the information necessary to store a Stroke in MIT XML.</param> /// <param name="transf">Transformation matrix to shift the stroke by</param> /// <param name="shift">Boolean for if we should shift by the transformation matrix</param> /// <returns>PointsAndShape object that stores the extracted information.</returns> private Sketch.Stroke StripStrokeData(Microsoft.Ink.Stroke inkStroke, Matrix transf, bool shift) { int i; int length; // Get the timestamp for the function using an undocumented GUID (Microsoft.Ink.StrokeProperty.TimeID) to // get the time as a byte array, which we then convert to a long long theTime; if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.StrokeProperty.TimeID)) { byte[] timeBits = inkStroke.ExtendedProperties[Microsoft.Ink.StrokeProperty.TimeID].Data as byte[]; long fileTime = BitConverter.ToInt64(timeBits, 0); //filetime format theTime = (fileTime - 116444736000000000) / 10000; //MIT time format // string time = theTime.ToString(); } else { theTime = DateTime.Now.Ticks; } // Grab X and Y int[] xData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.X); int[] yData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.Y); if (shift) { // Shift X and Y according to transformation matrix System.Drawing.Point[] pointData = new System.Drawing.Point [xData.Length]; length = xData.Length; for (i = 0; i < length; i++) { pointData[i] = new System.Drawing.Point(xData[i], yData[i]); } transf.TransformPoints(pointData); //Console.WriteLine("x: " + (pointData[0].X - xData[0]) + " y: " + (pointData[0].Y - yData[0])); for (i = 0; i < length; i++) { xData[i] = pointData[i].X; yData[i] = pointData[i].Y; } } Microsoft.Ink.DrawingAttributes drawingAttributes = inkStroke.DrawingAttributes; System.Drawing.Rectangle boundingBox = inkStroke.GetBoundingBox(); // Grab the color int color = drawingAttributes.Color.ToArgb(); // THIS IS DRAWING POINT (PENTIP) HEIGHT AND WIDTH... WE DONT HAVE ANYWHERE TO PUT IT... //string height = inkStroke.DrawingAttributes.Height.ToString(); //string width = inkStroke.DrawingAttributes.Width.ToString(); // Grab height and width of total stroke float height = boundingBox.Height; float width = boundingBox.Width; // Grab penTip string penTip = drawingAttributes.PenTip.ToString(); // Grab raster string raster = drawingAttributes.RasterOperation.ToString(); float x = Convert.ToSingle(boundingBox.X); float y = Convert.ToSingle(boundingBox.Y); float leftx = Convert.ToSingle(boundingBox.Left); float topy = Convert.ToSingle(boundingBox.Top); // If the pressure data is included take it, otherwise set to 255/2's int[] pressureData; if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.NormalPressure)) { pressureData = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.NormalPressure); } else { pressureData = new int[xData.Length]; length = pressureData.Length; for (i = 0; i < length; ++i) { pressureData[i] = (255 - 0) / 2; } } // If the time data is included take it, otherwise set to the timestamp plus and increment long[] adjustedTime = new long[xData.Length]; int[] timerTick; if (this.IsPropertyIncluded(inkStroke, Microsoft.Ink.PacketProperty.TimerTick)) { timerTick = inkStroke.GetPacketValuesByProperty(Microsoft.Ink.PacketProperty.TimerTick); length = timerTick.Length; for (i = 0; i < length; i++) { // This may be incorrect, we never encountered this because Microsoft Journal doesn't save TimerTick data. // We know that the timestamp of a stroke is made when the pen is lifted. adjustedTime[i] = theTime + (long)timerTick[i] * 10000; //add the time of the stroke to each offset } } else { timerTick = new int[xData.Length]; length = timerTick.Length; for (i = 0; i < length; i++) { // We believe this to be the standard sample rate. The multiplication by 1,000 is to convert from // seconds to milliseconds. // // Our time is in the form of milliseconds since Jan 1, 1970 // // NOTE: The timestamp for the stroke is made WHEN THE PEN IS LIFTED adjustedTime[i] = theTime - (long)((1 / SAMPLE_RATE * 1000) * (length - i)); } } // Create the array of ID's as new Guid's Guid[] idData = new Guid[xData.Length]; length = idData.Length; for (i = 0; i < length; i++) { idData[i] = Guid.NewGuid(); } // Create the internal representation Sketch.Stroke converterStroke = new Sketch.Stroke(); converterStroke.XmlAttrs.Id = System.Guid.NewGuid(); converterStroke.XmlAttrs.Name = "stroke"; converterStroke.XmlAttrs.Time = (ulong)theTime; converterStroke.XmlAttrs.Type = "stroke"; converterStroke.XmlAttrs.Source = "Converter"; Sketch.Substroke substroke = new Sketch.Substroke(); substroke.XmlAttrs.Id = System.Guid.NewGuid(); substroke.XmlAttrs.Name = "substroke"; substroke.XmlAttrs.Time = (ulong)theTime; substroke.XmlAttrs.Type = "substroke"; substroke.XmlAttrs.Color = color; substroke.XmlAttrs.Height = height; substroke.XmlAttrs.Width = width; substroke.XmlAttrs.PenTip = penTip; substroke.XmlAttrs.Raster = raster; substroke.XmlAttrs.X = x; substroke.XmlAttrs.Y = y; substroke.XmlAttrs.LeftX = leftx; substroke.XmlAttrs.TopY = topy; substroke.XmlAttrs.Source = "Converter"; substroke.XmlAttrs.Start = idData[0]; substroke.XmlAttrs.End = idData[idData.Length - 1]; // Add all of the points to the stroke length = inkStroke.PacketCount; for (i = 0; i < length; i++) { Sketch.Point toAdd = new Sketch.Point(); toAdd.XmlAttrs.Name = "point"; toAdd.XmlAttrs.X = Convert.ToSingle(xData[i]); toAdd.XmlAttrs.Y = Convert.ToSingle(yData[i]); toAdd.XmlAttrs.Pressure = Convert.ToUInt16(pressureData[i]); toAdd.XmlAttrs.Time = Convert.ToUInt64(adjustedTime[i]); toAdd.XmlAttrs.Id = idData[i]; substroke.AddPoint(toAdd); } converterStroke.AddSubstroke(substroke); return(converterStroke); }
/// <summary> /// Takes a wire and returns the shape that is closest to the end of the wire /// along the line shooting off of the end. /// </summary> /// <param name="beginning">Which end we're looking at.</param> /// <param name="wire">The wire we're considering.</param> /// <param name="sketch">The sketch providing the context for the wire.</param> /// <returns>The shape that's closest to the tip of the wire.</returns> private Sketch.Shape findClosest(bool beginning, Sketch.Substroke wire, Sketch.Sketch sketch) { List <Sketch.Substroke> intersects = new List <Sketch.Substroke>(); // One method of removing hooks (in situations where the substroke loops around to // get closer to its endpoint. List <Sketch.Point> points = Featurefy.Compute.DeHook(wire).PointsL; points.Sort(); if (!beginning) { points.Reverse(); } // First we will generate the line segments shooting off the endpoints of the wires. Sketch.LineSegment segment1; int count = 20; if (points.Count < 2 * count) { segment1 = new Sketch.LineSegment(points[0], points[points.Count - 1]); } else { segment1 = new Sketch.LineSegment(points.GetRange(0, count).ToArray()); } Sketch.Substroke closestSubstroke = null; Sketch.Substroke closestLinestroke = null; double closestSubstrokeDistance = Double.PositiveInfinity; double closestLineDistance = Double.PositiveInfinity; // Now, consider every substroke in the sketch. foreach (Sketch.Substroke substroke in sketch.SubstrokesL) { if (substroke.Id != wire.Id) { // We want this stroke if it is close to the line shooting out of the end of the wire // Or if it is close enough to the endpoint. foreach (Sketch.Point point in substroke.Points) { double linedistance = point.distance(segment1.getClosestPointOnLine(point)); double pointdistance = point.distance(points[0]); if (pointdistance < closestSubstrokeDistance) { closestSubstrokeDistance = point.distance(points[0]); closestSubstroke = substroke; } if (2 * linedistance + pointdistance < closestLineDistance) { // We want to make sure it's on the correct side of the line. Sketch.Point linepoint = segment1.getClosestPointOnLine(point); if ((segment1.StartPoint.X < segment1.EndPoint.X && linepoint.X < segment1.StartPoint.X) || (segment1.StartPoint.X > segment1.EndPoint.X && linepoint.X > segment1.StartPoint.X)) { closestLineDistance = 2 * linedistance + pointdistance; closestLinestroke = substroke; } } } } } // Returning the right substroke: double thresholdLineDistance = 50.0; // MAGIC NUMBER! A somewhat arbitrary number pulled out of the air. double thresholdSubstrokeDistance = 20.0; // MAGIC NUMBER! A somewhat arbitrary number pulled out of the air. // The threshold is stricter on substrokes that are nearby but // are not in the right direction // Substrokes that are close to the projected line get priority. if ((closestLineDistance < thresholdLineDistance) && (closestLinestroke != null) && (closestLinestroke.ParentShape != null)) { return(closestLinestroke.ParentShape); } // If the Linestroke wasn't good enough, try the closest substroke. else if ((closestSubstrokeDistance < thresholdSubstrokeDistance) && (closestSubstroke != null) && (closestSubstroke.ParentShape != null)) { return(closestSubstroke.ParentShape); } // We get here if none of the substrokes were close enough else { return(null); } //// Now we need to decide whether we want the closest substroke, or the one closest to the line... //if (closestLineDistance / 5 > closestSubstrokeDistance) // if (closestSubstroke == null) // return null; // else // return closestSubstroke.ParentShapes[0]; //else // if (closestLinestroke == null) // return null; // else // return closestLinestroke.ParentShapes[0]; }