| Home | zeGraph lib | Lua lib | Custom lib | Tutorials | Notes | XML Script | C-Talk | Z-Script |
Lesson 1
Lesson 2
Lesson 3
Lesson 4
Lesson 5
Lesson 6
Lesson 7
Lesson 8
Lesson 9
Lesson 10
Lesson 11
Lesson 12
Lesson 13
Lesson 14
Lesson 15
Lesson 16
Lesson 17
Lesson 18
Lesson 19

Lesson 16 Make Package and Shapes

The make pakage of the utility library has several shape creation functions. An object in the real world can often be dissembled to basic building blocks, some of which can be easily generated by these functions, which often use zeArray of double floating type for input and output.

Fan

If used alone without texture or light, a fan does not look interesting. We use an earth image here again to make it attractive. Note that if you set the sweeping angle to 360 degrees, you will get a disk that may be used as the top of a column or a large filled dot. A fan needs only one normal, which usually should point to the z-direction. Since we do not use light here, it is not necessary to have the normal.

require("register")

render, scene, node, texture, shape, xyz, st
    = zeGrf.new("render" ,"scene", "node",
      "texture", "polygon", "vertex", "texcoord")

render:add(scene)
scene:set{node = node}
node:add(texture, shape)

texture:set{image = "earth.png"}

shape:set{vertex = xyz, texture_coord = st,
         color = {1, 1, 1, 1}, type = "trianglefan"}

arr = zeUtl.new("double")
zeMake.fan(arr, 200, 36, 30, 120)

-- Vertex cooidinates are in the first three vectors and
-- texture coordinates are in the fourth and fifth vectors.

xyz:add(arr)
arr:shift(3)
st:add(arr)

render:tofile("fan.png")

fan.png

Disk

The disk function is very similar to the fan function. Actually, if the inner radius is zero, the resulted shape looks the same as a fan. However, there are subtle differences between the two: a fan has fewer vertices and hence should be more efficient for rendering; whereas a disk can be textured more accurately. Like a fan, a disk needs only one normal, which usually should point to the z-direction.

require("register")

render, scene, node, texture, shape, xyz, st
    = zeGrf.new("render" ,"scene", "node",
      "texture", "polygon", "vertex", "texcoord")

render:add(scene)
scene:set{node = node}
node:add(texture, shape)

texture:set{image = "earth.png"}

shape:set{vertex = xyz, texture_coord = st,
          color = {1, 1, 1, 1}, type = "quadstrip"}

arr = zeUtl.new("double")
zeMake.disk(arr, 80, 200, 36, 0, 360)

xyz:add(arr)
arr:shift(3)
st:add(arr)

render:tofile("disk.png")

disk.png

Cylinder

Sweeping a straight line around the z-axis forms a cylinder. A cone differs from a bar ony in the base radius or the top radius. In this example, the shape looks like a cup.

require("register")

render, scene, node, light, texture, shape, xyz, nor, st
    = zeGrf.new("render" ,"scene", "node", "light",
      "texture", "polygon", "vertex", "vertex", "texcoord")

render:add(scene)
scene:set{node = node}
node:add(light, texture, shape)
light:set{position = {500, -500, 500}}
texture:set{image = "earth.png"}
shape:set{vertex = xyz, vertex_normal = nor,
         texture_coord = st, color = {1, 1, 1, 1},
         type = "quadstrip"}

arr = zeUtl.new("double")
zeMake.cylinder(arr, 300, 80, 120, 36, 0, 360)

xyz:add(arr)
arr:shift(3)
nor:add(arr)
arr:shift(3)
st:add(arr)

node:rotatex(-80)
node:translate(0, -100, 0)

render:tofile("cylinder.png")

cylinder.png

Extrude

The function extrudes any curve for a given depth along the z-axis. To see how it works, first let's create a smooth curve using the spline interpolation function in the math package.

require("register")

render, scene, node, blend, line, lxyz, point, pxyz
    = zeGrf.new("render" ,"scene", "node", "blend",
      "line", "vertex", "point", "vertex")

render:add(scene)
scene:set{node = node}
node:add(blend, line, point)

