Problem with NURBS surfaces

Hi,

I implemented a class called Sheet, very similar to the Rope class but that uses a NurbsSurfaceEvaluator and a SheetNode instead of a NurbsCurveEvaluator and a RopeNode.

I’ve several problems, the first, most obvious, is that it’s awfully slow. A test with a simple surface with 6 subdivisions along u and v runs at 8fps. I don’t see anything weird in PStats but maybe I’m not looking in the correct place.

Second problem, I can’t see the back face when not in wireframe mode.

Here’s the Sheet class and a test file:

# -*- coding: utf-8-*-

from pandac.PandaModules import NodePath, SheetNode, NurbsSurfaceEvaluator
from pandac.PandaModules import VBase3, Point3

class Sheet(NodePath):
    
    showSheet = base.config.GetBool('show-sheet', 1)

    def __init__(self, name="sheet"):
        self.sheetNode = SheetNode(name)
        self.surface = NurbsSurfaceEvaluator()
        self.sheetNode.setSurface(self.surface)
        self.sheetNode.setUseVertexColor(True)
        NodePath.__init__(self, self.sheetNode)
        self.name = name
        
    def setup(self, uOrder, vOrder, uVertsNum, verts, uKnots=None, vKnots=None):
        
        self.uOrder = uOrder
        self.vOrder = vOrder
        self.verts = verts
        
        self.uVertsNum =  uVertsNum
        self.vVertsNum = len(self.verts) / self.uVertsNum
        
        self.uKnots = uKnots
        self.vKnots = vKnots
        
        self.recompute()
    
    def recompute(self):
        if not self.showSheet:
            return
        
        numVerts = len(self.verts)
        self.surface.reset(numVerts, numVerts)
        self.surface.setUOrder(self.uOrder)
        self.surface.setVOrder(self.vOrder)

        defaultNodePath = None
        defaultPoint = (0, 0, 0)
        defaultColor = (1, 1, 1, 1)

        useVertexColor = self.sheetNode.getUseVertexColor()
        # this function exists for ropeNode but not for sheetNode ?
        vcd = 0 #self.sheetNode.getVertexColorDimension()
        
        idx = 0
        for v in range(self.vVertsNum):
            for u in range(self.uVertsNum):
                vertex = self.verts[idx]
                if isinstance(vertex, tuple):
                    nodePath, point = vertex
                    color = defaultColor
                else:
                    nodePath = vertex.get('node', defaultNodePath)
                    point = vertex.get('point', defaultPoint)
                    color = vertex.get('color', defaultColor)
                    
                if isinstance(point, tuple):
                    if (len(point) >= 4):
                        self.surface.setVertex(u, v, \
                                    VBase4(point[0], point[1], point[2], point[3]))
                    else:
                        self.surface.setVertex(u, v, \
                                               VBase3(point[0], point[1], point[2]))
                else:
                    self.surface.setVertex(u, v, point)
                if nodePath:
                    self.surface.setVertexSpace(u, v, nodePath)
                if useVertexColor:
                    self.surface.setExtendedVertex(u, v, vcd + 0, color[0])
                    self.surface.setExtendedVertex(u, v, vcd + 1, color[1])
                    self.surface.setExtendedVertex(u, v, vcd + 2, color[2])
                    self.surface.setExtendedVertex(u, v, vcd + 3, color[3])
                idx +=1
        
        if self.uKnots != None:
            for i in range(len(self.uKnots)):
                self.surface.setUKnot(i, self.uKnots[i])
        
        if self.vKnots != None:
            for i in range(len(self.vKnots)):
                self.surface.setVKnot(i, self.vKnots[i])
        
        self.sheetNode.resetBound(self)
        
        
    def normalize(self, uKnots = True, vKnots = True):
        if uKnots:
            self.surface.normalizeUKnots()
        if vKnots:
            self.surface.normalizeVKnots()
        
        
    def getPoints(self, len):
        result = self.surface.evaluate(self)
        numPts = len
        sheetPts = []
        for i in range(numPts):
            pt = Point3()
            u = v = i / float(numPts -1)
            result.evalPoint(u,v, pt)
            sheetPts.append(pt)
        return sheetPts
 

And a test file:

# -*- coding: utf-8-*-

from pandac.PandaModules import loadPrcFileData 
loadPrcFileData("", "show-frame-rate-meter t")
loadPrcFileData("", "want-tk #t")
loadPrcFileData("", "want-pstats 1")
loadPrcFileData("", "pstats-tasks 1")

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from sheet import Sheet
import sys

base.oobe()

do = DirectObject()
do.accept("escape", sys.exit)
do.accept("w", lambda : base.wireframeOn())

vKnot = uKnot = [0,0,0,0,1,1,1,1]

