# Parametric Equations

 Parametric surfaces render very slowly in POV 3.5. Sometimes it is difficult, or even impossible, to specify a single equation in x, y and z that specifies the surface but a set of parametric equations might be available. In the 2d case, we know that x*x + y*y - r*r = 0 is the equation of a circle, but we can also describe it by the two parametric equations x = cos(theta), y = sin(theta). Each value of theta gives a value for x and y, i.e. a 2d point. As theta varies from 0 to 2pi the point goes round the unit circle. In the 3d case we need three parametric equations, one each for x, y and z; and we need two parameters which we will call u and v. If we consider the equations ``` x = sin(u) y = cos(u) z = v ``` we can see that each pair of values for u and v gives a single xyz point in 3d space. As u varies from 0 to 2pi, the point goes round a circle. As v varies from -2 to 2 the point moves parallel to the z axis. It turns out that these are the parametric equations for a cylinder. This is the cylinder generated by these parametric equations. The three parametric functions are listed; then the u,v bounds; Then the contained_by object. The u,v bounds used here specify that {u,v} varies from {0,-2} to {2*pi, 2} i.e. u varies from 0 to 2*pi and v varies from -2 to 2. The "contained_by" object can be a sphere or a box. The "isosurface", "evaluate", "max_trace", "threshold", "open" and "closed" keywords are not used. ``` parametric { function {sin(u)} function {cos(u)} function {v} <0,-2>,<2*pi,2> contained_by{box{<-2,-2,-2>,<2,2,2>}} pigment {rgb 0.9} finish {phong 0.5 phong_size 10} } ``` Here's a sort of conical spiral that would be very difficult to specify without using parametric equations. ``` parametric { function {u*v*sin(15*v)} function {v} function {u*v*cos(15*v)} <0,-1>,<1,1> max_gradient 4 contained_by{box{<-R,-R,-R>,}} ``` This is the same surface as last time, but in this case I've declared two of the equations beforehand. It is possible to perform variable substitution here. The "precompute" keyword can speed up the rendering by telling the renderer to store some calculations in an array, thus trading memory and parsing time against rendering time. I find that "precompute 18, x,y,z" tends to give a reasonable speed improvement on my machine. Higher values cause the parse time to become rather long. In this case, there's no speed gain from precomputing y, but there's not much of a penalty either. ``` #declare F1 = function {u*v*sin(15*v)} #declare F2 = function {u*v*cos(15*v)} parametric { function {F1(u,v,0)} function {v} function {F2(u,v,0)} <0,-1>,<1,1> contained_by{box{<-R,-R,-R>,}} precompute 18, x,y,z accuracy 0.003 pigment {rgb 0.9} finish {phong 0.5 phong_size 10} } ``` I get the distinct impression that things like pigment functions aren't really supported with parametric surfaces. The parser seems to be trying to prevent me from using "pigment" in the same function as "u" or "v", but the following syntax is accepted. ``` #declare Fx = function {u*v*sin(15*v)} #declare Fy = function {v} #declare Fz = function {u*v*cos(15*v)} #declare Fp = function {pigment {granite scale 0.1}} parametric { function {Fx(u,v,0)} function {Fy(u,v,0) + Fp(u,v,0).grey*0.2} function {Fz(u,v,0)} <0,-1>,<1,1> contained_by{box{<-R,-R,-R>,}} precompute 18, x,y,z pigment {rgb 0.9} finish {phong 0.5 phong_size 10} no_shadow } ``` This is the Astroidal Ellipse. The surface goes in where an ordinary ellipse goes out. ``` #declare A=1; #declare B=1; #declare C=1; #declare Fx = function {pow(A*cos(u)*cos(v),3)} #declare Fy = function {pow(B*sin(u)*cos(v),3)} #declare Fz = function {pow(C*sin(v),3)} parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} <0,0>,<2*pi,2*pi> contained_by{box{<-R,-R,-R>,}} precompute 18, x,y,z ``` This surface is called the Bohemian Dome. ``` #declare A=0.5; #declare B=1.5; #declare C=1.0; #declare Fx = function {A*cos(u)} #declare Fy = function {B*cos(v)+A*sin(u)} #declare Fz = function {C*sin(v)} parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} <0,0>,<2*pi,2*pi> contained_by{box{<-R,-R,-R>,}} precompute 18, x,y,z ``` This is part of Dini's Surface of constant negative curvature. ``` #declare A=1; #declare B=0.2; #declare Fx = function {A*cos(u)*sin(v)} #declare Fy = function {A*sin(u)*sin(v)} #declare Fz = function {A*(cos(v)+ln(tan(v/2))) + B*u} parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} <2*pi,0>,<4*pi,2.1*pi> contained_by{box{<-R,-R,-R>,}} precompute 18, x,y,z ``` This is a Moebius Strip. The range of "u" values controls how far round the circle we go (0 to 2*pi is a complete circle) and the range of "v" values controls the width of the strip. ``` #declare Fx = function {cos(u)+v*cos(u/2)*cos(u)} #declare Fy = function {sin(u)+v*cos(u/2)*sin(u)} #declare Fz = function {v*sin(u/2)} #declare U1 = 0; #declare U2 = 2*pi; #declare V1 = -0.3; #declare V2 = 0.3; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ``` The swallowtail is the second simplest catastrophe surface. ``` #declare Fx = function {u*v*v + 3*pow(v,4)} #declare Fy = function {-2*u*v - 4*pow(v,3)} #declare Fz = function {u} #declare U1 = -2; #declare U2 = 2; #declare V1 = -0.8; #declare V2 = 0.8; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ``` Several of these surfaces have been shamelessly nicked from http://www.uib.no/People/nfytn/mathgal.htm. You might like to take a look at the Pov 3.1 code on that site that was used to generate some of the images there. Now that we have parametric isosurfaces, we can specify a surface in a few lines that used to take yards of mathematics to specify with smooth triangles. It's quite easy to invent new parametric surfaces, and they're more likely to look interesting than new conventional isosurfaces are. If you use combinations of cos and sin functions you can ensure that your surface always remains within a certain distance of the origin, or that it only goes off to infinity in one dimension. For this surface that I just invented Fx, Fy and Fz are all trig functions that are never outside the range -1 to +1, so the complete surface must lie inside the unit cube. ``` #declare Fx = function {sin(u)*sin(v)} #declare Fy = function {cos(u)*sin(v)} #declare Fz = function {cos(u)*cos(v)} #declare U1 = -1*pi; #declare U2 = 1*pi; #declare V1 = -1*pi; #declare V2 = 1*pi; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ``` For this surface, I've allowed Fz to go off to go off to infinity, but I've clipped the surface by limiting v to the range -1.4 to +1.4. ``` #declare Fx = function {sin(u)} #declare Fy = function {cos(u+v)} #declare Fz = function {v} #declare U1 = -pi; #declare U2 = pi; #declare V1 = -1.4; #declare V2 = 1.4; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}}``` It's possible to arrange for the surface to go off to infinity in only one direction (in this case the negative y direction) by using the abs() function or by squaring the equation. ``` #declare Fx = function {sin(u)} #declare Fz = function {cos(u+v)} #declare Fy = function {-abs(v)/2} #declare U1 = -pi; #declare U2 = pi; #declare V1 = -40; #declare V2 = 40; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ``` The parametric form of the sphere is:```x = sin(u)*sin(v) y = cos(u)*sin(v) z = cos(v)``` To these equations I've added "+cos(20*v)*0.05" to the x and y equations. The "20*v" controls the frequency of the ripples, and the "*0.05" factor controls their amplitude. Numerous different ripple effects can be achieved by using "sin" instead of "cos", "20*u" instead of "20*v", and applying perturbations to different combinations of Fx, Fy Fz. ``` #declare Fx = function {sin(u)*sin(v) +cos(20*v)*0.05} #declare Fy = function {cos(u)*sin(v) +cos(20*u)*0.05} #declare Fz = function {cos(v)} #declare U1 = -1*pi; #declare U2 = 1*pi; #declare V1 = -1*pi; #declare V2 = 1*pi; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ``` Here's a similar trick performed with the parametric form of Helix2. The first term of Fx and Fy makes a circle of radius r2. The second term offsets that circle by a distance r1 in a direction that spirals round as u varies by 1/Turns. The third term creates the fluting effect. ``` #declare Turns=3; #declare r1 = 0.3; #declare r2 = 1.0; #declare Flute = 0.04; #declare Freq = 24; #declare Fx = function {r2*cos(v) + r1*sin(u*Turns) + Flute*sin(Freq*v)} #declare Fy = function {u} #declare Fz = function {r2*sin(v) + r1*cos(u*Turns) + Flute*sin(Freq*v)} #declare U1 = -1*pi; #declare U2 = 1*pi; #declare V1 = -1*pi; #declare V2 = 1*pi; parametric { function {Fx(u,v,0)} function {Fy(u,v,0)} function {Fz(u,v,0)} , contained_by{box{<-R,-R,-R>,}} ```