This is working code written in Microsoft Visual FoxPro 5.0, and object oriented version of xBase.
Return to Quadrays and XYZ Return to Quadrays and Volume


* Wandering Turtle Defined IVM Tetrahedra
* by Kirby T. Urner, November 8, 1997
* coded in Visual FoxPro 5.0 (Microsoft)
* Last modified: December 26, 1997
*
* Three classes defined:
*    Turtle       an object moving in space, keeping track
*                 of its position in xyz and quadray coordinates
*                 as well as its distance from the origin
*    Edger        an object containing methods for operating on
*                 pairs of turtles as edge vertices
*    Tetrahedron  an object that keeping track of four vertices
*                 and knows how to compute the volume of a tetrahedron
*                 based on its six edge lengths
*
* Experiment:
*
* Let four turtles wander for i jumps, each time choosing one of
* the 12 IVM directions.  Measure the 6 turtle interdistances and
* feed these to the tetrahedron's volume calculator.
*
* Let the turtles wander some more, and recompute, j time.
*
* Expectation:
*
* The tetrahedra will always have whole number volumes and will
* generally get bigger as the turtles tend to wander further apart
*
* Outcome:
*
* See Expectation.
*

#define root2  (2^.5)

otet = createobject("Tetrahedron")
? otet.ivmvol
* do walkit
release objects
return

procedure walkit

    * run this experiment 5 times, having vertex turtles start
    * from wherever they ended up last time
    for j=1 to 5

        * have the 4 vertex turtles wander -- 20 hops
        i = 20

        otet.init()
        otet.oturtle1.randivm(i)  && randivm is the 'hop i times' method
        otet.oturtle2.randivm(i)
        otet.oturtle3.randivm(i)
        otet.oturtle4.randivm(i)

        otet.calcivm()

        ? "Volume = " + str(otet.ivmvol,10,2)

    endfor  && repeat this volume experiment j times

endproc

