### OSLArchemedes Spiral

#### Introduction

For a point to be located on an Archemedian spiral it's distance from the center of the spiral (R) must be the same as some percentage (coeff) of it's rotational angle (theta) around the center of the spiral ie.

`R = coeff * theta`

Naively, it might be considered that all a shader has to do is,
- convert the s,t location of the point being shaded to polar coordinates,
- set a value for coeff,
If the angle of rotation (theta) multiplied by the coeff matches it's radial distance (R) to within a small margin of error then colorize the micro-polygon, say black, otherwise set it to a background color.

Figure 1 shows that this naive approach, listing 1, does not work. The spiral terminates after it's first "turn" - the black line should continue along the full spiral shown in blue. Figure 1 - woops

Listing 1 - woops.osl

 ```shader woops(color bakcolor = 1, color pencolor = 0, float coeff = 0.03, float tolerance = 0.01, float s = 0 [[int lockgeom = 0]], float t = 0 [[int lockgeom = 0]], output color resultRGB = 0) { float R, theta; // Convert the s,t location of the shading point // to polar coordinates. float xdiff = s - 0.5, ydiff = t - 0.5; R = sqrt(xdiff * xdiff + ydiff * ydiff); theta = atan2(ydiff, xdiff); // -PI to PI radians if(theta < 0.0) theta += 2 * M_PI; // 0 to 2PI radians // Check if we are close enough... if( abs(R - coeff * theta) < tolerance) resultRGB = pencolor; else resultRGB = bakcolor; }```

Clearly what the shader fails to take into account is the number of turns or cycles of the spiral. In other words the rotational angle calculated by the conversion to polar coordinates only goes from 0 to 2PI (0 to 360 degrees) when the angle should go from zero to 2PI * turns, say, 0 to 1080 degrees!

Listing 2 provides the code for a shader that will draw a full spiral. However, becuase of the simple antialiasing that it uses, the "blur" parameter may need to be adjusted in order to avoid the rendering artifacts shown on the right of figure 2. Figure 2

Listing 2 - a_spiral.osl

 ```shader a_spiral(color bakcolor = 1, color pencolor = 0, float center_s = 0.5, float center_t = 0.5, float coeff = 0.03 [[string help = "Smaller values create more turns"]], float blur = 0.03 [[string help = "Adjust to avoid artifacts"]], float s = 0 [[int lockgeom = 0]], float t = 0 [[int lockgeom = 0]], output color resultRGB = 0) { resultRGB = 1; float R, theta; // Convert the s,t location of the shading point to polar coordinates. float xdiff = s - center_s, ydiff = t - center_t; R = sqrt(xdiff * xdiff + ydiff * ydiff); theta = atan2(ydiff, xdiff); // -PI to PI radians if(theta < 0.0) theta += M_2PI; // 0 to 2PI radians // Remap theta from 0 to 2PI radians to the range 0.0 to 1.0 float percent_theta = theta/M_2PI; // The unitRadius defines the maximum size of the spiral when // theta is 2PI radians ie. 360 degrees. Unless it's size is // tweaked by the blur factor "cycles" increments before theta // reaches 2PI and the spiral will have breaks ie. artifacts. float unitRadius = coeff * M_2PI + mix(-blur,blur,percent_theta); // If the radial distance of shading point is, say, 1.5 the unit // radius is somewhere within the 2nd "turn" of the spiral. float turns = floor(R/unitRadius); theta = theta + turns * M_2PI; // Use "turns" to calculate the full rotational angle. theta = coeff * theta; // Determine how close the shading point is to the "true" spiral float blend = smoothstep(0, blur, abs(theta - R)); resultRGB = mix(pencolor, bakcolor, blend); // Uncomment the next couple of lines if you wish to view the // unitRadius. Because it is calculated with the addition of a // value based on blur and theta, the unitRadius defines a circle // with a gap, the size of which is twice the width of blur. /* if(abs(R - unitRadius) < 0.002) resultRGB = color(1,0,0); */ } ```