private MechanismBase MakeRodRopeOrSpring(Stroke stroke, RigidBodyBase headbody, RigidBodyBase tailbody) { // Rod, Rope, or Spring: we decide based on straightness, curvyness, and loopiness. int np = stroke.PacketCount; Point head = stroke.GetPoint(0), tail = stroke.GetPoint(np - 1); double distance = Geometry.DistanceBetween(head, tail); StrokeGeometry sg = new StrokeGeometry(stroke); double length = sg.IntegrateLength(); // Consider spring: analyze total net curvature of the stroke, and call it a // spring if it makes at least 540 degrees (1.5 loops) in the same direction. double tt = StrokeAnalyzer.AnalyzeTotalCurvature(stroke, 100.0); if (Math.Abs(Geometry.Rad2Deg(tt)) > 540.0) { dbg.WriteLine("new SpringMech"); SpringMech newmech = new SpringMech(); newmech.EndpointA = BodyRef.For(headbody, head); newmech.EndpointB = BodyRef.For(tailbody, tail); newmech.extension = distance; newmech.stiffness = MathEx.Square(tt) / 100; //Heuristic: th²/100 feels about right. newmech.strokeid = stroke.Id; doc.Mechanisms.Add(newmech); return(newmech); } // Straight and narrow? double rodropethreshold = 1.1; //heuristic if (length / distance < rodropethreshold) { dbg.WriteLine("new RodMech"); RodMech newmech = new RodMech(); newmech.EndpointA = BodyRef.For(headbody, head); newmech.EndpointB = BodyRef.For(tailbody, tail); newmech.length = distance; newmech.strokeid = stroke.Id; doc.Mechanisms.Add(newmech); return(newmech); } else { dbg.WriteLine("new RopeMech"); RopeMech newmech = new RopeMech(); newmech.EndpointA = BodyRef.For(headbody, head); newmech.EndpointB = BodyRef.For(tailbody, tail); newmech.length = length; newmech.strokeid = stroke.Id; doc.Mechanisms.Add(newmech); return(newmech); } }
static MagicDocument CreateDocumentFixture() { MagicDocument doc = new MagicDocument(); RigidBodyBase body; body = new EllipticalBody(); body.strokeid = 1; doc.Bodies.Add(body); body = new EllipticalBody(); body.strokeid = 2; doc.Bodies.Add(body); body = new PolygonalBody(); body.strokeid = 3; doc.Bodies.Add(body); ((PolygonalBody)body).Vertices = new Point[] { new Point(120, 340), new Point(560, 340), new Point(560, 780), new Point(120, 780) }; BindingMechanismBase mech; mech = new RodMech(); mech.EndpointA = BodyRef.For((RigidBodyBase)doc.Bodies[0], Point.Empty); mech.EndpointB = BodyRef.For((RigidBodyBase)doc.Bodies[1], Point.Empty); doc.Mechanisms.Add(mech); mech = new RopeMech(); mech.EndpointA = BodyRef.For((RigidBodyBase)doc.Bodies[1], Point.Empty); mech.EndpointB = BodyRef.For((RigidBodyBase)doc.Bodies[2], Point.Empty); doc.Mechanisms.Add(mech); mech = new SpringMech(); mech.EndpointA = BodyRef.For((RigidBodyBase)doc.Bodies[2], Point.Empty); mech.EndpointB = BodyRef.For((RigidBodyBase)doc.Bodies[0], Point.Empty); doc.Mechanisms.Add(mech); return(doc); }
private void inkoverlay_Stroke(object sender, InkCollectorStrokeEventArgs e) { dbg.WriteLine("----- inkoverlay_Stroke -----"); // Ensure we're on the UI thread. dbg.Assert(!this.InvokeRequired); // Check to make sure we're not erasing. if (inkoverlay.EditingMode == InkOverlayEditingMode.Delete) { return; } try // To prevent exceptions from propagating back to ink runtime. { // Hook for tap-to-select feature, in lasso-mode. if (inkoverlay.EditingMode == InkOverlayEditingMode.Select) { TryTapToSelect(e.Stroke); return; } // Analyze stroke geometry. bool closed; Point[] vertices; double tolerance = 500.0; //Heuristic: 500 seems about right. StrokeAnalyzer.AnalyzeClosedness(e.Stroke, tolerance, out closed, out vertices); // Interpret stroke in document-context: first, consider closed strokes. if (closed) { // Check for a small elliptical-gesture over two or more bodies. // If so, it's a pin joint! Rectangle bbox = e.Stroke.GetBoundingBox(); Point midp = Geometry.Midpoint(bbox); RigidBodyBase[] hits = doc.HitTestBodies(midp); if (hits.Length >= 2 && bbox.Width < 1000 && bbox.Height < 1000) { RigidBodyBase top = hits[0]; RigidBodyBase bottom = hits[1]; // Snap to CG if close to either top's or bottom's. if (Geometry.DistanceBetween(midp, top.CG) < 500.0) { midp = top.CG; } else if (Geometry.DistanceBetween(midp, bottom.CG) < 500.0) { midp = bottom.CG; } dbg.WriteLine("new JointMech"); JointMech newmech = new JointMech(); newmech.EndpointA = BodyRef.For(bottom, midp); newmech.EndpointB = BodyRef.For(top, midp); newmech.strokeid = e.Stroke.Id; doc.Mechanisms.Add(newmech); // Repaint area around the newmech (unions with stroke bbox, below). Rectangle dirtybbox = newmech.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); return; } else { // Larger stroke, and/or no centerpoint hits -- form a new solid body. RigidBodyBase newbody = MakeBodyFromClosedStroke(e.Stroke, vertices); // Repaint area around the newbody (unions with stroke bbox, below). Rectangle dirtybbox = newbody.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); // Select it, to show the smart tag. inkoverlay.Selection = this.MakeSingleStrokeCollection(e.Stroke); return; } } // An unclosed stroke -- // Check if head and/or tail is hit on existing bodies. int np = e.Stroke.PacketCount; Point head = e.Stroke.GetPoint(0), tail = e.Stroke.GetPoint(np - 1); RigidBodyBase[] headhits = doc.HitTestBodies(head); RigidBodyBase[] tailhits = doc.HitTestBodies(tail); if (headhits.Length == 0 && tailhits.Length == 0) { // Neither head or tail hit, so let's try harder to make a body // out of this stroke. Point[] dummy; tolerance = 2000.0; //Heuristic: vastly relax closure tolerance. StrokeAnalyzer.AnalyzeClosedness(e.Stroke, tolerance, out closed, out dummy); if (closed) { RigidBodyBase newbody = MakeBodyFromClosedStroke(e.Stroke, vertices); // Repaint area around the newbody (unions with stroke bbox, below). Rectangle dirtybbox = newbody.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); // Select it, to show the smart tag. inkoverlay.Selection = this.MakeSingleStrokeCollection(e.Stroke); return; } else if (Geometry.DistanceBetween(head, tail) > 500.0) { // Interpret this stroke as a gravity-vector. GravitationalForceMech newgrav = new GravitationalForceMech(); newgrav.Body = null; // Applies to all bodies! newgrav.origin = head; newgrav.vector = Vector.FromPoints(head, tail); // Repaint area around the gravity vector (unions with stroke bbox, below). Rectangle dirtybbox = newgrav.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); // Throw out the current gravity-vector stroke, if it exists. if (doc.Gravity != null) { dirtybbox = doc.Gravity.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); Stroke old = GetStrokeById(doc.Gravity.strokeid); doc.Ink.DeleteStroke(old); } newgrav.strokeid = e.Stroke.Id; doc.Gravity = newgrav; return; } else { // This stroke is probably an accidental tap -- discard it. e.Cancel = true; return; } } if (headhits.Length > 0 && tailhits.Length == 0) { // If only the head is hit, it must be an 'attractive force'. RigidBodyBase body = headhits[0]; dbg.WriteLine("new ExternalForceMech"); ExternalForceMech newmech = new ExternalForceMech(); newmech.Body = BodyRef.For(body, head); newmech.vector = Vector.FromPoints(head, tail); newmech.strokeid = e.Stroke.Id; doc.Mechanisms.Add(newmech); // Repaint area around the newmech (unions with stroke bbox, below). Rectangle dirtybbox = newmech.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); return; } if (headhits.Length == 0 && tailhits.Length > 0) { // If only the tail is hit, it must be a 'propulsive force'. RigidBodyBase body = tailhits[0]; dbg.WriteLine("new PropulsiveForceMech"); PropulsiveForceMech newmech = new PropulsiveForceMech(); newmech.Body = BodyRef.For(body, tail); newmech.vector = Vector.FromPoints(head, tail); newmech.strokeid = e.Stroke.Id; doc.Mechanisms.Add(newmech); // Repaint area around the newmech (unions with stroke bbox, below). Rectangle dirtybbox = newmech.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); return; } if (true) // scope { // Create a binding mechanism between two bodies. RigidBodyBase headbody = headhits[0], tailbody = tailhits[0]; // If both the head and the tail hit same object, // attach the head to the one behind. if (Object.ReferenceEquals(headbody, tailbody)) { if (headhits.Length > 1) { headbody = headhits[1]; } else if (tailhits.Length > 1) { tailbody = tailhits[1]; } else { // Don't self-connect. We will perhaps interpret the stroke as an // anchor-gesture or a selection-gesture, // but if we cannot, we will cancel. int nc = e.Stroke.PolylineCusps.Length; if (np <= 25) { inkoverlay.Selection = MakeSingleStrokeCollection(headbody.strokeid); SetEditingMode(InkOverlayEditingMode.Select, false); } else if (np <= 150 && (nc >= 3 && nc <= 5)) { headbody.anchored = !headbody.anchored; //toggle } e.Cancel = true; // Repaint area around the headbody (unions with stroke bbox, below). Rectangle dirtybbox = headbody.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); return; } } // Create a rope, rod, or spring out of the stroke. MechanismBase newmech = MakeRodRopeOrSpring(e.Stroke, headbody, tailbody); if (newmech != null) { // Repaint area around the newmech (unions with stroke bbox, below). Rectangle dirtybbox = newmech.BoundingBox; InvalidateInkSpaceRectangle(dirtybbox); return; } else { // Throw out the stroke, and return. e.Cancel = true; return; } } } catch (Exception ex) { // Cancel the stroke, and log the error. e.Cancel = true; Global.HandleThreadException(this, new System.Threading.ThreadExceptionEventArgs(ex)); } finally { // Repaint the area around the stroke (unions with newbody/mech region, above). Rectangle dirtybbox = e.Stroke.GetBoundingBox(); InvalidateInkSpaceRectangle(dirtybbox); } }