line:set{vertex = lxyz, color = {0, 0.5, 0.5, 1},
         type = "strip", solid = 2, smooth = true}
point:set{vertex = pxyz, color = {1, 0, 0, 1}, size = 5}
point:translate(0, 0, 10)  -- To avoid mixing with the line.

arr, vec, tmp = zeUtl.new("double", "double", "double")

arr:resize(10, 3)

-- Make the first vector increase monotonically
--and the third vector all zeros.

vec:range(-100, 20, 10)
arr:setarr(0, vec)
arr:setarr(2, 0)

-- Use the math object to populate the second
-- vector of arr with random numbers.

zeMath.rand(vec)

-- Scale and translate vec so that it varies in proper ranges.

vec:mul(200)
vec:sub(100)

arr:setarr(1, vec)

-- Transfer array data to the vertex object of points.

pxyz:add(arr)

-- Now the spline.
vec:copy(tmp)
zeMath.spline(tmp, 5)
arr:getarr(0, vec)
n = tmp:size()
arr:resize(n, 3)
arr:setarr(1, tmp)
zeMath.linear(vec, 5)
arr:setarr(0, vec)
arr:setarr(2, 0)

-- Transfer array data to the vertex object of line.

lxyz:add(arr)

render:tofile("spline.png")

spline.png

Now the extruding. The input array should have three vectors containing x-, y-, and z-coordinates. The first three vectors of the output array contains vertices, the second three vectors contains normals, and the last two vectors contains texture coordinates.

require("register")

render, scene, node, light, poly, xyz, nor
    = zeGrf.new("render" ,"scene", "node", 
      "light", "polygon", "vertex", "vertex")

render:add(scene)
scene:set{node = node}
node:add(light, poly)
light:set{position = {100, 500, 500}}

poly:rotatex(60)
poly:set{vertex = xyz, vertex_normal = nor,
      color = {0, 1, 1, 1}, type = "quadstrip"}

arr, vec, tmp = zeUtl.new("double", "double", "double")

-- generate x coordinate

tmp:range(-100, 20, 10)
zeMath.linear(tmp, 5)
n = tmp:size()
arr:resize(n, 3)
arr:fill(0)
arr:setarr(0, tmp)

-- generate y coordinate

vec:resize(10, 1)
zeMath.rand(vec)
vec:mul(200)
vec:sub(100)
zeMath.spline(vec, 5)
arr:setarr(1, vec)

-- generate the surface

zeMake.extrude(vec, 150, arr)
xyz:add(vec)
vec:shift(3)
nor:add(vec)

render:tofile("extrude.png")

extrude.png

Sweep

This function sweeps a curve around the z-axis to from a surface. Most of us are used to think of a curve on the x-y plane, as in extruding, but in sweeping we must get used to imagine the curve on the x-z or y-z plane. Sweeping a curve a few degrees a time for 360 degrees results in a symmetrical surface. Similar to the extruding functions, the input array should have three vectors containing x-, y-, and z-coordinates. The first three vectors of the output array contains vertices, the second three vectors contains normals, and the last two vectors contains texture coordinates. The following code demonstrates creating a Piriform teardrop by sweeping. To have a smoother surface, the normals should be calculated from the derivatives of the equation.

require("register")

render, scene, root, node, light
    = zeGrf.new("render" ,"scene", "node", "node", "light")

render:add(scene)
scene:set{node = root}
root:add(light, node)
light:set{position = {500, 500, 500}}

-- x = sqrt(z^3 - z^4) for 0 < z < 1

arr, z, x, p = zeUtl.new("double", "double", "double", "double")

x:range(0, 0.02, 51)
z:range(0, 0.02, 51)

x:mul(z)
x:mul(z)           -- z^3
z:mul(x)           -- z^4
x:sub(z)           -- z^3 - z^4
zeMath.sqrt(x)  -- x = sqrt(z^3 - z^4)

arr:resize(51, 3)
arr:fill(0)
x:mul(400)               -- scale x
arr:setarr(0, x)
z:range(0, 0.02, 51)
z:mul(400)               -- scale z
arr:setarr(2, z)

