SheetNode substitute

SheetNode and NURBS-surfaces in general are used for very compact surface-representation with kind of unlimited LOD.

As for big surfaces SheetNode is too slow because it re-tesselates the surface every frame i wrote something almost compatible.
Each update/generate is really slow but rendering is much faster.

# -*- coding: utf-8 -*-
'''
Created on 20.02.2010

@author: Praios
'''
from pandac.PandaModules import GeomVertexData, GeomVertexFormat, Geom, GeomVertexWriter, GeomTristrips, GeomNode, Vec3, PandaNode, Point3

class Sheet(PandaNode):
    def __init__(self, name, nse=None):
        self._clean = False
        self._nse = nse
        self._usub = 2
        self._vsub = 2
        self._uvc = False
        PandaNode.__init__(self, name)
        if nse is not None:
            self.generate()

    def getNumUSubdiv(self):
        return self._usub

    def getNumVSubdiv(self):
        return self._vsub

    def getSurface(self):
        return self._nse

    def setNumUSubdiv(self, n):
        self._usub = n
        self._clean = False

    def setNumVSubdiv(self, n):
        self._vsub = n
        self._clean = False

    def setSurface(self, nse):
        self._nse = nse
        self._clean = False

    def setUseVertexColors(self, b):
        self._uvc = bool(b)
        self._clean = False

    def getUseVertexColors(self):
        return self._uvc

    def update(self):
        if self._clean:
            return False
        self.generate()
        return True

    def generate_OLD(self):
        usub, vsub, nsr, uvc = self._usub * self._nse.getNumUKnots(), self._vsub * self._nse.getNumVKnots(), self._nse.evaluate(), self._uvc
        self._clean = True
        if uvc:
            vdata = GeomVertexData('name', GeomVertexFormat.getV3n3c4t2(), Geom.UHStatic)
            color = GeomVertexWriter(vdata, 'color')
        else:
            vdata = GeomVertexData('name', GeomVertexFormat.getV3n3t2(), Geom.UHStatic)
        vertex = GeomVertexWriter(vdata, 'vertex')
        normal = GeomVertexWriter(vdata, 'normal')
        texcoord = GeomVertexWriter(vdata, 'texcoord')
        vec = Vec3(0, 0, 0)
        umin, umax = nsr.getStartU(), nsr.getEndU()
        ustep = (umax - umin) / usub
        vmin, vmax = nsr.getStartV(), nsr.getEndV()
        vstep = (vmax - vmin) / vsub
        for ui in xrange(usub):
            for vi in xrange(vsub):
                u, v = ui * ustep + umin, vi * vstep + vmin
                nsr.evalPoint(u, v, vec)
                vertex.addData3f(vec)
                nsr.evalNormal(u, v, vec)
                normal.addData3f(vec)
                texcoord.addData2f(u, v)
                if uvc:
                    color.addData4f([nsr.evalExtendedPoint(u, v, i) for i in (0, 1, 2, 3)])
        prim = GeomTristrips(Geom.UHStatic)
        for ui in xrange(usub - 1):
            for vi in xrange(vsub - 1):
                i = ui * vsub + vi
                prim.addVertices(i, i + vsub)
            prim.closePrimitive()
        geom = Geom(vdata)
        geom.addPrimitive(prim)
        node = GeomNode('gnode')
        node.addGeom(geom)
        self.removeAllChildren()
        self.addChild(node)

    def generate(self):
        result = self._nse.evaluate()
        use_vertex_color = self._uvc
        num_u_segments = result.getNumUSegments()
        num_v_segments = result.getNumVSegments()
        num_u_verts = self._usub + 1
        num_v_verts = self._vsub + 1
        if use_vertex_color:
            format = GeomVertexFormat.getV3n3c4t2()
        else:
            format = GeomVertexFormat.getV3n3t2()
        vdata = GeomVertexData("sheet", format, Geom.UHStatic);
        vertex = GeomVertexWriter(vdata, 'vertex')
        normal = GeomVertexWriter(vdata, 'normal')
        texcoord = GeomVertexWriter(vdata, 'texcoord')
        color = GeomVertexWriter(vdata, 'color')
        strip = GeomTristrips(Geom.UHStatic)
        point = Point3()
        norm = Vec3()
        for ui in xrange(num_u_segments):
            for uni in xrange(num_u_verts):
                u0 = float(uni) / num_u_verts
                u1 = float(uni + 1) / num_u_verts
                u0_tc = result.getSegmentU(ui, u0)
                u1_tc = result.getSegmentU(ui, u1)
                for vi in xrange(num_v_segments):
                    for vni in xrange(num_v_verts):
                        v = float(vni) / (num_v_verts - 1)
                        v_tc = result.getSegmentV(vi, v)
                        result.evalSegmentPoint(ui, vi, u0, v, point)
                        result.evalSegmentNormal(ui, vi, u0, v, norm)
                        vertex.addData3f(point)
                        normal.addData3f(norm)
                        texcoord.addData2f(u0_tc, v_tc)
                        result.evalSegmentPoint(ui, vi, u1, v, point)
                        result.evalSegmentNormal(ui, vi, u1, v, norm)
                        vertex.addData3f(point)
                        normal.addData3f(norm)
                        texcoord.addData2f(u1_tc, v_tc)
                        if use_vertex_color:
                            color.add_data4f(result.evalSegmentExtendedPoint(ui, vi, u0, v, 0, 4))
                            color.add_data4f(result.evalSegmentExtendedPoint(ui, vi, u1, v, 0, 4))
                    strip.addNextVertices(num_v_verts * 2)
                    strip.closePrimitive()
        geom = Geom(vdata)
        geom.addPrimitive(strip)
        node = GeomNode('gnode')
        node.addGeom(geom)
        self.removeAllChildren()
        self.addChild(node)

