I'm working on some graphics code that does live drawing of smooth, continuous curves. I want to add support for feathered brushes. For that, I need to be able to calculate "normals", or lines perpendicular to, the line segments that make up my curve.
The pure math way to do that is to use arctan to find the angle of a segment, rotate 90 degrees, and use sine and cosine to find the x and y offsets of my normals. Once I have my angles it would be pretty easy to use a lookup table to replace sine and cosine, but writing a performant, low precision version of atan2()
seems tricky.
The lengths and angles of my normals (perpendicular lines) doesn't need to be exact. If it's off by a tenth it won't matter.
Have any of you developed a high speed, crude version of atan2() for this sort of graphics work? Optimizing this sort of thing for performance is fussy and time-consuming.
I'm working in Swift 3, but am "multi-lingual". I'm fine with integrating code written in C/Objective-C as well. (or perhaps converting it to Swift.)
EDIT:
More details:
The project involves taking freehand drawing, which provides a series of points that are sometimes fairly far apart if the user drags their finger quickly, and using Catmull-Rom splines to add intermediate points to create a series of line segments small enough that they look like a smooth curve.
(Catmull-Rom splines are a curve that is similar to the better-known Bezier curve, but all the control points for the curve lie on the curve, so it's straightforward to smooth a curve the user enters "freehand" using the input vertexes to do the smoothing.)
(From now on I'll refer to "curves", but what I really mean is polylines composed of line segments so short that they appear to be smooth curves)
I've factored my code so that I generate splines for only the parts of the curve that have changed. It's plenty fast now, and creates beautifully smooth curves as fast as you can draw. Subjectively, it seems that I'm drawing a curve that follows your finger track point by point, with no jumping between points.
The next goal is to be able to draw with a soft-edged brush. To do that, I want to find curves on the left and right of the user's finger-track that are parallel to the curve. I will then use OpenGL to create triangle strips that define the thick curve between the left and right lines, and use polygon shading to feather the curve from opaque along the user's finger-track curve to transparent along the left and right parallel curves.
Say I want to draw a soft-edged curve that's 6 points thick. The curves that follow the user's finger-track on the left and on the right would each need to extend 3 points from the user's curve.
I'm planning to find the vertexes of the end-points for the left and right curves by finding line segments perpendicular to the line segments of the user's finger-track that pass through the vertexes of the finger-track, and extend by 1/2 of the desired line thickness to the left and right of the user's finger-track.
Below is a screen-shot of a curve drawn by the current version of the program, with the input vertexes drawn as blue diamonds, and the smoothing points I add drawn as hollow squares. (I've decreased the number of added smoothing points so you can better see what's going on.)
Imagine drawing a series of "hash marks" through each of the vertex points, each 6 points long, centered on one of those vertex points, and perpendicular to the line first line segment that of the smoothed curve that ends at that vertex.
As Peter O. pointed out in his answer, finding a line segment perpendicular to any particular line segment is easy - you just invert the slope. However, I want line segments of a specific length. (In my example, 6 points, 3 points on either side of a vertex.) I'm looking for way to do that fast. I could figure out the end-points of my normals using either trig or square roots, both of which are really slow.
For your problem, if all you want is to find a line that's perpendicular to another, you don't even need to use
atan2
; you can use the following approach instead. Let (x1, y1) and (x2, y2) be the input line segment.Alternatively, if you insist on
atan2
, I am aware of one public domain implementation at http://www.dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization. Try both approaches and see which suits your purpose better.In general, if you find yourself calling
atan2(dy,dx)
just to get the proper angle forcos
andsin
, you can often just use the x and y components of the normalized(dx, dy)
vector to get the cosine and sine instead, respectively.