NurbsCurveDrawer Help

Hi, I’m trying to use NurbsCurveDrawer to draw a curve I generated using the CurveFitter. I know the curve is being constructed b.c. I can use it to make a MoPath and move an object along it. However I can’t seem to get NurbsCurveDrawer to actually draw the curve for me. The following is the snippet of code I’m using.

        nurbDrawer = NurbsCurveDrawer()
        nurbDrawer.setCurves(nurbsCurve)
        nurbDrawer.setThickness(100)
        nurbDrawer.setShowKnots(True)
        nurbDrawer.setShowHull(True)
        nurbDrawer.setShowCvs(True)
        nurbDrawer.setKnotColor(0.0,0.0,1.0)
        nurbDrawer.setTickScale(20)
        if(nurbDrawer.draw()):
            print "CURVE DRAWING!"
        np= self._nodepath.attachNewNode(nurbDrawer.getGeomNode())
        np.reparentTo(render)

The ‘nurbsCurve’ is the output of CurveFitter.makeNurbs() after I construct my curve.

Thanks in advance for any help

Hmm, we are actually in the process of phasing out the NurbsCurveDrawer and related classes. As such, this class no longer exists in the CVS version, so it’s difficult for me to help you debug the problem.

But maybe you don’t want to use NurbsCurveDrawer anyway. You should understand that the CurveFitter, Mopath, and NurbsCurveDrawer classes are all part of the older parametric curve implementation that we are gradually replacing with the NurbsCurveEvaluator and the RopeNode (and the higher-level Python Rope class).

Unfortunately in your case, while we have already removed support for NurbsCurveDrawer (the new Rope is a much better way to draw curves), we don’t have anything like the CurveFitter in the new system.

You could still use the CurveFitter as it stands. In order to use the old-style CurveFitter with the new-style Rope class, you would have to take the output of the CurveFitter as a NurbsCurve and copy it, vertex-by-vertex, into the Rope.

Then again, the CurveFitter was never really that great, which is part of why we haven’t bothered to port it forward. You might be better off using some third-party math library to fit a NURBS curve to your points. Or, depending on your needs, maybe you could use a Rope directly. I don’t know what your application is, but certain things that used to be difficult, like drawing a curve connecting animating objects, are quite easy to do with a Rope.

David

Actually, that’s a perfect reply. I was really just trying to see if the CurveFitter was doing what I expected it to. Now that I know that stuff is being phased out, I think I’ll do as you suggest and use a third party curve fitting algo and the Rope class.

Thanks again!

Hi,

Ok, I got the Rope class working and it’s displaying the path perfectly. Now I would like to use the Rope’s curve as motion path for a character’s movement using an interval.

I quicky discovered that passing it in to Mopath does not work (As you said, Mopath uses the old ParametricCurve interface and complains when I pass it a Rope).

I can’t seem to find any obvious pre-cooked way of doing this with a Rope in the documentation (no RopeMotionInterval, etc…). Am I missing something, or should I just roll my own Rope motion interval class?

Thanks,
Aaron

You could roll your own Rope MotionInterval class–it wouldn’t be very complex. Or you could just use the LerpFunctionInterval to do it. Use NurbsCurveResult.evalPoint() to get the point on the curve at time t.

David

Yeah, that’s what I ended up doing. I should’ve scratched my noggin over it a bit before making that last post.

Thanks Again,
Aaron

Ok,I got an actor moving along a nurbs curve using the method you suggested. It looks something like this:

class RopeInterval(LerpFunctionInterval):
        def __init__(self,rope,model,duration=0.0):
            curve = rope.node().getCurve()
            curve.normalizeKnots()
            result=curve.evaluate()
            def updateModelPos(val,curve=result,model=model):
                point = Vec3()
                tang  = Vec3()
                curve.evalPoint(val,point)
                curve.evalTangent(val,tang)
                angle = atan2(tang.getY(),tang.getX())
                model.setHpr(degrees(angle)+90,0,0)
                model.setPos(point)
            
            LerpFunctionInterval.__init__(self,updateModelPos,duration = duration,fromData = result.getStartT(), toData = result.getEndT())
   

I quickly noticed that the actor’s motion is not uniform along the curve. That is, it’s fast in someplaces and slow in others (mainly around bends).