define class turtle as custom

    * class variables
    distance = 0          && distance from origin
    dimension xyzpos(3)   && current position in xyz coords
    dimension quadpos(4)  && current position in quadrays

    * when object is defined...
    procedure init
        this.reset()
    endproc

    * reset to origin
    procedure reset
        with this
            store 0.0 to .xyzpos, .quadpos, .distance
        endwith
    endproc

    * add a quad vector
    procedure quadmov(a,b,c,d)
        with this
            .quadpos(1) = .quadpos(1) + a
            .quadpos(2) = .quadpos(2) + b
            .quadpos(3) = .quadpos(3) + c
            .quadpos(4) = .quadpos(4) + d
            .simplify()
            .quad2xyz(a,b,c,d)  && update xyz position
            .distance = .quaddist()  && update distance
        endwith
    endproc

    * add an xyz vector
    procedure xyzmov(x,y,z)
        with this
            .xyzpos(1) = .xyzpos(1) + x
            .xyzpos(2) = .xyzpos(2) + y
            .xyzpos(3) = .xyzpos(3) + z
            .xyz2quad(x,y,z)  && update quadray position
            .distance = .xyzdist()  && update distance
        endwith
    endproc

    procedure quad2xyz(a,b,c,d)
        with this
            .xyzpos(1) = .xyzpos(1) + 1/root2 * (a - b - c + d)
            .xyzpos(2) = .xyzpos(2) + 1/root2 * (a - b + c - d)
            .xyzpos(3) = .xyzpos(3) + 1/root2 * (a + b - c - d)
        endwith
    endproc

    procedure xyz2quad(x,y,z)
        with this
            .quadpos(1) = .quadpos(1) ;
                + 1/root2 * (iif(x>=0,x, 0)+iif(y>=0,y, 0)+iif(z>=0,z, 0))
            .quadpos(2) = .quadpos(2) ;
                + 1/root2 * (iif(x>=0,0,-x)+iif(y>=0,0,-y)+iif(z>=0,z, 0))
            .quadpos(3) = .quadpos(3) ;
                + 1/root2 * (iif(x>=0,0,-x)+iif(y>=0,y, 0)+iif(z>=0,0,-z))
            .quadpos(4) = .quadpos(4) ;
                + 1/root2 * (iif(x>=0,x, 0)+iif(y>=0,0,-y)+iif(z>=0,0,-z))
            .simplify()
        endwith
    endproc

    * keep quadray coordinates in simplest form
    procedure simplify
        with this
            local i
            minval=.quadpos(1)
            for i=1 to 4
                minval = min(minval,.quadpos(i))
            endfor
            for i=1 to 4
                .quadpos(i)=.quadpos(i)-minval
            endfor
        endwith

        * compute distance using quadray distance formula
    function quaddist
        rtnval=0
        with this
            rtnval = ((3/2)*(.quadpos(1)^2 + .quadpos(2)^2 + ;
                             .quadpos(3)^2 + .quadpos(4)^2) - ;
                      ( .quadpos(1)*.quadpos(2) + ;
                        .quadpos(1)*.quadpos(3) + ;
                        .quadpos(1)*.quadpos(4) ;
                      + .quadpos(2)*.quadpos(3) + ;
                        .quadpos(2)*.quadpos(4) + .quadpos(3)*.quadpos(4)) )^0.5
        endwith
        return rtnval
    endfunc

    * compute distance using xyz distance formula
    function xyzdist
        rtnval=0
        with this
            rtnval = (.xyzpos(1)^2 + .xyzpos(2)^2 + .xyzpos(3)^2)^0.5
        endwith
        return rtnval
    endfunc

    * hop in one of 12 IVM directions 'nojumps' times (parameter)
    procedure randivm(nojumps)
        local pos2, pos0, rv(4)
        for i = 1 to nojumps
            rv = 1  && preset (a,b,c,d) to (1,1,1,1)
            * pick a number between 1 and 4
            pos2 = mod(1+int(1000*rand()),4)+1  && pos2 is where the 2 will be
            pos0 = pos2                         && pos0 is where the 0 will be
            do while pos2=pos0
                pos2 = mod(1+int(1000*rand()),4)+1
            enddo
            rv(pos2) = 2  &&  one of the coordinates becomes a 2
            rv(pos0) = 0  &&  another, different coord becomes a 0
            * the remaining 2 coordinates remain set at 1
            this.quadmov(rv(1),rv(2),rv(3),rv(4))
        endfor
    endproc

    * display current position information
    procedure saypos
        with this
            ? this.name
            ?
            ? "XYZ  =  (" + ltrim(str(.xyzpos(1),5,2)) + ", " ;
                + ltrim(str(.xyzpos(2),5,2)) + ", " ;
                + ltrim(str(.xyzpos(3),5,2)) + ")"
            ? "QUAD =  (" + ltrim(str(.quadpos(1),5,2)) + ", " ;
                + ltrim(str(.quadpos(2),5,2)) + ", " ;
                + ltrim(str(.quadpos(3),5,2)) + ", " ;
                + ltrim(str(.quadpos(4),5,2)) + ")"
            ?
            ? "XYZDIST =   "+ str(.xyzdist(),5,2)
            ? "QUADDIST=   "+ str(.quaddist(),5,2)
        endwith
    endproc

enddefine

