Striscia
v. 1.01
a free and open utility for POV-Ray™ v. 3.1

written by Daniele Varrazzo
(w) 1997-1999 by PiroSoftware c.n.f.

Homepage: Punto triplo

For any suggests, bug reports, new ideas you can write to piro@officine.it


    Index


An old fashion surface definition is

"the place of the points of a curve that is moved and deformed at the same time"

I want to give you exactly this: you will take a curve in your hands and will move it in the space. You could take a simple shape, such as a yellow hula-hop and start walking, moving it in the air.

Think about a camera watching the scene, with a long exposure time: in the photo you will see a yellow surface, something like a "pipeline". If you chose to walk in a straight line, you obtained an open cylinder. If you spinned around, now you have a "torus", a donut.

You will hold in your hands any shape you want. And walk on any path.

And more.


Before doing anything, you must create a new scene file, and add the line

#include "Striscia.inc"

"Striscia.inc" and all the other files should stay in a library path.

Now you have to learn to describe curves before going on. If you already used lathe and prism objects in POV language, then you already know almost everything.

You can describe curves with the same prism base rules: you can describe a curve made up with linear pieces, or a cubic curve described with interpolated points or with the Bezier polygon. You won't use the quadric system: i consider it hard to control and pretty useless.

I wrote some macros that return a curve as it were an object: you can, for example, assign it to a variable.

This is the simplest spline function: it returns a linear spline. You can call it with:

splLinearSpline(array[n] {v1, v2, ...., vn})

The elements from v1 to vn are 3D vectors. This is the main difference from the prism and lathe control curves, that must be defined on the z=0 plane.

This example shows a square contained in the horizontal plane (the XY plane) that abruptly "raises" along the z axis:

#declare splOddSquare = splLinearSpline(array[6] {
   <-1, 0, -1>, <1, 0, -1>, <1, 0, 1>, <0, 0, 1>, <-1, 1, 1>, <-1, 1, -1>
})
You don't have to use the semicolon ";" at the end of the row. The curve don't need to be closed and i won't close it for you.

Do you want to see the look of your curve? Well, i wrote a macro for this. Add to your scene the row:

PlotSpline(splOddSquare, <0.75, 2, -2.5>)

where the vector following the name of the spline to be plotted is the observer point of view. The result will be:

A not planar curve.

If you define a curve laying in the XY plane, you can plot it using the macro PlotXYSpline(splSpline). For example:

#declare splFlat = splLinearSpline(array[5] {
   <-1, -1, 0>, <-2, 2, 0>, <-1, 4, 0>, <3, 1, 0>, <1, -1, 0>
})
PlotXYSpline(splFlat)

A curve in the XY plane.

You can plot more curves on the same image: if you add to the previous scene the rows:

#declare splSecond = splLinearSpline(array[6] {
   <-1, 1, 0>, <-0.5, 2, 0>, <0.5, 2, 0>, <1, 1, 0>, <-1, -1, 0>, <1, -1, 0>
})
PlotXYSpline(splSecond)

Two curves in the same image.

Other spline functions are:

splCubicSpline(array[n] {v1, v2, ..., vn})

it's analogous to the curve generated when you use the cubic instruction inside a prism: the curve will start from v2 and end in vn-1, while the points v1 and vn are only used to shape the first and last curve segments. Differences with POV version are the same of the linear curves: it can even be open and don't have to lie entirely in a plane. For example:

#declare splCubicCurve = splCubicSpline(array[6] {
   < 3, -5, 0>, //control knot
   < 3,  5, 0>, <-5,  0, 0>, < 3, -5, 0>, < 3,  5, 0>, 
   <-5,  0, 0>  //control knot
})
PlotXYSpline(splCubicCurve)

Cubic curve in the plane.

splBezierSpline(array[n] {v1, v2, ..., vn})

generates a cubic curve from its Bezier polygon. Each four points it will generate a cubic segment, from the first to the fourth. The second and the third point are control points. Same analogies and differences with a prism defined with bezier option.

