The current version of Haul Infinity uses standard straight-line segments to display roads. In future, we’re looking to transition to rendering smooth roads. The benefits of this approach include: more accurate distance calculations, and the capability to calculate and visualize road curvature for use in applying speed limits.
The following two images show a classic switchback, rendered in Haul Infinity with straight lines and Bezier splines, respectively:
I retrieved the equations for the cubic Bezier spline from the Adobe PostScript language reference.
From page 565, the parametric form of the equation is:
Given p0 (segment start), p3 (segment end), p1 (start anchor) and p2 (end anchor), to calculate the coefficients for the parametric form:
public Bezier(Point p0, Point p1, Point p2, Point p3) { _cx = 3*(p1.X - p0.X); _bx = 3*(p2.X - p1.X) - _cx; _ax = p3.X - p0.X - _cx - _bx; _cy = 3*(p1.Y - p0.Y); _by = 3*(p2.Y - p1.Y) - _cy; _ay = p3.Y - p0.Y - _cy - _by; _x0 = p0.X; _y0 = p0.Y; }
Then for each subdivision of the segment (more subdivisions for more detail):
public Point Get(double t) { if(t < 0.0 || t > 1.0) throw new InvalidOperationException(); var t2 = t*t; var t3 = t2*t; return new Point( _ax * t3 + _bx * t2 + _cx * t + _x0, _ay * t3 + _by * t2 + _cy * t + _y0 ,_z0 + _zd*t); }
Choosing the anchor points is up to you — some design packages require the user to specify anchor points for each road segment. To keep our software easy to use, I chose to automatically generate anchor points to create predictably smooth roads (note this code ignores the z-axis, since we need to keep user-specified grades):
public static Point CalculateHandle(Point pAttached, Point pShared, Point pOther) { double axd = (pAttached.X - pShared.X); double ayd = (pAttached.Y - pShared.Y); double bxd = (pOther.X - pShared.X); double byd = (pOther.Y - pShared.Y); double at = Math.Atan2(ayd, axd); double at2 = Math.Atan2(byd, bxd); double angle = at2 - at; double tangentAngle = at + (angle+ Math.PI) / 2; double tt = Math.Tan(tangentAngle); double weight2 = 0.15*(bxd*bxd+byd*byd); double x = Math.Sqrt(weight2/(Math.Pow(tt, 2) + 1)); double y = x*tt; if (Math.Abs(Math.Atan2(y, x) - at2) > Math.PI/2) { x = -x; y = -y; } return new Point(pShared.X + x, pShared.Y + y, 0); }
The geometric basis for the above code is (badly) diagrammed here:
In the given code, lambda is set to Sqrt(0.15) .. but this could be adjusted to achieve desired ‘curviness’.
Leave a comment