define class tetrahedron as custom
    add object oturtle1 as turtle
    add object oturtle2 as turtle
    add object oturtle3 as turtle
    add object oturtle4 as turtle
    add object oedger   as edger

    dimension elengths(6)  && edgelengths

    a=0
    b=0
    c=0
    d=0
    e=0
    f=0
    a2=0
    b2=0
    c2=0
    d2=0
    e2=0
    f2=0
    xyzvol=0
    ivmvol=0

    procedure init
        this.oturtle1.quadmov(1,0,0,0)
        this.oturtle2.quadmov(0,1,0,0)
        this.oturtle3.quadmov(0,0,1,0)
        this.oturtle4.quadmov(0,0,0,1)
        this.elengths=1
        this.setlengths()
        this.calcivm()
        this.calcxyz()
    endproc

    procedure setverts(matrix)
    with this
    
        .oturtle1.reset()
        .oturtle2.reset()
        .oturtle3.reset()
        .oturtle4.reset()                       
        
        .oturtle1.quadmov(matrix(1,1),matrix(1,2),matrix(1,3),matrix(1,4))
        .oturtle2.quadmov(matrix(2,1),matrix(2,2),matrix(2,3),matrix(2,4))      
        .oturtle3.quadmov(matrix(3,1),matrix(3,2),matrix(3,3),matrix(3,4))      
        .oturtle4.quadmov(matrix(4,1),matrix(4,2),matrix(4,3),matrix(4,4))
        
        .setlengths()       

        .calcivm()
        .calcxyz()        
    endwith
    endproc
    
    procedure setlengths
        * a,b,c radiate from a common vertex with triangle
        * def forming the opposite triangle, giving closed
        * triangles abd, bce, acf, def.  Pairs of opposite
        * edges are ae, cd, bf.
        with this

            .oedger.setpos1(.oturtle1.quadpos(1),;
                .oturtle1.quadpos(2),;
                .oturtle1.quadpos(3),;
                .oturtle1.quadpos(4))

            .oedger.setpos2(.oturtle2.quadpos(1),;
                .oturtle2.quadpos(2),;
                .oturtle2.quadpos(3),;
                .oturtle2.quadpos(4))

            .elengths(1)=.oedger.diffdist

            .oedger.setpos2(.oturtle3.quadpos(1),;
                .oturtle3.quadpos(2),;
                .oturtle3.quadpos(3),;
                .oturtle3.quadpos(4))

            .elengths(2)=.oedger.diffdist

            .oedger.setpos2(.oturtle4.quadpos(1),;
                .oturtle4.quadpos(2),;
                .oturtle4.quadpos(3),;
                .oturtle4.quadpos(4))

            .elengths(3)=.oedger.diffdist

            .oedger.setpos1(.oturtle2.quadpos(1),;
                .oturtle2.quadpos(2),;
                .oturtle2.quadpos(3),;
                .oturtle2.quadpos(4))

            .oedger.setpos2(.oturtle3.quadpos(1),;
                .oturtle3.quadpos(2),;
                .oturtle3.quadpos(3),;
                .oturtle3.quadpos(4))

            .elengths(4)=.oedger.diffdist

            .oedger.setpos2(.oturtle4.quadpos(1),;
                .oturtle4.quadpos(2),;
                .oturtle4.quadpos(3),;
                .oturtle4.quadpos(4))

            .elengths(6)=.oedger.diffdist

            .oedger.setpos1(.oturtle3.quadpos(1),;
                .oturtle3.quadpos(2),;
                .oturtle3.quadpos(3),;
                .oturtle3.quadpos(4))

            .elengths(5)=.oedger.diffdist
                    
            .a=.elengths(1)/2
            .b=.elengths(2)/2
            .c=.elengths(3)/2
            .d=.elengths(4)/2
            .e=.elengths(5)/2
            .f=.elengths(6)/2
                        
        endwith
    endproc

    procedure calcxyz
        this.setlengths()
        this.power2()
        this.xyzvol = 1/12 * (this.addopen()-this.addclosed()-this.addopposite())^0.5
        return

    procedure calcivm
        this.setlengths()
        this.power2()
        this.ivmvol = ((this.addopen()-this.addclosed()-this.addopposite())/2)^0.5
        return

        * do all 2nd powering in one place, for simplicity
    protected procedure power2
        with this
            .a2=.a*.a
            .b2=.b*.b
            .c2=.c*.c
            .d2=.d*.d
            .e2=.e*.e
            .f2=.f*.f
        endwith
        return

        * open triangles term
    function addopen
        local sumval
        with this
            sumval = .f2*.a2*.b2
            sumval = sumval + .d2*.a2*.c2
            sumval = sumval + .a2*.b2*.e2
            sumval = sumval + .c2*.b2*.d2
            sumval = sumval + .e2*.c2*.a2
            sumval = sumval + .f2*.c2*.b2
            sumval = sumval + .e2*.d2*.a2
            sumval = sumval + .b2*.d2*.f2
            sumval = sumval + .b2*.e2*.f2
            sumval = sumval + .d2*.e2*.c2
            sumval = sumval + .a2*.f2*.e2
            sumval = sumval + .d2*.f2*.c2
        endwith
        return sumval
    endfunc

    * closed triangles term
    function addclosed
        local sumval
        with this
            sumval = .a2*.b2*.d2
            sumval = sumval + .d2*.e2*.f2
            sumval = sumval + .b2*.c2*.e2
            sumval = sumval + .a2*.c2*.f2
        endwith
        return sumval
    endfunc

    * opposite triangles term
    function addopposite
        local sumval
        with this
            sumval = .a2*.e2*(.a2+.e2)
            sumval = sumval + .b2*.f2*(.b2+.f2)
            sumval = sumval + .c2*.d2*(.c2+.d2)
        endwith
        return sumval
    endfunc