edit: better SheetNode compatibility (but slower)
edit2: removed doubled vertices in the prim-loop (i hope)
edit3: added SheetNode-stop-switch to the wishlist
edit4: started learning c++, added reimplementation from c+±code
edit5: small optimizations, marked old code ‘OLD’
edit6,7: typos
edit8: added short description of what nurbs-mean

Nice!? Haw fast is it?

using:

import direct.directbase.DirectStart,time
from panda3d.core import NurbsSurfaceEvaluator, SheetNode
from sheet import Sheet

nse=NurbsSurfaceEvaluator()
nse.reset(16,16)
for u in xrange(16):
    for v in xrange(16):
        nse.setVertex(u,v,(u,v,abs(u-v)))
nse.normalizeUKnots()
nse.normalizeVKnots()
l=[]
for i in range(50):
    t=time.clock()
    s=Sheet("test")
    #s=SheetNode("Test")
    s.setNumUSubdiv(4)
    s.setNumVSubdiv(4)
    s.setSurface(nse)
    s.update() # has to be called explicitly
    t2=time.clock()
    l.append(t2-t)
print sum(l)/len(l)
np=render.attachNewNode(s)
np.setY(10)
base.wireframeOn()
run()

i get more (42-45) frames on my eepc (win xp, atom 1.6ghz, intel 945 express) using my Sheet than using SheetNode (0.8-2.5 fps). the program does need a bit more time to start with my Sheet.

(each setup takes 0.57 seconds for my Sheet compared to 0.0001 seconds for SheetNode)

i think there is quite a bit of room for speed improvement in this implementation :wink:

maybe using the technique described in this paper could speed rendering nurbs in panda up alot but i dont actually know how to implement that so id need help.
paper
slides
web-page

This is an as-literally-as-possible rewrite of the c++ method for maximum compatibility:

    def generate2(self):
        result=self._nse.evaluate()
        use_vertex_color=self._uvc
        num_u_segments = result.getNumUSegments()
        num_v_segments = result.getNumVSegments()
        num_u_verts = self._usub+ 1
        num_v_verts = self._vsub + 1
        if use_vertex_color:
            format =GeomVertexFormat.getV3n3cpt2()
        else:
            format = GeomVertexFormat.getV3n3t2()
        vdata = GeomVertexData("sheet", format, Geom.UHStream);
        vertex = GeomVertexWriter(vdata, 'vertex')
        normal = GeomVertexWriter(vdata, 'normal')
        texcoord = GeomVertexWriter(vdata, 'texcoord')
        color = GeomVertexWriter(vdata, 'color')
        strip=GeomTristrips(Geom.UHStream)
        for ui in xrange(num_u_segments):
            for uni in xrange(num_u_verts):
                u0 = float(uni) / num_u_verts
                u1 = float(uni + 1) / num_u_verts
                u0_tc = result.getSegmentU(ui, u0)
                u1_tc = result.getSegmentU(ui, u1)
                for vi in xrange(num_v_segments):
                    for vni in xrange(num_v_verts):
                        v = float(vni) /(num_v_verts - 1)
                        v_tc = result.getSegmentV(vi, v)
                        point=Point3()
                        norm=Vec3()
                        result.evalSegmentPoint(ui, vi, u0, v, point)
                        result.evalSegmentNormal(ui, vi, u0, v, norm)
                        vertex.addData3f(point)
                        normal.addData3f(norm)
                        texcoord.addData2f(u0_tc, v_tc)

                        result.evalSegmentPoint(ui, vi, u1, v, point)
                        result.evalSegmentNormal(ui, vi, u1, v, norm)
                        vertex.addData3f(point)
                        normal.addData3f(norm)
                        texcoord.addData2f(u1_tc, v_tc)
                        if use_vertex_color:
                            color.add_data4f(result.evalSegmentExtendedPoint(ui, vi, u0, v, 0, 4))
                            color.add_data4f(result.evalSegmentExtendedPoint(ui, vi, u1, v, 0, 4))

                    strip.addNextVertices(num_v_verts * 2)
                    strip.closePrimitive()
        geom = Geom(vdata)
        geom.addPrimitive(strip)
        node = GeomNode('gnode')
        node.addGeom(geom)
        self.removeAllChildren()
        self.addChild(node)

this code is a bit slower than mine but is not optimized for python.
benchmark results on Core2Quad 2.66GHz, 2GB, Geforce9400GT, Win7:
Sheet 0.091 sec - 470-480fps
Sheet using generate2 0.076 sec - 570-590fps
SheetNode 0.00001 sec - 7.6-9.1fps

Propably generate2 makes a better mesh than generate but i havent found the problem in my algotithm yet.

render.analyze() says:
for generate

for generate2:

So obviously my original generate-method does not make optimal meshes…

Sorry, but what is this actually good for in practice?
I’ve never heard of SheetNode so far.

I believe it tesselates NURBS surfaces.

Exact. I forgot to mention this in the first post…
SheetNode and NURBS-surfaces in general are used for very compact surface-representation with kind of unlimited LOD.

I personally want to use them mainly for terrains as you can generate almost-infinite-resolution meshes from them with as many LOD-steps as you want.
Of course they could be used well for about anything looking organic.