zeMake.sweep(z, 5, arr)
z:copy(x)
x:shift(3)

for i = 0, 355, 5 do
    poly, xyz, nor = zeGrf.new("polygon", "vertex", "vertex")
    node:add(poly)
    poly:set{vertex = xyz, vertex_normal = nor,
             color = {0, .8, .8, 1}, type = "quadstrip"}
    poly:rotatez(i)
    xyz:add(z)
    nor:add(x)
end

node:rotatex(90)
node:translate(0, 200, 0)

render:tofile("sweep.png")

sweep.png

Iso-surface

The function is implemented for volume rendering of 4D data in the 3D space, equivalent to contour of 3D data in a 2D space. We again use the Piriform teardrop equation to validate the isosurf() function. One form of the equation can be written as

f(x, y, z) = x^2 + y^2 + z^4 - z^3

What the following code does is using the isosurf() function to construct the surface on which f(x, y, z) = 0.

require("register")

render, scene, root, light, material, shape, xyz, nor
    = zeGrf.new("render", "scene", "node", "light",
     "material", "polygon", "vertex", "vertex")
    
render:add(scene)
scene:set{node = root}
root:add(light, material, shape)
light:set{position = {-500, 500, 500}}
material:set{ambient = {0.0215, 0.1745, 0.0215, 1, 0},
             diffuse = {0.07568, 0.61424, 0.07568, 1, 0},
             specular = {0.633, 0.727811, 0.633, 1, 0},
             shininess = {78.8, 0}}
shape:set{vertex = xyz, vertex_normal = nor,
          type = "triangles", color = {1, 1, 1, 1}}
shape:translate(0, 0, -200)
shape:rotatex(90)

arr, tri = zeUtl.new("double", "double")
arr:resize(8, 4)

step = 0.01
scale = 300

for z = 0, 1, step do
    z1 = z*z*z*z - z*z*z
    z2 = z + step
    z2 = z2*z2*z2*z2 - z2*z2*z2

    arr:setele(0, 2, scale*z)
    arr:setele(1, 2, scale*z)
    arr:setele(2, 2, scale*z)
    arr:setele(3, 2, scale*z)
    arr:setele(4, 2, scale*(z+step))
    arr:setele(5, 2, scale*(z+step))
    arr:setele(6, 2, scale*(z+step))
    arr:setele(7, 2, scale*(z+step))
    
    for x = -0.35, 0.33, step do
        x1 = x*x
        x2 = (x+step)*(x+step)

        arr:setele(0, 0, scale*x)
        arr:setele(1, 0, scale*(x+step))
        arr:setele(2, 0, scale*(x+step))
        arr:setele(3, 0, scale*x)
        arr:setele(4, 0, scale*x)
        arr:setele(5, 0, scale*(x+step))
        arr:setele(6, 0, scale*(x+step))
        arr:setele(7, 0, scale*x)
        
        for y = -0.35, 0.33, step do
            y1 = y*y
            y2 = (y+step)*(y+step)
            
            arr:setele(0, 1, scale*y)
            arr:setele(1, 1, scale*y)
            arr:setele(2, 1, scale*(y+step))
            arr:setele(3, 1, scale*(y+step))
            arr:setele(4, 1, scale*y)
            arr:setele(5, 1, scale*y)
            arr:setele(6, 1, scale*(y+step))
            arr:setele(7, 1, scale*(y+step))

            arr:setele(0, 3, -(z1+x1+y1))
            arr:setele(1, 3, -(z1+x2+y1))
            arr:setele(2, 3, -(z1+x2+y2))
            arr:setele(3, 3, -(z1+x1+y2))
            arr:setele(4, 3, -(z2+x1+y1))
            arr:setele(5, 3, -(z2+x2+y1))
            arr:setele(6, 3, -(z2+x2+y2))
            arr:setele(7, 3, -(z2+x1+y2))
            
            zeMake.isosurf(tri, 0, arr)
            n = tri:size()
            
            if (n > 2) then
                xyz:add(tri)
                tri:shift(3)
                nor:add(tri)
            end
        end
    end
