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);
}
}
}