I think this has something to do w/how the NURBS equations work. So then I tried to decompose the curve into linear segments so I can control the rate of travel of the actor (and make it uniform). I came up w/this.

   
class RopeMotionSequence2(Sequence):
        
        def getPoints(self, len):
            """Returns a list of len points, evenly distributed in
            parametric space on the rope, in the coordinate space of the
            Rope itself."""
            curve = self.rope.node().getCurve()
            curve.normalizeKnots()
            result = curve.evaluate(render)
            numPts = len
            ropePts = []
            print "CURVE START T: "+str(result.getStartT())
            print "CURVE END T: "+str(result.getEndT())
            for i in range(numPts):
                pt = Point3()
                #print "evaluating t: "+str(i / float(numPts - 1))
                result.evalPoint(i / float(numPts - 1), pt)
                ropePts.append(pt)
            return ropePts
        
        def __init__(self,rope,model,duration=0.0):
            grandularity = 100
            self.rope = rope
            points= self.getPoints(grandularity)
            dur = float(duration)/float(grandularity)
            seq = []
            
            for ptId in range(len(points)-1):
                #print "Creating interval: "+str(points[ptId])+" "+str(points[ptId+1])
                seq.append(LerpPosInterval(model,duration=dur,startPos=points[ptId],pos=points[ptId+1]))
            
            print 'created: ' +str(len(seq))+' lerp intervals '
            return Sequence.__init__(self,*seq)

No dice here either. It seems to be a little better, but still basically the same issue. I think what I need is to somehow capture the arc length of the curve between two points and use that to make uniform lerp intrevals.
Anyways, I’m sure someone has done this before so any hints or suggestions would be useful.

Thanks,
Aaron

One thing I have found is that if you simply unify the intervals of the knot sequence, you will improve the uniformity of a NURBS curve without much affecting its shape.

That is, for each two consecutive knots, k[i] and k[i + 1], you should make the interval between them either exactly 0 or exactly 1. If the interval is currently 0 (k[i + 1] == k[i]), then keep it 0. If the interval is anything else, make it 1 (make k[i + 1] = k[i] + 1).

Your new knot vector, then, will be an array of integers beginning with 0, possibly with some integers repeated.

David

Hi,

I tried your suggestion. It seemed to work ok, but not well enough for my purposes. After googling around a bit and turning up nothing I ended up coming up w/my own solution that seems to work reasonably well. I doubt this is the best way to do it, but it works, so I thought I would share.

"""
Approximates the length of a NURBS curve between t0 and t1 by summing the distances between evenly
spaced sample values of T. The more samples the more accurate the distance measure.
This is a very gangster form of curve integration
"""
def NurbsCurveDist(curveResult,t0,t1,samples=1000):
        maxT = max(t0,t1)
        minT = min(t0,t1)
        tInc = float(maxT-minT)/float(samples)
        p0 = Vec3()
        p1 = Vec3()
        tDist = 0
        for i in range(samples-1):
            curveResult.evalPoint(minT+tInc*i,p0)
            curveResult.evalPoint(minT+tInc*(i+1),p1)
            tDist += (p1-p0).length()
        return tDist

"""
An Interval that moves a model along a rope with uniform velocity. The method for doing this is to
segment the curve into numSegments segments and use the NurbsCuveDist approximation function to appropriatly
scale the duration of each segment relative to the specified duration for traversing the entire rope distance. The larger numSegments is the more uniform the motion will be.

"""
class UniformRopeMotionInterval(Sequence):
    def __init__(self,rope,model,duration=0.0,numSegments=1000,numSamples = 10000):
        curve = rope.node().getCurve()
        curve.normalizeKnots() #Force tStart and tEnd to be [0,1]
        curveResult=curve.evaluate()
        #Get an approximation of the total length of the curve
        curveLength = NurbsCurveDist(curveResult,0,1,numSamples)
        segs =[]
        
        #This is the method called to actually update the model along each curve segment
        def updateModelPos(val,curve=curveResult,model=model):
            point = Vec3()
            curve.evalPoint(val,point)
            model.setPos(point)
        
        for i in range(numSegments-1):
            #Calculate the startT and endT for this segment
            t0 = float(i) / float(numSegments - 1) 
            t1 = float(i+1) / float(numSegments - 1)
            
            #Get an approximate length for this segment
            segLen =  NurbsCurveDist(curveResult,t0,t1,numSamples/numSegments)
            
            #Calculate a scale factor based on the percent of the total curve length that this segment represents
            segmentDurationScale = segLen/curveLength
            
            #Calculate the duration of time it should take to traverse this segment of the curve
            segDur = segmentDurationScale*duration
            
            #Create a lerp func to do the deed
            segs.append(LerpFunctionInterval(updateModelPos,duration=segDur,fromData=t0,toData = t1))
            
        Sequence.__init__(self,*segs)