end

render:tofile("isosurf.png")

isosurf.png

Shell

You have seen the applications of sphere in examples for zeLight and zeMaterial. The difference between sphere() and sphere2() is that the former generates less triangles for a similar quality sphere while the later generate texture coordinates as well. The sphere2() function actually call shell() function 8 times for produce a sphere shape. Here let's a product of the shell function.

require("register")

render, scene, node, light, texture, shape, xyz, nor, st
    = zeGrf.new("render" ,"scene", "node", "light",
      "texture", "polygon", "vertex", "vertex", "texcoord")

render:add(scene)
scene:set{node = node}
node:add(light, texture, shape)
light:set{position = {500, 500, 500}}
texture:set{image = "earth.png"}
shape:set{vertex = xyz, vertex_normal = nor,
          texture_coord = st, color = {1, 1, 1, 1},
          type = "triangles"}

arr = zeUtl.new("double")
zeMake.shell(arr, 200, 4, 1)

xyz:add(arr)
arr:shift(3)
nor:add(arr)
arr:shift(3)
st:add(arr)

node:rotatey(-30)
node:rotatex(30)

render:tofile("shell.png")

shell.png

Delaunay Triangulation

The Delaunay triangulation is a method to construct triangles from a series of randomly sampled data. These triangles may then be used for surface and contour plots. The function takes a double array of m by 3 as input and returns triangles on the xy-plane and the z-value at triangle vertices. Here is a test for the function:

require("register")

render, scene, node, blend, poly, point, xyz
    = zeGrf.new("render" ,"scene", "node",
      "blend", "polygon", "point", "vertex")

render:add(scene)
scene:set{node = node}
node:add(blend, poly, point)

poly:set{vertex = xyz, color = {0, 0, 1, 1},
        type = "triangles", fill = 0, linewidth = 1.5,
        smooth = true}
point:translate(0, 0, 10)    -- avoid overlap of line on point
point:set{vertex = xyz, color = {1, 0, 0, 1}, size = 5}

arr, tmp = zeUtl.new("double", "double")
tmp:resize(100, 3)
zeMath.rand(tmp)

-- Sort the whole array in assending order according
-- of the first vector. and then triangulate

tmp:sort(0, 1)
zeMake.delaunay(arr, tmp)

-- Scale and array

arr:sub(0.5)
arr:mul(200)

xyz:add(arr)

render:tofile("delaunay.png")

delaunay.png

We took a short cut in this sample: let the line and point objects to share the same vertex object. To make a similar plot with a large number of data, it may be much more efficient to use separate vertex objects for line and point and transfer the data to the vertices of points before triangulation, because the array on output is several times larger than its initial size.

Finding a contour line is very easy. Here is an example.

require("register")

render, scene, node, blend, poly, pxyz, pclr, line, lxyz
    = zeGrf.new("render" ,"scene", "node", "blend",
      "polygon", "vertex", "color", "line", "vertex")

render:add(scene)
scene:set{node = node}
node:add(blend, poly, line)
poly:set{vertex = pxyz, vertex_color = pclr, type = "triangles"}
line:translate(0, 0, 10)
line:set{vertex = lxyz, color = {0, 0, 0, 1},
        solid = 1.5, smooth = true, type = "lines"}

arr, vec, tmp = zeUtl.new("double", "double", "double")
tmp:resize(100, 3)
zeMath.rand(tmp)
tmp:sort(0, 1)
zeMake.delaunay(arr, tmp)

pxyz:add(arr)

-- Color triangles acoording to z-values

arr:getarr(2, tmp)
n = tmp:size()
vec:resize(n, 4)
vec:fill(1)
vec:setarr(0, tmp)
vec:setarr(1, tmp)
vec:setarr(2, tmp)

pclr:add(vec)

-- Contour

tmp:resize(1, 1)
tmp:fill(0.5)
zeMake.contour3(vec, tmp, 0, arr)

lxyz:add(vec)

node:scale(200, 200, 10)

render:tofile("delaunay2.png")

delaunay2.png