* Main sequence -- instantiates objects, sets properties, 
* triggers methods.

close tables

otetra = createobject("tetrahedron")

otetra.setcolor("Orange")
otetra.writeoutput()

otetra.setcolor("Black")
otetra.rotate(90,"X")
otetra.writeoutput()

otetra.rotate(-90,"X")  && set the data table back how it was

release otetra
return


* The class definitions below are what define the properties
* and behaviors of the instantiated objects.

define class tetrahedron as polyhedron

    procedure init(ptable,etable)
        if parameters()=0
            this.setpoints("tetpoints.dbf")
            this.setedges("tetedges.dbf")
        else
            this.setpoints(ptable)
            this.setedges(etable)
        endif
        * the parent class has code for opening both tables, no
        * matter what their names (always the same aliases), so
        * invoke the parent version of this init() method too
        polyhedron::init()
    endproc

enddefine

define class polyhedron as custom
    add object oWritePOV as WritePOV
    add object oMatrixOps as MatrixOps
    color  = ""
    degrees = 0  && default rotation angle
    axis = "X"   && default axis of rotation

    procedure init(ptable,etable)
        if parameters()>0
            this.setpoints(ptable) && inform imported objects
            this.setedges(etable)  && inform imported objects
        endif

        if not used("points")
            select select(1)
            * open the Points table (alias points)
            use (this.owritepov.pointstable) alias points order pointid
        endif
        if not used("edges")
            * open the Edges table (alias edges)
            select select(1)
            use (this.owritepov.edgestable) alias edges
        endif

    endproc

    procedure setpoints(dbname)
        this.omatrixops.pointstable = dbname
        this.owritepov.pointstable  = dbname
    endproc

    procedure setedges(dbname)
        this.owritepov.edgestable  = dbname
    endproc

    procedure setcolor(cname)
        this.owritepov.shapecolor  = cname
    endproc

    procedure setrotate(deg,axis)
        this.degrees = deg * pi()/180
        this.omatrixops.setdegrees(this.degrees)
        this.axis = axis
        this.omatrixops.axis = this.axis
    endproc

    procedure writeoutput
        this.owritepov.writeoutput()
    endproc

    procedure rotate(degrees, axis)
        if parameters()>0
            this.setrotate(degrees, axis)
        endif

        if not used("points")
            select select(1)
            use (this.omatrixops.pointstable) alias points order pointid
        endif

        select points
        go top

        do case
        case this.axis="X"
            this.omatrixops.xrotate()
        case this.axis="Y"
            this.omatrixops.yrotate()
        case this.axis="Z"
            this.omatrixops.zrotate()
        endcase
        return
    endproc

    procedure destroy
        close tables
        return
    endproc

enddefine

