using System; using System.Collections.Generic; using System.Windows; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SketchAssistantWPF { class TrajectoryGenerator { static readonly int CONSTANT_A = 10; static readonly double DEADZONE = 3; /// /// the template for the line currently being drawn /// InternalLine currentLine; /// /// the points of the current line template, as an ordered list /// List currentPoints; //Point lastCursorPosition; /// /// pointer to the active section of the line template, indexing the ending point of the active section /// int index; /// /// updates the pointer to the template for the line currently being drawn, and resets indices etc. /// /// the new template for the line currently being drawn public void setCurrentLine(InternalLine newCurrentLine) { currentLine = newCurrentLine; currentPoints = currentLine.GetPoints(); //lastCursorPosition = currentPoints.ElementAt(0); index = 1; } /// /// generates the new trajectory back to the template based on the current cursor position /// /// the current cursor position /// the direction in which to go, as an angle on the drawing plane, in degree format, with 0° being the positive X-Axis, increasing counterclockwise public double GenerateTrajectory(Point cursorPosition) { //update index to point to current section if one or more section divideing lines have been passed since last call while (index < (currentPoints.Count - 1) && SectionDividingLinePassed(cursorPosition, currentPoints.ElementAt(index - 1), currentPoints.ElementAt(index), currentPoints.ElementAt(index + 1))) { index++; } //lastCursorPosition = cursorPosition; //project teh point onto the active line segment to be able to compute distances Point orthogonalProjection = ComputeOrthogonalProjection(cursorPosition, currentPoints.ElementAt(index - 1), currentPoints.ElementAt(index)); //index of the last reachable actual point int targetIndex = index; List> strikeZones = new List>(); //if "far" away from the next actual point of the line, generate an auxiliary point at a constant distance (constantA) on the current line segment Point auxiliaryPoint = new Point(-1, -1); if (ComputeDistance(orthogonalProjection, currentPoints.ElementAt(index)) <= CONSTANT_A) { auxiliaryPoint = MoveAlongLine(orthogonalProjection, currentPoints.ElementAt(index - 1), currentPoints.ElementAt(index), CONSTANT_A); strikeZones.Add(computeStrikeZone(auxiliaryPoint, orthogonalProjection, cursorPosition)); targetIndex--; } //aim for the furthest actual point of the line reachable by the descent rate constraints (lower bounds) given by the various strike zones while (targetIndex < (currentPoints.Count - 1) && allStrikeZonesPassed(strikeZones, cursorPosition, currentPoints.ElementAt(targetIndex + 1))) { strikeZones.Add(computeStrikeZone(currentPoints.ElementAt(targetIndex + 1), orthogonalProjection, cursorPosition)); targetIndex++; } Point furthestCrossingPoint = new Point(-1, -1); ; if (targetIndex < index) //auxiliary point created and next actual point not reachable { furthestCrossingPoint = ComputeFurthestCrossingPoint(cursorPosition, orthogonalProjection, strikeZones, auxiliaryPoint, currentPoints.ElementAt(targetIndex + 1)); //if such a point exists, use it as target for the new trajectory if (furthestCrossingPoint.X != -1) { Debug_DrawStrikeZones(strikeZones); Debug_DrawTrajectoryVector(cursorPosition, furthestCrossingPoint); return computeOrientationOfVector(cursorPosition, furthestCrossingPoint); } //else use the last reachable actual point else { Debug_DrawStrikeZones(strikeZones); Debug_DrawTrajectoryVector(cursorPosition, auxiliaryPoint); return computeOrientationOfVector(cursorPosition, auxiliaryPoint); } } else { //aim for the furthest (auxiliary) point on the line segment after the last reachable actual point (only if there is such a segment: not if that last reachable point is the last point of the line) if (targetIndex < (currentPoints.Count - 1)) { furthestCrossingPoint = ComputeFurthestCrossingPoint(cursorPosition, orthogonalProjection, strikeZones, currentPoints.ElementAt(targetIndex), currentPoints.ElementAt(targetIndex + 1)); } //if such a point exists, use it as target for the new trajectory if (furthestCrossingPoint.X != -1) { Debug_DrawStrikeZones(strikeZones); Debug_DrawTrajectoryVector(cursorPosition, furthestCrossingPoint); return computeOrientationOfVector(cursorPosition, furthestCrossingPoint); } //else use the last reachable actual point else { Debug_DrawStrikeZones(strikeZones); Debug_DrawTrajectoryVector(cursorPosition, currentPoints.ElementAt(targetIndex)); return computeOrientationOfVector(cursorPosition, currentPoints.ElementAt(targetIndex)); } } } /// /// prints the trajectory vector on the drawing pane for debugging and calibration purposes /// /// origin point of the trajectory vector /// target point of the trajectory vector private void Debug_DrawTrajectoryVector(Point vectorStartPoint, Point vectorEndPoint) { throw new NotImplementedException(); } /// /// prints all strike zones on the drawing pane for debugging and calibration purposes /// /// list of all strike zones to be drawn private void Debug_DrawStrikeZones(List> strikeZones) { throw new NotImplementedException(); } /// /// computes the orientation of the given vector on the drawing plane /// /// origin point of the direction vector /// target point of the direction vector /// the orientation angle, in degree format private double computeOrientationOfVector(Point vectorStartPoint, Point vectorEndPoint) { double x = vectorEndPoint.X - vectorStartPoint.X; double y = vectorEndPoint.Y - vectorStartPoint.Y; return Math.Atan( y / x ); } /// /// computes the furthest point on the given line segment that will still pass all previous strike zones when connecting it with the current cursor position in a straight line /// /// the current cursor position /// the orthogonal projection of the current cursor position onto the active line segment /// list of all strike zones which have to be passed /// starting point of the line segment on which the point has to be found /// ending point of the line segment on which the point has to be found /// the furthest such point or null, if there is no such point on the given segment (start and end point excluded) private Point ComputeFurthestCrossingPoint(Point cursorPosition, Point orthogonalProjection, List> strikeZones, Point lineSegmentStartPoint, Point lineSegmentEndPoint) { Tuple bsf= new Tuple(new Point(-1, -1), new Point(-1, -1)); Double bsfAngle = 180; for (int j = 0; j < strikeZones.Count; j++) { double currentAngle = ComputeAngle(orthogonalProjection, cursorPosition, strikeZones.ElementAt(j).Item2); if (currentAngle < bsfAngle) { bsfAngle = currentAngle; bsf = strikeZones.ElementAt(j); } } Point furthestCrossingPoint = ComputeCrossingPoint(cursorPosition, bsf.Item2, lineSegmentStartPoint, lineSegmentEndPoint); return furthestCrossingPoint; } private double ComputeAngle(Point point1, Point centerPoint, Point point2) { return (Math.Abs(computeOrientationOfVector(centerPoint, point1) - computeOrientationOfVector(centerPoint, point2)) % 180); } private Point ComputeCrossingPoint(Point line1Point1, Point lne1Point2, Point line2Point1, Point line2Point2) { throw new NotImplementedException(); } /// /// checks if all strike zones are passed by the trajectory given by the straight line from the cursor position to the next target point /// /// list of all already computed strike zones /// the current cursor position /// index of the next target point /// true if all strike zones are passed, else false private bool allStrikeZonesPassed(List> strikeZones, Point cursorPosition, Point targetPoint) { foreach (Tuple s in strikeZones ) { if (!SectionsCrossing(cursorPosition, targetPoint, s.Item1, s.Item2)) return false; } return true; } private bool SectionsCrossing(Point section1StartingPoint, Point section1EndingPoint, Point section2StartingPoint, Point section2EndingPoint) { Point crossingPoint = ComputeCrossingPoint(section1StartingPoint, section1EndingPoint, section2StartingPoint, section2EndingPoint); if (BeyondSection(crossingPoint, section1StartingPoint, section1EndingPoint)) return false; if (BeyondSection(crossingPoint, section1EndingPoint, section1StartingPoint)) return false; if (BeyondSection(crossingPoint, section2StartingPoint, section2EndingPoint)) return false; if (BeyondSection(crossingPoint, section2EndingPoint, section2StartingPoint)) return false; return true; } /// /// computes the strike zone for a point using the cursor position, its orthogonal projection onto the active line segment and tunable constants /// /// the point to compute the strike zone of /// orthogonal projection of the cursor position onto the active line segment /// the current cursor position /// private Tuple computeStrikeZone(Point targetedPoint, Point orthogonalProjection, Point cursorPosition) { if (ComputeDistance(cursorPosition, orthogonalProjection) < DEADZONE) return new Tuple(targetedPoint, targetedPoint); throw new NotImplementedException(); } /// /// moves a point a given distance along a vector defined by two points /// /// the point to be moved along the line /// origin point of the direction vector /// target point of the direction vector /// distance by which to move the point /// a new point that is located distance away from pointToBeMoved in the direction of the given vector private Point MoveAlongLine(Point pointToBeMoved, Point lineStartPoint, Point lineEndPoint, double distance) { double xOffset = lineEndPoint.X - lineStartPoint.X; double yOffset = lineEndPoint.Y - lineStartPoint.Y; double vectorLength = ComputeDistance(lineStartPoint, lineEndPoint); xOffset /= vectorLength; xOffset *= distance; yOffset /= vectorLength; yOffset *= distance; return new Point(pointToBeMoved.X + xOffset, pointToBeMoved.Y + yOffset); } /// /// computes the euclidean distance between two points /// /// point 1 /// point 2 /// euclidean distance between point1 and point2 private double ComputeDistance(Point point1, Point point2) { return Math.Sqrt( (double) ( Math.Pow((point2.X - point1.X), 2) + Math.Pow((point2.Y - point1.Y), 2) ) ); } /// /// computes the orthogonal projection of a point onto the given line segment /// /// the current cursor position /// beginning point of the current section /// ending point of current section /// the orthogonal projection of a point onto the given line segment, or the respective segment end point if the orthogonal projection lies outside the specified segment private Point ComputeOrthogonalProjection(Point cursorPosition, Point lastPoint, Point currentPoint) { double r = ComputeDistance(cursorPosition, currentPoint); Point auxiliaryPoint = IntersectCircle(cursorPosition, r, lastPoint, currentPoint); if (BeyondSection(auxiliaryPoint, lastPoint, currentPoint)) return currentPoint; Point orthogonalProjection = MoveAlongLine(auxiliaryPoint, auxiliaryPoint, lastPoint, ComputeDistance(auxiliaryPoint, lastPoint) / 2); if (BeyondSection(orthogonalProjection, currentPoint, lastPoint)) return lastPoint; return orthogonalProjection; } private Point IntersectCircle(Point cursorPosition, double r, Point lastPoint, Point currentPoint) { throw new NotImplementedException(); } /// /// checks if a Point lies on a given line section or beyond it, works only if pointToTest lies on the line defined by sectionStartingPoint and sectionEndingPoint /// /// the Point to test /// the starting point of the section /// the ending point of the section /// true iff pointToTest lies beyond sectionEndingPoint, on the line defined by sectionStartingPoint and sectionEndingPoint private bool BeyondSection(Point pointToTest, Point sectionStartingPoint, Point sectionEndingPoint) { if(sectionEndingPoint.X > sectionStartingPoint.X) { if (pointToTest.X > sectionEndingPoint.X) return true; } else if(sectionEndingPoint.X < sectionStartingPoint.X) { if (pointToTest.X < sectionEndingPoint.X) return true; } else { if (sectionEndingPoint.Y > sectionStartingPoint.Y) { if (pointToTest.Y > sectionEndingPoint.Y) return true; } else if (sectionEndingPoint.Y < sectionStartingPoint.Y) { if (pointToTest.Y < sectionEndingPoint.Y) return true; } } return false; } /// /// checks if the angle dividing line between this section and the next one has been passed /// /// the current cursor position /// beginning point of the current section /// ending point of current and beginning point of next section /// ending point of next section /// true iff cursorPosition is closer to the next section than to the current one private bool SectionDividingLinePassed(Point cursorPosition, Point lastPoint, Point currentPoint, Point nextPoint) { //compute a point at the same distance to the dividing line as nextPoint, but on the other side of the line Point auxiliaryPoint = MoveAlongLine(currentPoint, currentPoint, lastPoint, ComputeDistance(currentPoint, nextPoint)); //line passed iff cursorPosition closer to nextPoint than to auxiliaryPoint return ComputeDistance(cursorPosition, nextPoint) <= ComputeDistance(cursorPosition, auxiliaryPoint); } } }