As you have seen up there, i used the "spl" prefix for all the functions returning a spline. This will be true for many other functions i will talk of, and i will follow similar rules with other peculiar objects.

Now you already know almost everything...


To specify a surface, you have to define two parameters. They both are spline curves.

The first parameter is the shape you want to hold in your hands. The shape can be any spline curve, both planar or not. But there is a privileged coordinates system.

If you define as shape a curve completely lying in the z=0 plane, walking along the path, it will always remain perpendicular to it. Think about a rubber-tube: you can bend it in any way, but if you cut it perpendicularly to the path you will always find a circular section.

You can define your shape anywhere in the space, but the alignment will always be made with the z=0 plane. If you define a not planar curve, the perpendicularity condition could not be respected anywhere along the path.

To create a curve representing a circle with radius 1 in the z=0 plane, you can use the function:

#declare MyShape = splCircle(4)

I will show later its usage, you should only know it generates the curve:

A radius 1 circle.

Now you have to declare that "this is the surface shape": do it with the macro:

SetShapeSpline(MyShape)

The other half of the work is to specify the path. To describe it yu have to define a spline, then call the macro SetPathSpline(). The path can be any curve. The shape defined with SetShapeSpline() will follow it faithfully, keeping itself always perpendicular to it. Do you want to walk in a straight line? Do it with the instructions

#declare MyPath = splLinearSpline(array[2] {<0, 2, 0>, <8, 2, 0>})
SetPathSpline(MyPath)
Now you have to generate the surface: you can do with the these instructions:

object {
   Striscia(UNION)
   texture { pigment { color rgb <1, 1, 0> } }
}
...sketch a background...

camera { location <-2, 6, -4> look_at <3, 1, 0> }
light_source { <-50, 50, -0> color rgb 1}
plane { y, 0 pigment { checker color rgb 1 color blue 1 } }
...run the scene and...

Ok, ok, it's just a cylinder, but...

Do you want to take a zigzag walk? Do it with

#declare MyPath = splCubicSpline(array[8] {
        <-1, 0, 0> <0, 0, 0>, <1, 0, 1>, <2, 0, -1>, 
        <3, 0, 1>, <4, 0, -1>, <5, 0, 0>, <6, 0, 0>})
SetPathSpline(MyPath)

...that's something new.

Striscia (pl: strisce) literally means "strip". The argument in the parenthesis of the Striscia() macro is the kind of the object you want to create. It can assume one of these values:

If you define a path with a corner, the surface will have a corner too. I can't work like frame-makers do, cutting wooden board with 45° angles to obtain my corners: this solution doesn't fit with all the shapes. So i use another technique: i skew the sections while they go close to the corner, so they will reach to a common plane from both sides.

All the work is done internally. You only have to specify, if you have to change the default value, how long the correction area must be. The default value is gCorrArea=2: the deformation will take effect in an area that extends for 2 units after and before the corner (walking on the path). In this area, there isn't perpendicularity between sections and path. The default value fits for roughly unit-size sections: if the section would be bigger, you could see bumps and odd phenomena in this area (surface collapsing onto itself). In this case, just raise the value for the gCorrArea variable.

You can even deal with 180° angles: the surface won't be distorted (it would requira an infinite distortion) but it will "bounce" on an invisible plane. There will be an hole in the corner, but later i will show you how to close it.

Here are two pictures showing how corners distorsion routine works. It's a detail of a striscia with circular shape with radius=1/5 and square path with side=1. The striscia is halved to show its inside. Squares are side=1/10.

gCorrArea=1/3
the correction area is big enough compared to the section.
gCorrArea=1/25
the correction area is too little: the curve "enters" the corner, then folds onto itself to align.

Warning! The corner may not work properly if the section is not planar: there could be holes on the surface. Does anybody here have any idea?


Here are the rules for the surface creation: there is a little man in your PC (a kind of gnome, sure!), and, for each path point:

  1. he takes the shape putting the origin of its coordinates system on the path point. Now he need to orient it;
  2. orientates the z axis of the shape coordinates system along the path direction. Now the shape is still free to rotate on itself along the z axis;
  3. rotates the shape pushing the y axis of the shape coordinates system toward the sky direction.