define class writepov as custom
    pointstable = ""
    edgestable = ""
    cyldiam = "0.04"
    drawaxes = .T.
    axlength = 2.5
    axdiam = "0.02"
    shapecolor = "Blue"
    axcolor = "Green"
    hnd = 0

    outputfile = "myfile.pov"

    procedure init()
        local temp
        this.startpov()
        if this.drawaxes
	        this.makeaxes()
	    endif
        return
    endproc

    procedure startpov()
        with this
            local filename

            filename=this.outputfile

            if file(filename)
                erase (filename)
            endif

            .hnd=fcreate(filename)

            if .hnd>0
                =fopen(filename)
            endif

            =fputs(.hnd, "//POV-Ray script")
            =fputs(.hnd, '#version 3.1')
            =fputs(.hnd, 'global_settings { assumed_gamma 2.2 }')
            =fputs(.hnd, '#include "colors.inc"')
            =fputs(.hnd, '#include "shapes.inc"')
            =fputs(.hnd, '#include "glass.inc"')
            =fputs(.hnd, '#include "woods.inc"')
            =fputs(.hnd, '#include "metals.inc"')
            =fputs(.hnd, '#include "textures.inc"')
            =fputs(.hnd, '#default {texture{pigment{color White}'+;
                'finish{phong 0.01 ambient 0.2 diffuse 0.6}}}')
            =fputs(.hnd, '#declare T1 = texture{Gold_Metal}')
            =fputs(.hnd, '#declare T2 = texture{T_Wood1}  // Oak ')
            =fputs(.hnd, '#declare T3 = texture{T_Copper_3A}')
            =fputs(.hnd, "")
            =fputs(.hnd, "#declare Cam_factor = 8")
            =fputs(.hnd, "#declare Camera_X = 1 * Cam_factor")
            =fputs(.hnd, "#declare Camera_Y = 0.5 * Cam_factor")
            =fputs(.hnd, "#declare Camera_Z = -0.9 * Cam_factor")
            =fputs(.hnd, "<Camera_X, Camera_Y, Camera_Z>camera { location  ")
            =fputs(.hnd, "		up        <0, 1.0,  0>    right     <-4/3, 0,  0>")
            =fputs(.hnd, "		direction <0, 0,  3>      look_at   <0, 0, 0> ")
            =fputs(.hnd, "		rotate <0,0,0>}")
            =fputs(.hnd, "")
            =fputs(.hnd, "<Camera_X - 2, Camera_Y + 5 , Camera_Z + 5>light_source {  color White }")
            =fputs(.hnd, "<Camera_X - 2, Camera_Y + 5 , Camera_Z - 3>light_source {  color White }")
            =fputs(.hnd, "")
            =fputs(.hnd, "// Background:")
            =fputs(.hnd, "background {color White}")
        endwith
    endproc

    procedure makeaxes
        local tempshape, tempdiam
        tempshape = this.shapecolor
        tempdiam  = this.cyldiam
        this.shapecolor = this.axcolor
        this.cyldiam    = this.axdiam

        this.writecylinder(this.axlength,0,0,-this.axlength,0,0)
        this.writecylinder(0,this.axlength,0,0,-this.axlength,0)
        this.writecylinder(0,0,this.axlength,0,0,-this.axlength)

        this.shapecolor = tempshape
        this.cyldiam = tempdiam
        return
    endproc

    procedure writepoint(a,b,c)
        with this
            =fputs(.hnd, "sphere{<";
                +str(a,10,7)+",";
                +str(b,10,7)+",";
                +str(c,10,7)+">," + .cyldiam;
                +" pigment {color "+ .shapecolor + "} no_shadow}")
        endwith
    endproc

    procedure writecylinder(a,b,c,d,e,f)
        * write a line in the POV file defining a cylinder w/ spherical nibs
        with this
            =fputs(.hnd, "cylinder{<";
                +str(a,10,7)+",";
                +str(b,10,7)+",";
                +str(c,10,7)+">,<";
                +str(d,10,7)+",";
                +str(e,10,7)+",";
                +str(f,10,7)+">," + this.cyldiam;
                +" pigment {color "+this.shapecolor+"} no_shadow}")
        endwith
    endproc

    procedure destroy()
        =fclose(this.hnd)
        return
    endproc

    procedure writeoutput
        local x1,y1,z1,x2,y2,z2

        select edges  && select the Edges table
        go top

        scan while not eof()  && scan to the end

            =seek(vert1,"points")  && get first vertex
            x1=points.xcoord
            y1=points.ycoord
            z1=points.zcoord

            =seek(vert2,"points")  && get second vertex
            x2=points.xcoord
            y2=points.ycoord
            z2=points.zcoord

            this.writepoint(x1,y1,z1)  && nub
            this.writecylinder(x1,y1,z1,x2,y2,z2)  && edge
            this.writepoint(x2,y2,z2)  && nub

        endscan
        return

    endproc

enddefine

define class matrixops as custom
    pointstable = ""
    theta=0
    axis=""
    cos_theta=0
    sin_theta=0

    procedure setdegrees(deg)
        this.theta = deg
        this.cos_theta  = cos(this.theta)
        this.sin_theta  = sin(this.theta)
        return
    endproc

    procedure xrotate
        local newx, newy, newz
        ? "Rotate around X by " + str(this.theta,5,2) + " degrees using " + this.pointstable

        *         / 1   0        0       \
        * X AXIS  | 0   cos(a)  -sin(a)  |
        *         \ 0   sin(a)   cos(a)  /

        scan while not eof()
            newx = xcoord
            newy = this.cos_theta*ycoord - this.sin_theta*zcoord
            newz = this.sin_theta*ycoord + this.cos_theta*zcoord
            replace xcoord with newx, ycoord with newy, zcoord with newz
        endscan
        return
    endproc

    procedure yrotate
        local newx, newy, newz
        ? "Rotate around Y by " + str(this.theta,5,2) + " degrees using " + this.pointstable


        *         / cos(a)   0    -sin(a) \
        * Y AXIS  | 0        1    0       |
        *         \ sin(a)   0    cos(a)  /

        scan while not eof()
            newx = this.cos_theta*xcoord - this.sin_theta*zcoord
            newy = ycoord
            newz = this.sin_theta*xcoord + this.cos_theta*zcoord
            replace xcoord with newx, ycoord with newy, zcoord with newz
        endscan
        return
    endproc


    procedure zrotate
        local newx, newy, newz
        ? "Rotate around Z by " + str(this.theta,5,2) + " degrees using " + this.pointstable

        *        / cos(a)  -sin(a)  0  \
        * Z AXIS | sin(a)   cos(a)  0  |
        *        \ 0        0       1  /

        scan while not eof()
            newx = this.cos_theta*xcoord - this.sin_theta*ycoord
            newy = this.sin_theta*xcoord + this.cos_theta*ycoord
            newz = zcoord
            replace xcoord with newx, ycoord with newy, zcoord with newz
        endscan
        return
    endproc

enddefine