please check my plan

I’m new to Panda3D and I’m trying to get this project done. Follows is the description and then several possible solutions. I would greatly appreciate it if anyone can advise me as to which mechanism is best suited to architecture of Panda3D.

Classic tunnelware but flexible.

  • cylindrical tunnel leads away from user into distance.
  • tunnel is flexible - controlled by bspline.
  • tunnel has animated texture map on it.
  • (Actually several concentric tunnels travelling at different speeds, revealed using alpha maps).
    Simple model based
  • tried adapting tunnel demo to have separately rotated objects but tunnel sections cannot be joined wihout seams.
  • adandonedProcedural manual transform
  • create classic procedural multislice cylinder
  • loop over each ring doing manual transforms and UV edit
  • OR use UvScrollNode to do UV animation
    (possibly slow)Procedural auto transform
  • create classic procedural multislice cylinder
  • however each ring of verts goes into its own array and is also used to create separate ring objects.
  • These standalone ring objects are manipulated using setPos, setHpr node based manipulations.
    Because they share verts the cylinder moves as a side effect.
    (is this even possible)Rope style
  • Use Rope primitive and somehow get animated UVs onto the setRenderMode(RopeNode.RMTube) style.
  • Camera inside the Rope.
    (is this even possible)
    Some other way ?

I’m using 1.7.2 as 1.8 is marked unstable on download page. Is it OK to use ?
numpy is fast for array manipulations. Is numpy able to be mapped onto panda3D’s structures ? Should I bother ?

Any responses and guidance is greatly appreciated.

so it looks like the choice above entitled “Procedural auto transform” is not possible.

I created a GeomVertexData array holding my cylinder vertices. (vdata)
Then I created the GeomTriangles(Geom.UHStatic) for all of them.
I also made GeomLines(Geom.UHStatic)for a single ring in the cyclinder, indexing into the same array.
So I was hoping they were sharing the same vdata.
But the calls to create the Geom nodes seem to have split them apart.

        self.cylGeom = Geom(vdata)
        for p in tubeprims:
        self.ring1Geom = Geom(vdata)
        for p in ringprims:
        self.tunnelA = GeomNode('TunnelA')
        c = render.attachNewNode(self.tunnelA)
        self.ring1 = GeomNode('Ring1')
        d = render.attachNewNode(self.ring1)

So subsequent calls to (say) d.setPos do not move the vertices in the cylinder object :frowning:

Oh well next plan on the list.

Can anyone save me some time and tell me what else won’t work ?

Not sure if I understand your intentions correctly, but maybe this relates to the first question I ever asked on this forum: [My questions thread)

However my solution involving LerpTexOffsetInterval applied to some uniform cylinder, not a more dynamic, irregular shape you seem to descibe. I guess what you want would require a shader or whatever.

Thanks for the feedback Bradamante. Great thread. I will look into LerpTex and friends. Any ideas on UvScrollNode ?