Notice that there could be troubles in the third point: if there is a point on the path where the direction is parallel to the sky direction, your gnome will not be able to choose how to rotate it. It's the same trouble of defining a "camera" looking straight to the "sky" direction. So you have to specify a new sky by setting the gvSky varaible with a new vector, such as

#declare gvSky = z;

Make your gnomes happy! Give them a new sky!


It's important to be free in the control curves description. Using Bezier cubics you have all the power you need, but in simple tasks they can be too much complex. I wrote some easier macro.

You already met one of them: the splCircle(n) macro.

splCircle(n)

generates a circle on the z=0 plane with radius 1. the "n" parameters indicates how many segments will form the circle: it's impossible to describe a whole circle using only a cubic segment. In order to archieve good results, n must be at least 4. With values greater than 4, the result won't be better, it will be useful for other goals. Later we will see how to morph a curve into another one. But both curves must have th same number of segments: you can morph a square in a circle with 4 segments and an exagon in a circle with 6 segments.

splArc(V)

generates an arc on the circle in the z=0 plane and with radius 1. V is an array where each element indicates, in degrees, the breakpoint position in the arc. So, a n elements array will make a n-1 segments arc. The 0° angle is on the x axis and your left hand will show you the angles positive direction in the usual way: point the thumb toward +z and the finger will curl in the positive verse of rotation. Each segment should not extend for more than 90°, or it will dent. Some examples:

  • splArc(array[5] {0, 90, 180, 270, 360}) is the same circle of splCircle(4);
  • splArc(array[4] {0, 30, 60, 90}) splits the 90° angle from <1,0,0> to <0,1,0> in 3 pieces;
  • splArc(array[9] {0, 90, 180, 270, 360, 450, 540, 630, 720}) strange: it turns two times. What can be used for? You can use it together with...

splSumSplines(spl1, spl2)

generates a spline by adding up two other splines:

#declare splFirst = splArc(array[9] {
   0, 90, 180, 270, 360, 450, 540, 630, 720
})
#declare splSecond = splLinearSpline(array[9] {
   <0, 0, 0>,   <0, 0, 1/3>, <0, 0, 2/3>, 
   <0, 0, 3/3>, <0, 0, 4/3>, <0, 0, 5/3>, 
   <0, 0, 6/3>, <0, 0, 7/3>, <0, 0, 8/3>
})
#declare splSum = splSumSplines(splFirst, splSecond)
PlotSpline(splSum, <3, 1, 3>)

A spiral

splPolygon(n)

generates a regular polygon with n sides, lying in the z=0 plane and inscribed in the unit circle. The first corner is always in the <1,0,0> point. For example:

#declare splOctagon = splPolygon(8)

splTranslate(Spline, V)

splRotate(Spline, V)

splScale(Spline, V)

they perform on a spline the same transformations of the translate, rotate, scale primitive modifiers. Furthermore you can scale a spline with a vector with some 0 components: at worst you'll only generate some degenerate triangle.

Notice that you must use these macros like they were functions: they return a result that must be assigned to some variable, even the starting one, e.g.

// Creates a square
#declare splSpline = splPolygon(4)
// Draws it with a red pen
PlotXYSpline(splSpline)

// Rotates the square
#declare splSpline = splRotateSpline(splSpline, z * 45)
// Changes the square into a rectangle
#declare splSpline = splScaleSpline(splSpline, <2, 0.5, 1>)
// Translates the rectangle
#declare splSpline = splTranslateSpline(splSpline, <1, 1, 0>)
// Draws the rectangle with the red pen
PlotXYSpline(splSpline)

The same curve, before and after some changes

splCombineSplines(Spl1, Spl2)

Makes a curve from two curves. This doesn't mean you will obtain a continue curve: if the splines don't touch each other they won't even after their combining.

splJoinSplines(Spl1, Spl2)