enddefine

define class edger as custom
    * include two Turtle objects as part of class definition
    add object vertex1 as turtle
    add object vertex2 as turtle

    vert1dist = 0   && length of v1
    vert2dist = 0   && length of v2
    diffdist  = 0   && length of difference
    angle     = 0   && angle between v1 and v2 in radians
    xyzArea   = 0   && area between 2 vectors (parallelogram)
    Area      = 0   && area between 2 vectors (triangle) with
                    && equiangular triangle 1 x 1 = 1

    procedure init
        this.vertex1.reset()
        this.vertex2.reset()
    endproc

    procedure destroy
        release object vertex1
        release object vertex2
    endproc

    procedure setpos1(a,b,c,d)
        with this
            .vertex1.reset()
            .vertex1.quadmov(a,b,c,d)
            .getdiffdist()
            .vert1dist = .vertex1.distance
            if .vert1dist>0 and .vert2dist>0
                .getangle()
                .getArea()
                .getxyzArea()                            
            endif
        endwith
    endproc

    procedure setpos2(a,b,c,d)
        with this
            .vertex2.reset()
            .vertex2.quadmov(a,b,c,d)
            .vert2dist = .vertex2.distance            
            .getdiffdist()
            if .vert1dist>0 and .vert2dist>0
                .getangle()
                .getArea()
                .getxyzArea()                
            endif
        endwith
    endproc

    procedure getangle      
        local i, temp(4), vsum(4), abdist    
        with this
            for i = 1 to 4
                vsum(i) = (.vertex1.quadpos(i)/.vertex1.distance ;
                         - .vertex2.quadpos(i)/.vertex2.distance)
                temp(i)=.vertex1.quadpos(i)
            endfor
            .vertex1.reset()
            .vertex1.quadmov(vsum(1),vsum(2),vsum(3),vsum(4))
            abdist =.vertex1.distance
            .vertex1.reset()
            .vertex1.quadmov(temp(1),temp(2),temp(3),temp(4))
        endwith     
        .angle = 2 * asin(.5 * abdist)
    endproc

    procedure getXyzArea
        .xyzArea = .vert1dist/2 * .vert2dist/2 * sin(.angle)
    endproc
    
    procedure getArea
        .Area = (4/3)^0.5 * .vert1dist/2 * .vert2dist/2 * sin(.angle)
    endproc
    
    procedure getdiffdist
        local i, temp(4), vsum(4)
        with this
            for i = 1 to 4
                vsum(i) = .vertex1.quadpos(i) - .vertex2.quadpos(i)
                temp(i)=.vertex1.quadpos(i)
            endfor
            .vertex1.reset()
            .vertex1.quadmov(vsum(1),vsum(2),vsum(3),vsum(4))
            .diffdist =.vertex1.distance
            .vertex1.reset()
            .vertex1.quadmov(temp(1),temp(2),temp(3),temp(4))
        endwith
    endproc

enddefine

Synergetics on the Web
maintained by Kirby Urner