Here I have implemented my “Procedural manual transform” and The tunnel behaves usefully and looks to be calculating fast enough. So I guess I’ll go with that.
Next is this issue of animating the uvs (which I could also do manually within ‘turn’ but I want it as efficient as possible.


# do fullscreen etc first
from panda3d.core import loadPrcFileData
#loadPrcFileData('', 'fullscreen 1')

import math
import direct.directbase.DirectStart 
from panda3d.core import Fog, Texture, GeomNode
from panda3d.core import TextNode
from panda3d.core import Light,Spotlight,AmbientLight,DirectionalLight
from panda3d.core import Vec3,Vec4,Point3, Mat4, VBase3
from panda3d.core import lookAt
from panda3d.core import GeomVertexFormat, GeomVertexData
from panda3d.core import Geom, GeomTriangles, GeomLines, GeomVertexWriter
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.interval.MetaInterval import Sequence
from direct.interval.LerpInterval import LerpFunc
from direct.interval.FunctionInterval import Func
from direct.task.Task import Task
import sys

# tunnel defs [radius, length, ringsteps, lengthsteps, texturefile]
tunneldefs = [[12,60,16,8, "models/tunnel.jpg"],
              [20,60,16,5, "models/wood.jpg"]  ]

#A simple function to make sure a value is in a given range, -1 to 1 by default
def restrain(i, mn = -1, mx = 1): return min(max(i, mn), mx)

class World(DirectObject):  
    def __init__(self):
        #base.disableMouse() #disable mouse control so that we can place the camera
        #camera.setPosHpr(0,0,10, 0, -90, 0)
        camera.setPosHpr(0,0,0, 0, 0 , 0)
        # define for 35mm lenses
        base.camLens.setFilmSize(24, 36)
        # set the background color to black
        # setup task to control tunnel
        taskMgr.add(self.turn, "turn")
        ###World specific-code

        #Define the keyboard input
        #Escape closes the demo
        self.accept('escape', sys.exit)         
        self.accept('1', self.inc_fov, [-2])
        self.accept('2', self.inc_fov, [2])

        #Load the tunel and start the tunnel
        self.tunnels = [] # [[node, initialV, vdata], tunneldef]
        for t in tunneldefs:
            self.tunnels.append([self.init_tube(t[0], t[1], t[2], t[3], t[4]), t])

    def show_tunnels(self):
        i = 0
        for t in self.tunnels:
            print "tunnel", i
            print "  [0][0] Node=",t[0][0]
            print "  [0][1] initialV has", len(t[0][1]), "vertices"
            print "  [0][2] vdata has", repr(t[0][2])
            print "  [1] defs = [radius, length, ringsteps, tubesteps]]"
            print "  [1] defs =", t[1]

    def myNormalize(self, myVec):
        # can't normalise in-place
        return myVec

    def circle(self, radius, steps):
        """ Generate a single ring to be used for each ring in cylinder
        pos = []
        norm = []
        angle = math.pi*2/steps
        cos = math.cos(angle)
        sin = math.sin(angle)
        xn, zn = radius, 0
        for i in range(steps):
            nf = 1/(xn*xn+zn*zn)**0.5
            norm.append((xn*nf, zn*nf))
            xt = xn
            xn = cos*xn - sin*zn
            zn = sin*xt + cos*zn
        # do last one (duplicate of first to close)
        return pos,norm

    def init_tube(self, radius, length, ringsteps, tubesteps, texture="models/tunnel.jpg"):
        length = length
        tubesteps = tubesteps
        ringsteps = ringsteps
        initialV = []
        form = GeomVertexFormat.getV3n3t2()#getV3n3cpt2()
        vdata =    GeomVertexData('cyl', form, Geom.UHDynamic)
        vertex =   GeomVertexWriter(vdata, 'vertex')
        normal =   GeomVertexWriter(vdata, 'normal')
        #color =    GeomVertexWriter(vdata, 'color')
        texcoord = GeomVertexWriter(vdata, 'texcoord')
        ring, normals =, ringsteps)
        ustep = 1.0/ringsteps
        vstep = 1.0/(tubesteps-1)
        ystep = float(length) / (tubesteps-1)
        y = 0
        v = 0
        for i in range(tubesteps):
            for j in range(ringsteps+1): # same as len(ring)
                p = ring[j]
                n = normals[j]
                vertex.addData3f(p[0], y, p[1])
                initialV.append(VBase3(p[0], y, p[1]))
                #color.addData4f(0.5, 1.0*j/ringsteps, 1.0*i/tubesteps, 1.0)
                texcoord.addData2f(u, v)
                u += ustep
            y += ystep
            v += vstep
        # vdata now defined
        print len(initialV), "Vertices in tunnel"
        #print self.vdata
        # make triangles
        tris = []
        for i in range(tubesteps-1):
            for j in range(0,ringsteps):
                idx = j+i*(ringsteps+1)
                #print idx, [idx,idx+1,idx+ringsteps+1], [idx+ringsteps+1, idx+1, idx+1+ringsteps+1]
                tris.append([idx,idx+1,idx+ringsteps+1]) # 1/2 quad
                tris.append([idx+ringsteps+1, idx+1, idx+1+ringsteps+1]) # 2nd 1/2 quad
        # have list make triangles into prims
        #print "\n",tris
        tubeprims = []
        # make tube triangles
        for t in tris:
            prim = GeomTriangles(Geom.UHStatic)
        # finalise
        cylGeom = Geom(vdata)
        for p in tubeprims:
        tunnelA = GeomNode('TunnelA')
        c = render.attachNewNode(tunnelA)
        # texture
        tex = loader.loadTexture(texture)
        return (c, initialV, vdata)

    def turn(self, task):
        """ Based on mouse input (later to be replaced)
            bend tubes progressively into distance.
            Would prefer a bspline here and to set each tube ring to position/orientation of bspline
        #Check to make sure the mouse is readable
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            #Here we multiply the values to get the amount of degrees to turn
            screenYrot = restrain(mpos.getX()) * 150
            screenXrot = restrain(mpos.getY()) * 200
            Zaxis = Vec3( 0, 0, -1 ) # up vector
            Xaxis = Vec3( 1, 0, 0 )
            for tunnels in self.tunnels:
                verts = GeomVertexWriter(tunnels[0][2], 'vertex')
                tubesteps = tunnels[1][3]
                length = tunnels[1][1]
                # make up one mat per ring to move those ring's vertices
                newmats = range(tubesteps)
                pos = VBase3(0,0,0)
                zangle = 0
                xangle = 0
                for i in range(tubesteps):
                    #print i,zangle, pos
                    scalefact = 1.1-float(i)/(tubesteps-1)
                    newmats[i] = Mat4.scaleMat(scalefact, 1, scalefact) * Mat4.rotateMat( xangle, Xaxis ) * Mat4.rotateMat( zangle, Zaxis )# * Mat4.translateMat( pos)
                    # calc next ring's values
                    #pos[1] += float(length)/(tubesteps-1) # ! using initial tube values
                    zangle += float(screenYrot)/(tubesteps -1)
                    xangle += float(screenXrot)/(tubesteps -1)
                # Move the verts by writing new ones
                i = 0
                rstep = tunnels[1][2] + 1 # ringsteps
                initialV = tunnels[0][1] # initial tube verts in VBase3 format
                while not verts.isAtEnd():
                    ringidx = i/rstep
                    mat = newmats[ringidx]
                    #print i, ringidx
                    newv = mat.xformPoint(initialV[i])
                    #if i == 0:  print " ",self.initialV[i],newv
        return task.cont #Task continues infinitely

    def inc_fov(self, change):
        print"focal = ", base.camLens.getFocalLength()+change
        status = self.mySound.status()

#End Class World

w = World()