Another way to make a curve from two curves. Now, the second one will be translated so that its starting point will meet the first one's ending point, and then combined. The result will be a continue spline.


Nice, isn't it? you made a pipe. Ugly, isn't it? its quality is quite low... Of course you can increase the drawing precision.

If the triangles approximation is too coarse, you can divide the curves more. You can do it by setting a pair of variables.

They are the main variables: each spline path segment is divided into gPathDiv linear segments; in the same way each shape segment is divided into gShapeDiv linear segments. Each time you double one of these values, the number of generated triangles doubles.

The default values are gPathDiv=4 e gShapeDiv=4.

Increasing these variables could be a not so smart way to increase quality: in the second example, the zigzag pipe, the curve is made up with almost straight sections together with veri curved ones. Increasing excessively the gPathDepth variable means to divide more even the straight parts, wasting lots of time and space toward no quality increasing.

You can use an adaptive algorithm: you can enable it by setting a value greater than 0 to the gPathDepth variable. So, if a path section is too much bent, it will be split in two halves, and each half, if too bent, split again... This process can stop for two reasons:

  1. because i split a starting segment gPathDepth times;
  2. because i estimate i have an error lesser than gPathErr.
Now, the segments number used to approximate the path is variable: it raises if the curve is more bent. For each path segment i will use from gPathDiv to gPathDiv*(2^gPathDepth) linear segments.

This is true for sections too, using gShapeDepth and gShapeErr. Each section is split up as it needs.

If you reach the maximum splitting depth but the error is still greater than what you asked for, you will be warned after the surface parsing. Now you can choose if to increase the splitting depth or keep the results as they are.

The default values are gPathDepth = gShapeDepth = 0 and gPathErr = gShapeErr = 0.01.

Error values are not "high" or "low" in an absolute way: it depends from the size of the surface in your image and from the image resolution too. Default values could be good for about unit-size objects, but it depends from many factors... just try.

If you add the rows

#declare gPathDepth = 3;
#declare gShapeDepth = 3;
to the zigzag scene (before the Striscia() instruction) you will obtain:

A better result than the previous one.

gPathDiv, gPathDepth and gPathErr variables even work with PlotSpline and PlotXYSpline macros.

The error estimation routine along the path uses an approximation: it's optimized to evaluate as it were drawing a square section of side 2, centered on the path. For surfaces with bigger sections there could be bigger errors.

Furthermore, the error along the path is measured only in one of the square vertex: the one with both coordinates positive. This could lead to asymmetrical evaluations.

Eventually, in some (rare, i hope) cases, the evaluation routines (mainly the shapes one) make... very rough errors! If, for example, i have a S shaped curve, perfectly symmetrical, and i cut it in two halves, it looks like the error were 0. In fact the routine measures how much the curve segment middle point is far from the linear segment middle point connecting its ends. Because the error looks really little, the routine thinks it's dealing with a straight line, so the S is approximated only with one segment. To avoid troubles like this (you can notice it because increasing the splitting depth the quality doesn't increase) try to increment (or even decrement) by 1 the starting division: this will break symmetrical schemes.


We are going to learn how to deform the shape while it moves along the path. But first you have to learn another kind of control. You already met it too in POV language.

This control is a cubic spline function (not a curve - it moves in only one direction) i call slope. It's the same function you deal with when you modify a normal pattern with the slope_map command. A slope function, like a spine, can be seen as an object and assigned to a variable. I can generate it using the macro:

slpSlope(array[n] {<X1, Y1, M1>, <X2, Y2, M2>,..., <Xn, Yn, Mn>})

The parameter is a 3D vectors array, but the array components are not read as a space point's x, y, z components, but respectively as:

If i want to create a function defined from 0 to 3, leaving the origin horizontally, raising up to 1 when x=2 and descending to 0, i can use:

#declare slpMySlope = slpSlope(array[3] {
   <0, 0, 0>, <2, 1, 0>, <3, 0, -2>
})

Do you want to see your function graph? Just add the instruction PlotSlope(slpMySlope). With the previous declaration, you will get the graph:

