N
not_a_commie
I have here a function at the crux of my path search heuristic. The
function takes two points, an angle at each point, and a radius and
returns the shortest arc-line-arc triplet connecting those two points.
The function creates four connections between adjacent circles at each
end and returns the shortest of those connections. I need it to run
500k times a second. Currently it runs at a 10th that speed. I'm
grateful for any speedup ideas that you can offer! I think I'm just
allocating too many objects, but I'm not sure what to do about it. A
few notes: SegmentList is the same as List with a function for getting
the summed length of all its members and a length comparison function
with an early exit. Vector and Point are the same as Vector and Point
shipping with .NET 3.0. They have a few extension functions for
getting the current angle (an atan2 call) and creating a vector from
an angle (a sin and cos call). A negative radius is a clockwise arc.
CC is short for counter-clockwise. My arc and line classes have
internal constructors that allow you to set the cached entrance and
exit angles and the length directly rather than having to calculate
those on their first call. NaN means the cache is invalid.
public static double ShortestArcLineArc(Point start, double
startAngle, Point end, double endAngle, double minRadius, out
SegmentList<ISegment> shortestPath)
{
SegmentList<ISegment> tempPath = new SegmentList<ISegment>();
SegmentList<ISegment> shortPath = new SegmentList<ISegment>();
Point startCC = start + Vector.FromLengthAngle(minRadius, startAngle
+ Constants.PI_OVER_TWO);
Point endCC = end + Vector.FromLengthAngle(minRadius, endAngle +
Constants.PI_OVER_TWO);
Point endC = end + Vector.FromLengthAngle(minRadius, endAngle -
Constants.PI_OVER_TWO);
Point startC = start + Vector.FromLengthAngle(minRadius, startAngle -
Constants.PI_OVER_TWO);
Point lineStart, lineEnd;
Vector startCCtoEndCC = endCC - startCC;
Vector startCCtoEndC = endC - startCC;
Vector startCtoEndC = endC - startC;
Vector startCtoEndCC = endCC - startC;
// toss any diagonal on an overlapping circle:
double radius2 = minRadius * minRadius * 4, lastAngle; // diameter
squared
// startCCtoEndCC is our line. it just needs to be connected to the
right start point
lastAngle = startCCtoEndCC.Angle;
lineStart = startCC + Vector.FromLengthAngle(minRadius, lastAngle -
Constants.PI_OVER_TWO);
lineEnd = lineStart + startCCtoEndCC;
if (!start.Equals(lineStart))
shortPath.Add(new ArcSegment(start, lineStart, startCC, minRadius,
startAngle, lastAngle, double.NaN));
shortPath.Add(new LineSegment(lineStart, lineEnd, lastAngle,
lineStart.DistanceTo(lineEnd)));
if (!lineEnd.Equals(end))
shortPath.Add(new ArcSegment(lineEnd, end, endCC, minRadius,
lastAngle, endAngle, double.NaN));
// startCtoEndC is our line. it just needs to be connected to the
right start point
lastAngle = startCtoEndC.Angle;
lineStart = startC + Vector.FromLengthAngle(minRadius, lastAngle +
Constants.PI_OVER_TWO);
lineEnd = lineStart + startCtoEndC;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startC, -minRadius,
startAngle, lastAngle, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd, lastAngle,
lineStart.DistanceTo(lineEnd)));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endC, -minRadius,
lastAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 || tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
// in these next two our angle is not exactly Constants.PI_OVER_TWO;
that leads to an unfortunate arccos call and a sqrt on the length
if (startCCtoEndC.LengthSquared >= radius2)
{
double diff = Math.Acos(minRadius / (startCCtoEndC.Length * 0.5));
Vector v = Vector.FromLengthAngle(minRadius, startCCtoEndC.Angle -
diff);
lineStart = startCC + v;
lineEnd = endC - v;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startCC, minRadius,
startAngle, double.NaN, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endC, -minRadius,
tempPath[0].EndAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 ||
tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
}
if (startCtoEndCC.LengthSquared >= radius2)
{
double diff = Math.Acos(minRadius / (startCtoEndCC.Length * 0.5));
Vector v = Vector.FromLengthAngle(minRadius, startCtoEndCC.Angle +
diff);
lineStart = startC + v;
lineEnd = endCC - v;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startC, -minRadius,
startAngle, double.NaN, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endCC, minRadius,
tempPath[0].EndAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 ||
tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
}
shortestPath = shortPath;
return shortestPath.Length;
}
function takes two points, an angle at each point, and a radius and
returns the shortest arc-line-arc triplet connecting those two points.
The function creates four connections between adjacent circles at each
end and returns the shortest of those connections. I need it to run
500k times a second. Currently it runs at a 10th that speed. I'm
grateful for any speedup ideas that you can offer! I think I'm just
allocating too many objects, but I'm not sure what to do about it. A
few notes: SegmentList is the same as List with a function for getting
the summed length of all its members and a length comparison function
with an early exit. Vector and Point are the same as Vector and Point
shipping with .NET 3.0. They have a few extension functions for
getting the current angle (an atan2 call) and creating a vector from
an angle (a sin and cos call). A negative radius is a clockwise arc.
CC is short for counter-clockwise. My arc and line classes have
internal constructors that allow you to set the cached entrance and
exit angles and the length directly rather than having to calculate
those on their first call. NaN means the cache is invalid.
public static double ShortestArcLineArc(Point start, double
startAngle, Point end, double endAngle, double minRadius, out
SegmentList<ISegment> shortestPath)
{
SegmentList<ISegment> tempPath = new SegmentList<ISegment>();
SegmentList<ISegment> shortPath = new SegmentList<ISegment>();
Point startCC = start + Vector.FromLengthAngle(minRadius, startAngle
+ Constants.PI_OVER_TWO);
Point endCC = end + Vector.FromLengthAngle(minRadius, endAngle +
Constants.PI_OVER_TWO);
Point endC = end + Vector.FromLengthAngle(minRadius, endAngle -
Constants.PI_OVER_TWO);
Point startC = start + Vector.FromLengthAngle(minRadius, startAngle -
Constants.PI_OVER_TWO);
Point lineStart, lineEnd;
Vector startCCtoEndCC = endCC - startCC;
Vector startCCtoEndC = endC - startCC;
Vector startCtoEndC = endC - startC;
Vector startCtoEndCC = endCC - startC;
// toss any diagonal on an overlapping circle:
double radius2 = minRadius * minRadius * 4, lastAngle; // diameter
squared
// startCCtoEndCC is our line. it just needs to be connected to the
right start point
lastAngle = startCCtoEndCC.Angle;
lineStart = startCC + Vector.FromLengthAngle(minRadius, lastAngle -
Constants.PI_OVER_TWO);
lineEnd = lineStart + startCCtoEndCC;
if (!start.Equals(lineStart))
shortPath.Add(new ArcSegment(start, lineStart, startCC, minRadius,
startAngle, lastAngle, double.NaN));
shortPath.Add(new LineSegment(lineStart, lineEnd, lastAngle,
lineStart.DistanceTo(lineEnd)));
if (!lineEnd.Equals(end))
shortPath.Add(new ArcSegment(lineEnd, end, endCC, minRadius,
lastAngle, endAngle, double.NaN));
// startCtoEndC is our line. it just needs to be connected to the
right start point
lastAngle = startCtoEndC.Angle;
lineStart = startC + Vector.FromLengthAngle(minRadius, lastAngle +
Constants.PI_OVER_TWO);
lineEnd = lineStart + startCtoEndC;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startC, -minRadius,
startAngle, lastAngle, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd, lastAngle,
lineStart.DistanceTo(lineEnd)));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endC, -minRadius,
lastAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 || tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
// in these next two our angle is not exactly Constants.PI_OVER_TWO;
that leads to an unfortunate arccos call and a sqrt on the length
if (startCCtoEndC.LengthSquared >= radius2)
{
double diff = Math.Acos(minRadius / (startCCtoEndC.Length * 0.5));
Vector v = Vector.FromLengthAngle(minRadius, startCCtoEndC.Angle -
diff);
lineStart = startCC + v;
lineEnd = endC - v;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startCC, minRadius,
startAngle, double.NaN, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endC, -minRadius,
tempPath[0].EndAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 ||
tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
}
if (startCtoEndCC.LengthSquared >= radius2)
{
double diff = Math.Acos(minRadius / (startCtoEndCC.Length * 0.5));
Vector v = Vector.FromLengthAngle(minRadius, startCtoEndCC.Angle +
diff);
lineStart = startC + v;
lineEnd = endCC - v;
if (!start.Equals(lineStart))
tempPath.Add(new ArcSegment(start, lineStart, startC, -minRadius,
startAngle, double.NaN, double.NaN));
tempPath.Add(new LineSegment(lineStart, lineEnd));
if (!lineEnd.Equals(end))
tempPath.Add(new ArcSegment(lineEnd, end, endCC, minRadius,
tempPath[0].EndAngle, endAngle, double.NaN));
if (shortPath.Count <= 0 ||
tempPath.IsShorterThan(shortPath.Length))
{
shortPath.Clear();
shortPath.AddRange(tempPath);
}
tempPath.Clear();
}
shortestPath = shortPath;
return shortestPath.Length;
}