surface = Sheet("flat")
verts = [{'node':None, 'point': (3, 0, 0), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (6, 0, 0), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (9, 0, 0), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (12, 0, 0), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (3, 3, 0), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (6, 3, 0), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (9, 3, 0), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (12, 3, 0), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (3, 6, 0), 'color' : (0,0,1,0)} ,
         {'node':None, 'point': (6, 6, 0), 'color' : (0,0,1,0)} ,
         {'node':None, 'point': (9, 6, 0), 'color' : (0,0,1,0)} ,
         {'node':None, 'point': (12, 6, 0), 'color' : (0,0,1,0)} ,
         {'node':None, 'point': (3, 10, 0), 'color' : (0,1,1,0)} ,
         {'node':None, 'point': (6, 10, 0), 'color' : (0,1,1,0)} ,
         {'node':None, 'point': (9, 10, 0), 'color' : (0,1,1,0)} ,
         {'node':None, 'point': (12, 10, 0), 'color' : (0,1,1,0)}
         ]
#surface.setup(4,4,4, verts, uKnot, vKnot)
#surface.reparentTo(render)

surface2 = Sheet("curve")
verts = [
         {'node':None, 'point': (-7.5, -8., 0.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (-5., -8.3,- 3.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (5., -8.3,- 3.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (7.5, -8, 0.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (-9.8, -2.7, 3.), 'color' : (0,0.5,0,0)} ,
         {'node':None, 'point': (-5.3, -7.2, -3.), 'color' : (0,0.5,0,0)} ,
         {'node':None, 'point': (5.3, -7.2, -3.), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (9.8, -2.7, 3.), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (-11., 4.0, 3.), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (-6., -1.8, 3.), 'color' : (0,1,0,0)} ,
         {'node':None, 'point': (6., -1.8, 3.), 'color' : (0,0.5,0,0)} ,
         {'node':None, 'point': (11, 4.0, 3.), 'color' : (0,0.5,0,0)} ,
         {'node':None, 'point': (-9.5, 9.5, -3.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (-7., 7.8, 3.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (7., 7.8, 3.), 'color' : (0,0,0,0)} ,
         {'node':None, 'point': (9.5, 9.5, -3.), 'color' : (0,0,0,0)} ,
         ]

surface2.setup(4,4,4, verts, uKnot, vKnot)
surface2.sheetNode.setNumUSubdiv(6)
surface2.sheetNode.setNumVSubdiv(6)
surface2.reparentTo(render)

surface2.flattenStrong()

render.analyze() 

run()

    

I don’t master yet all the maths behind NURBS surfaces so I guess that if it’s running so slowly I may have done something wrong. Any ideas ?

It’s nothing you’re doing wrong. It’s just that the SheetNode itself is not (presently) very optimal. It was originally designed as a proof-of-concept and hasn’t been heavily used since, and so it hasn’t had a lot of pressure to make it run fast. This is a big part of why we didn’t provide a Sheet.py. :slight_smile:

If you want to render a NURBS surface on-the-fly, you might do well to use EggNurbsSurface instead, which will convert into a static (and highly optimized) triangle mesh. Of course, you won’t be able to deform this mesh at runtime in the same way that you can deform the SheetNode.

David

Oh, wait. There is something wrong here. The SheetNode isn’t great, but it’s not all that bad.

The problem is that you have created a SheetNode with far too many vertices. Replace:

        self.surface.reset(numVerts, numVerts)

With:

        self.surface.reset(self.uVertsNum, self.vVertsNum)

This is the difference between a sheet of 4 x 4 = 16 vertices, and one of 16 x 16 = 256 vertices. Quite a difference. :slight_smile:

David

As to not seeing the backface, you can enable backface rendering with:

surface2.setTwoSided(True)

This is standard Panda stuff.

David

silly me ! you’re right, thanks a lot !

I think I can post the Sheet class to the Code Snippets forum now :slight_smile:

I was wondering why that file was missing… :slight_smile:

My idea is to use a SheetNode to modify the surface at run-time and convert back and forth to/from EggNurbsSurface/SheetNode when saving/loading.

Unfortunately it doesn’t work, I tried also CullFaceAttrib.make(CullFaceAttrib.MCullNone) which I guess does the same thing, but no luck.

Works fine for me. Are you sure you called it on the right node?

David

ok, found the problem. I was calling flattenStrong before setTwoSided(True).

Thanks again.

looks quite cool - it propably doesnt help much in the test-scene but maybe adaptive subdivision (more triangles at points of interest/more curved areas and less in “flat” areas) could speed things up even more…

Finding those areas would be even more computationally intensive. I recommend using a static mesh and using the vertex shader for deformations.

just found out that its already implemented…