A simple slope function.

Even this graph quality is ruled by the usual gPathDiv, gPathDepth and gPathErr variables.

A more complex slope example, copied and slightly modified from POV documentation, is:

/*#declare MySlope = slpSlope(array[15] {
   < 0, 0, 1>,   // Do tiny triangle here
   < 2, 2, 1>,   //  down
   < 2, 2,-1>,   //     to
   < 4, 0,-1>,   //       here.
   < 4, 0, 0>,   // Flat area
   < 5, 0, 0>,   //   through here.
   < 5, 2, 0>,   // Square wave leading edge
   < 6, 2, 0>,   //   trailing edge
   < 6, 0, 0>,   // Flat again
   < 7, 0, 0>,   //   through here.
   < 7, 0, 3>,   // Start scallop
   < 8, 2, 0>,   //   flat on top
   < 9, 0,-3>,   //     finish here.
   < 9, 0, 0>,   // Flat remaining through 1.0
   <10, 0, 0>
})

A more complex slope function.

If you want to create a linear slope, you can use the slpLinearSlope(V) macro, where V is a 2D vectors array. These vectors are to be read as:

Nice, isn't it? It's a sin it doesn't work!! :(

There is a bug in the POV parser (still there up to 3.1a version, the one i'm working with), that prevents to read the 2D vectors components. Does you version works properly? If not, use the slpLinearSlopeBug(V), where V is a 3D vectors array. The x and y components will work as the u and v components do in slpLinearSlope(V), while the z component will be ignored.

Of course i never tested the slpLinearSlope(V) macro, so i don't bet anything on its work...


When i draw a shape, i can deform it before applying to the path. But why don't do it dnamically, changing the amount of transformations along the path?

You have to know how much is long your path. If it's bent, it's hard to calculate it. Once you set the path with the SetPathSpline() macro, you will know it by reading the gPathLength variable.

Don't change this value, only read it!

Let's create a different kind of torus, with major radius 3 and minor radius 1, but with a square section. You can do it with this code:

#declare splPath = splCircle(4)                 // Circle in the xy plane
#declare splPath = splRotate(splPath, x * 90)   // Moves the circle in the xz plane
#declare splPath = splScale(splPath, <3, 3, 3>) // Makes the radius 3
SetPathSpline(splPath)                          // Apply it as path

#declare splShape = splPolygon(4)               // Square in the xy plane
SetShapeSpline(splShape)                        // Apply it as shape

But now i want the square, moving along the circle, rotates entirely around itself. I do this defining a slope that describes the rotation:

#declare slpRotSlope = slpLinearSlope(array[2] {<0, 0>, <gPathLength, 360>})
AddShapeTransform(PATH_ROTATE, slpRotSlope)

Not just the usual torus.

The AddShapeTransform() macro takes two parameters. The first one tells what transformation to perform. It can assume one of these values:

The second value passed to the function is a slope that expresses the transformation amount in each path point. It should be defined on an interval that is as long as the path.

Another example: a torus with variable minor radius. The path is the same as before.

#declare splShape = splCircle(4)
SetShapeSpline(splShape)

#declare slpScaleSlope = slpSlope(array[3] 
   {<0, 1.2, 0>, <gPathLength/2, 0.5, 0>, <gPathLength, 1.2, 0>
})
AddShapeTransform(UNIF_SCALE, slpScaleSlope)

Another variation on the same theme.

You can specify any number of transformations you want: they will be applied in the same order you declare them. All the considerations for the objects transformations are still valid: if i translate a shape and then rotate it, the shape will also orbit around the path. If i first rotate, then translate, there won't be any orbit, only a rotation on the place.

I can apply the same transformations in the section dimension of the second example to the rotation of the first one:

Melting the previous examples.

Here is another example in composing transformations: it's like a cone with base radius 1 and height 1 but, while rising, it twists along a vertical axis, at distance 1 from the cone axis.
// The path is a vertical segment, from <0, 0, 0> to <0, 1, 0>
#declare gvSky = z;
#declare splPath = splLinearSpline(array[2] {<0, 0, 0>, <0, 1, 0>}) 
SetPathSpline(splPath)                      

// The shape is a circle with radius 1
#declare splShape = splCircle(4)
SetShapeSpline(Shape)

// The shape, moving upside, become smaller
#declare slpScale = slpLinearSlope(array[2] {<0, 1>, <1, 0.0001>})
AddShapeTransform(UNIF_SCALE, slpScale)

// Translate the shape to make it orbit
#declare slpTrans = slpLinearSlope(array[2] {<0, 1>, <1, 1>})
AddShapeTransform(HORZ_TRANSLATE, slpTrans)

// Orbit the shape along the path
#declare slpRotate = slpLinearSlope(array[2] {<0, 0>, <1, 360>})
AddShapeTransform(PATH_ROTATE, slpRotate)

Odd cone

I end the point with a very little value instead of 0, in order to prevent degenerate triangles generation.

If you apply any transformation to the section, evaluating the sampling error they will be taken into account, both in path and shape direction. It's like the transform were applied to the "square" used to measure the errors. So, to have a good sampling precision with big sections too, you could draw the shape in about unit size, and then scale it up with a transform.


Another way to deform the shape is to set different sections in different path points. In the other points the shape will be a blend of the previous and the next ones.

For example, here is an easy to think but hard to describe primitive: a cylinder morphing into a box.

First i set up the path: a straight one will fit.

#declare Path = splLinearSpline(array[2] {<-1, 1, 0>, <2, 1, 0>})
SetPathSpline(Path)
To define the shape, you have to use a more flexible version of the SetShapeSpline():

AddShapeSpline(splShape, X)

In the X point on the path, the shape will be splShape. X is measured with the usual arc length, so it should range from 0 to gPathLength. In the "cylinder turning into a box" example...

#declare Shape = splCircle(4)
#declare Shape = splRotateSpline(Shape, z * 45)
AddShapeSpline(Shape, 0)

#declare Shape = splPolygon(4)
#declare Shape = splRotateSpline(Shape, z * 45)
#declare Shape = splScaleSpline(Shape, <1.4, 1.4, 0>)
AddShapeSpline(Shape, 3)

A cylinder turns into a box.

Now the surface is no more drawn from the path start to its end, but only from the first shape to the last one. Of course you need at least two shapes to see anything...


In each point, the surface section is a weighted sum only of the previous shape and the next one. The shapes percentage depends on the point distance from the shapes: if i morph a square in a circle, when i'm closer to the square i obtain "more square" sections.

A control lets me manually choose the shapes percentages. For example i could choose to keep the shape circular along almost all the path and only at the end change it into the square.

To change the amount of user defined shapes to create the shape in each point, you must use a slope. Its values should range from 0 to 1 and tell, for each point, the next shape percentage used to build the section. The previous shape percentage will be 1 less the next shape percentage. So, a 0 value means you are only using the previous shape, 1 only the next one. 0.3 means you are using the 70% of the previous section and the 30% of the next one.

You can also choose value bigger than 1 or lesser than 0, but the results are hardly predictable...

Here are some examples of morph function graphs with the relative effect on the previous "cylinder to box":

The usual morph, without modifiers

Circle for 2/3 of the path, then quickly square

1/3 circle, 1/3 morph, 1/3 square

Ping pong effect

External values to the [0,1] interval: interesting effect but not too much intuitive...


Did you like the odd cone texture? I did it with the uv mapping technique, that maps a functions (in our case the texture) defined on a simple set (such as a square) onto a more complex surface.

Think about the object:

polygon { <0, 0, 0>, <1, 0, 0>, <1, 1, 0>, <0, 1, 0>, <0, 0, 0> }

representing a square with side 1 in the z=0 plane. Apply to it any texture you want, multi-layered too. The same texture you would see on this square will be wrapped on your surface.

To do this you must use the

SetWrappedTexture(MyTexture)

macro, with any texture as parameter. In the odd cone example i used the code:

SetWrappedTexture(texture {
   pigment {
      checker color rgb 1 color blue 1
      scale 1/16
   }
   finish {
      specular 0.8		
   }
})
The lower side of the square will be mapped on the shape at the path start, while the upper side will be mapped on the shape at the end. The "left" side (the one with x=0) will be mapped on the starts of each section while the "right" side on their ends.

Less words and more draws... The command for this wrapped texture is:

SetWrappedTexture(texture {
   pigment {
      image_map {
         png "c:\\Bitmaps\\Me.png"
         interpolate 2
         once
      }
   }
})


That's me in the corner...

...that's me in the spotlight!

Er... forgive me :) The blue arrow shows the path, the red ones show two shapes.

Be careful: you can't generate a MESH if you apply a wrapped texture. In fact a wrapping requires a different matrix transform for the texture in each triangle.


Generating a surface could be a slow task. If i have to compose my scene, i don't want to waste all the time generating always the same surface, only to move a knick-knack of an inch. Even if i want to create an animation, i don't want to generate the same surface at each frame. I could ask to store the surface data in a file.

To store the surface data in a file you only have to specify the name of the file you want to create, using the gsBufferName variable. For example

#declare gsBufferName = "OddCone"

stores all the data in the "OddCone.inc" file. You don't have to specify the buffer file extension. The file will be written in the scene path, but you can also specify a different one.

The second time you will launch the scene, the routine will find the saved file and will read it (it must be in a reachable path, such the scene path or in a library path), without generating any new triangle.

If you want to change something in the surface, you have to edit its parameters and delete the buffer file before render the scene again.

If you save a surface with a wrapped texture, the routine will store only the texture transformation, not the texture data. So you can change the texture without generating the surface again.

Watch out! If you stop the macro while it's generating the surface, it will create a partial buffer file. On next rendering you could get errors (maybe a "missing }"). In this case, simply delete the buffer file and parse again. To avoid this problem i need instructions to delete and erase files, but POV-Ray lacks them... Will anybody...? :)

The routine also reads a compressed mesh file, using the compressed mesh macro file by Chris Colefax. You only have to give the .pcm compressed file the same name of the .ini generated file. You must have the pcm.mcr macro file in a library path: you can find it on the The POV-Ray Include File Page by Chris Colefax. Maybe in the future i will output the data directly in this format, but i still know too little about it...


Diaframma means "diaphragm". If you want to close your surface with two caps, you can use the rows:

object { Diaframma(          0, POLYGON) texture {MyTexture}}
object { Diaframma(gPathLength, POLYGON) texture {MyTexture}}
The first parameters is the cap location. You will usually want to close your surface adding a cap on each path end (0 and gPathLength), but it's not a duty. You could add internal diaphragms and watch them through holes in the surface, or even not draw the surface at all, leaving only the diaphragms...

The second parameter indicates what kind of object you want to generate. It can be:

You can also use a diaframma to close the hole made by a 180° corner in the path: you only have to put it in the corner. I.e.

#declare splPath = splBezierSpline(array[8] {
   <0, 0, 0>, <-6, 6, 0>, <0, 9, 0>, <0, 5, 0>
   <0, 5, 0>, <0, 9, 0>, <6, 6, 0>, <0, 0, 0>
})
SetPathSpline(splPath)
#declare gvSky = z;

#declare splShape = splCircle(4)
#declare splShape = splScaleSpline(splShape, <1,1,1>*0.5)
SetShapeSpline(splShape)

union {
   object { Striscia(UNION) }
   object { Diaframma(gPathLength/2, POLYGON) }
   pigment { color red 1 } finish { phong 0.7 }
}

Heart and diaphragm...

All the triangles in a diaphragm have a common vertex. This mean that you can properly draw a diaframma only on those curves where you can connect an internal point to each point on the curve with a straight line, without crossing the curve itself with this line.

By default the triangles meet in the section's coordinates system origin. You can express any point where to meet by setting the gvShapeCenter with a 3D vector value.

This value describes a point in the section coordinates system. The drawing routine will move it in the proper position in the space. For example, with the curve

#declare splSpline = splCircle(4)
#declare splSpline = splTranslate(splSpline, <4, 0, 0>)

you could set

#declare gvShapeCenter = <4, 0, 0>;

Using triangles you could also create a conic cap: You just have to put the gvShapeCenter not on the z=0 plane. I.e...

#declare gvShapeCenter = <0, 0, 0.75>;
object {Diaframma(gPathLength/2, UNION) }

Now the diaphragm is conic.


If you want to add more surfaces in your scene, you have to reset the control parameters using the ResetParameters() macro. It will reset the path and the shapes, the control slopes and the wrapped texture. It won't change the other variables (sky directions, sampling parameters...).


To put any object on the surface you could use the PutOnSurface(X,T) transform. X and T coordinates indicate the point on the surface: X is the coordinate in the path direction and can range from 0 to gPathLength. T is the coordinate in the shapes direction and can always range from 0 to 1.

The object will be rotated and translated on the surface. The x axis of the object's coordinate system will be parallel to the section while its y axis will be perpendicular to the surface. The z axis will be perpendicular to the other ones.

The PutOnSurface() macro works properly only when you describe your shape turning counterclockwise, such as the one generated by splCircle(). If your section turns clockwise, the object would end inside the surface. In this case you could use the PutUnderSurface(X,T) macro, that works exactly at the same way but puts the object on the other side of the surface.

For example, always using the "cylinder in shape" i can add:

// loop along the path
#declare I=0;
#while (I<=8)
   
   // loop along the sections
   #declare J=0; 
   #while (J<16)
      
      // a cone planted in the ground
      cone {
         <0, -0.05, 0>, 0, <0, 0.2, 0>, 0.05
         pigment {color blue 1}
         
         // move it on the surface
         PutOnSurface(I/8*gPathLength, J/16)
      }
      #declare J=J+1;
   #end
   #declare I=I+1;
#end

Stings in the surface

For a deeper interaction between the surface and the scene, you can read the transformation matrix values given by PutOnSurface(). The bBaseOnSurface(X,T) returns a four components array filled up with 3D vectors: they represent in order the x, y and z axis and the origin point of the coordinates system that touchs the surface in the (X,T) point.

In this example, i simulate a wire-frame surface joining a grid of points on the surface with thin cylinders.

// Cylinders texture
#declare txtWireFrame = texture {
   pigment {color rgbf <0, 1, 0, 0.8>}
   finish {ambient 0.6 diffuse 0.4}
}

// Buffer to store the points
#declare Points = array[9][16]

// points sampling 
#declare I=0;
#while (I<=8)
   #declare J=0;
   #while (J<16)
      #declare bBase = bBaseOnSurface(I/8*gPathLength, J/16)
      #declare Points[I][J] = bBase[3];
      #declare J=J+1;
   #end
   #declare I=I+1;
#end

// drawing loop
#declare I=0;
#while (I<=8)
   #declare J=0;
   #while (J<16)
      cylinder { Points[I][J], Points[I][mod(J+1,16)], 0.02 
         no_shadow texture {txtWireFrame} }
      cylinder { Points[I][J], Points[mod(I+1,8)][J], 0.02 
         no_shadow texture {txtWireFrame} }
      #declare J=J+1;
   #end
   #declare I=I+1;
#end

An old fashion surface displaying.


Appendices

Phew! That's my first English doc file... i hope my Spaghetti English is good enough...

This macro file, its documentation and samples are copyright 1999 by Daniele Varrazzo.

You can freely use, copy and edit this macro file. If you edit it and obtain something good, let me know!

If you want to include this macro file in any distribution, please before contact me: i will give you the more recent version.

This program is provided as is without any warranty. I'm not liable for any losses, damage or injury caused by its usage, including divorces, teeth-aches, punctures, alien invasions.

POV-Ray™ and Persistence of Vision™ are trade marks of the POV-Ray Team™.

the Compressed Mesh Macro File is copyright by Chris Colefax.