Version 2 of Triangle Strips for OpenGL

Updated 2003-11-17 02:47:58

I recently had a need to generate triangle strips for OpenGL meshes and found Pierre Tierdman's nice C++ code at http://www.codercorner.com/Strips.htm . I was in the middle of converting it to plain C when it occurred to me that this would be a nice little project to implement in Tcl, not only to validate my specfic conversion, but also to share with the community.

 package provide tristrip

 namespace eval tristrip {

 # public
        variable oneside 1 ;# generate one-sided triangle strips
        variable cnctall 0 ;# connect all strips
        variable SGIalgo 0 ;# use SGI algorithm for trilist traversal
        variable oppoext 1 ;# do opposite direction strip extension

        #Generate Triangle Strips from Triangle List
        #
        proc genTriStrips {trilst {one 1s} {cnc noconnect} {sgi nosgi}} {
                variable edgmap; catch { array unset edgmap }
                variable usegbl; catch { array unset usegbl }

                # set options (this needs reworking (suggestions welcome)...)
                #
                variable oneside; variable cnctall; variable SGIalgo
                if {[string equal $one "1s"]} {
                        set oneside 1 } else { set oneside 0 }
                if {[string equal $cnc "connect"]} {
                        set cnctall 1 } else { set cnctall 0 }
                if {[string equal $sgi "sgi"]} {
                        set SGIalgo 1 } else { set SGIalgo 0; }

                # create edge -> triangles mapping
                #
                newEdgMap $trilst ;

                # unimplemented (exercise for the reader :)
                #
                variable SGIalgo
                if {$SGIalgo} {
                        # sort trilst ascending based on the number of neighbors
                        # per tri. i.e., visit most-isolated tris first.
                }

                # create strips
                #
                set stripLst [list]
                foreach tri $trilst {
                        # starting from unused triangles ...
                        if {![info exists usegbl($tri)]} {
                                # generate a strip, save it, mark its tri's used
                                foreach {s u} [genBestStrip $tri] { break }
                                lappend stripLst $s
                                foreach t $u { set usegbl($t) 1 }
                        }
                }

                catch { array unset edgmap; array unset usegbl }

                if {$cnctall} {
                        set stripLst [list [connectAllStrips $stripLst]]
                }

                return $stripLst
        }

 # private
        variable edgmap    ;# triangles sharing an edge
        variable usegbl    ;# triangles in use globally

        proc genBestStrip {tri0} {
                # best strip so far for input tri
                set bestVtxLst {}; set bestTriLst {}

                # generate strips in all three directions
                #
                foreach \
                        fwdDir {{old mid dum} {mid dum old} {dum old mid}} \
                        bakDir {{mid old dum} {old dum mid} {dum mid old}} \
                {
                        # initialize for this strip
                        catch {array unset uselcl};
                        foreach $fwdDir [set tri $tri0] { break }
                        set vLst [list $old $mid]; set tLst [list]

                        # extend strip
                        foreach {vLst tLst} \
                                [extendStrip $vLst $tLst $tri $old $mid uselcl] { break }

                        # if opposite-direction strip extension is configured
                        variable oppoext
                        if {$oppoext} {
                                # look backwards from original tri
                                foreach $bakDir [set tri $tri0] { break }

                                # for an adjacent unused tri
                                set tri [unusedTri [otherTri $tri0 $old $mid] uselcl]
                                if {$tri != ""} {
                                        # found one so reverse strip
                                        reverseLst vLst; reverseLst tLst

                                        # extend again
                                        foreach {vLst tLst} \
                                                [extendStrip $vLst $tLst $tri $old $mid uselcl] \
                                                        { break }

                                        # for one-sided strips,
                                        # reverse strip and check/correct original windings
                                        #
                                        variable oneside
                                        if {$oneside} {
                                                reverseLst vLst; reverseLst tLst;
                                                set idxtri0 0;
                                                foreach t $tLst {
                                                        if {$t == $tri0} { break };
                                                        incr idxtri0
                                                }
                                                if {[expr {$idxtri0%2}] == 1} {
                                                        set vLst [linsert $vLst 0 [lindex $vLst 0]]
                                                }
                                        } 
                                }
                        }

                        # save strip if longer than current best strip
                        #
                        set tLen [llength $tLst]
                        set bLen [llength $bestTriLst]
                        if {$tLen > $bLen} {
                                set bestVtxLst $vLst; set bestTriLst $tLst
                        }
                }

                return [list $bestVtxLst $bestTriLst]
        }

        # extend input strip (and it's trilst) in the old/mid direction
        proc extendStrip {vLst tLst tri old mid uselclnam} {
                upvar 1 $uselclnam uselcl

                while {$tri != ""} {
                        lappend vLst [set new [otherVtx $tri $old $mid]]
                        lappend tLst $tri; set uselcl($tri) 1
                        set tri [unusedTri [otherTri $tri $mid $new] uselcl]
                        set old $mid; set mid $new
                }

                return [list $vLst $tLst]
        }

        # flatten all strip lists to one strip if configured
        proc connectAllStrips {stripLst} {
                variable oneside

                set vLst [list]; set vLen 0
                foreach s $stripLst {
                        if {$vLst != ""} {
                                set vEnd [lindex $vLst end]
                                set sBeg [lindex $s 0]

                                lappend vLst $vEnd $sBeg; incr vLen 2

                                # check/correct for one sided strip winding flip
                                if {$oneside && [expr {$vLen%2}] == 1} {
                                        foreach {v1 v2 rest} $s { break }
                                        if {$v1 != $v2} {
                                                lappend vLst $v1; incr vLen
                                        }
                                }
                        }

                        # append the existing strip
                        foreach v $s { lappend vLst $v; incr vLen }
                }

                return $vLst
        }

        # Create an edge-to-triangles map.  Keys are ordered pairs of
        # vertex indices and values are a list of triangles sharing the edge
        #
        proc newEdgMap {trilst} {
                variable edgmap; array unset edgmap
                foreach tri $trilst { foreach {v1 v2 v3} $tri { break }
                        addEdgTri $v1 $v2 $tri
                        addEdgTri $v2 $v3 $tri
                        addEdgTri $v3 $v1 $tri
                }
                # can't handle non-manifold meshes
                foreach edg [array names edgmap] {
                        if {[llength $edgmap($edg)] > 2} {
                                return -code error \
                                        "Non-manifold input : edge $edg $edgmap($edg)"
                        }
                }
        }

        # add a triangle to the list of triangles sharing edge ab
        proc addEdgTri {a b tri} {
                variable edgmap;
                if {$a < $b} { set lo $a; set hi $b
                } else       { set lo $b; set hi $a }
                lappend edgmap($lo,$hi) $tri
        }

        # get the list of triangles sharing edge ab
        proc getEdgTriLst {a b} {
                variable edgmap
                if {$a < $b} { set lo $a; set hi $b 
                } else       { set lo $b; set hi $a }
                return $edgmap($lo,$hi)
        }

        # reverse list variable in the caller's scope
        proc reverseLst {lstvarnam} {
                upvar 1 $lstvarnam lst
                set revlst [list]; set n [llength $lst] 
                while {[incr n -1] >= 0} { lappend revlst [lindex $lst $n] }
                set lst $revlst
        }

        # find the other vertex of a triangle when given two
        proc otherVtx {tri a b} {
                foreach {v1 v2 v3} $tri { break }
                if {$v1 == $a && $v2 == $b || $v1 == $b && $v2 == $a} { return $v3 }
                if {$v2 == $a && $v3 == $b || $v2 == $b && $v3 == $a} { return $v1 }
                if {$v3 == $a && $v1 == $b || $v3 == $b && $v1 == $a} { return $v2 }
        }

        # find the other triangle sharing the edge ab
        proc otherTri {tri a b} {
                variable edgmap
                foreach {t1 t2} [getEdgTriLst $a $b] { break }
                if {$tri == $t1} { set oth $t2 } else { set oth $t1 }
                return $oth
        }

        # return input triangle if unused both globally and in the given map
        proc unusedTri {tri lclusenam} {
                variable usegbl; upvar 1 $lclusenam uselcl
                set unused $tri
                if {[info exists usegbl($tri)]} { set unused "" } ;# in use globally
                if {[info exists uselcl($tri)]} { set unused "" } ;# in use locally
                return $unused
        }
 }