Procedural 3D surfaces ... where to start?

I am wanting to experiment with rendering some simple texture mapped terrain surfaces and I am not quite sure where to start. I have quite a bit of experience with CLOD terrain algorithms in the past (my own implementations of Rottger, a quadtree, and ROAM in C++) and would eventually like to tinker with such also. However, before I can start, I need to understand how to draw simple faceted geometry in Panda3D. I need to understand the mechanizms for drawing facets, colorizing them, lighting them, and for mapping textures onto them. Just for starters I would like to build a simple grid composed of simple facets with varying elevation and apply a repeating texture to it. Would also be nice to tinker with lighting it after I got this working … Can anyone point me to a good documentation source that would help me out here?

[edit]Note that I am not talking about displaying a terrain stored in a model file. I want to write the renderer myself and all I need is to understand the basic rendering primitives.[/edit]

Thanks in advance,
Paul

The internal structures that Panda uses to represent geometry, along with the classes and methods you need to construct geometry on-the-fly, are fully documented in the manual. Also, one of the demo applications that ships with Panda demonstrates creating dynamic geometry to render a number of branching trees. Furthermore, if you search through the forums, I think you’ll find a number of different threads from various people experimenting with this technique.

Is any of these inadequate to your needs? Do you have some more specific questions about any of it that are not addressed by the existing documentation and examples?

David

You can try something like this, i just wrote this code on the fly, it may not compile but it should be close…


## create the proper vert writers
format=GeomVertexFormat.getV3n3t2()
vdata=GeomVertexData("TerrainVertices", format, Geom.UHStatic)
vertex=GeomVertexWriter(vdata, 'vertex')
normal=GeomVertexWriter(vdata, 'normal')
texcoord=GeomVertexWriter(vdata, 'texcoord')

## create a 32 by 32 chunk of terrain
for y in range(0, 32):
     for x in range(0, 32):
          vertex.addData3f(x, 0, y)
          normal.addData3f(0, 1, 0)
          texcoord.addData2f(x, y)

## now build the triangle indexes for the terrain
tris=GeomTriangles(Geom.UHStatic)
for y in range(0, 32):
     for x in range(0, 32):
          tris.addVertex((y-0) * (32) + (x-0)) 
          tris.addVertex((y+1) * (32) + (x-0)) 
          tris.addVertex((y-0) * (32) + (x+1))
          tris.closePrimitive()				
          tris.addVertex((y-0) * (32) + (x+1)) 
          tris.addVertex((y+1) * (32) + (x-0)) 
          tris.addVertex((y+1) * (32) + (x+1))
          tris.closePrimitive()

## add the new geometry to scene
chunkGeom=Geom(vdata) 
chunkGeom.addPrimitive(tris)
terrGN=GeomNode("terrainGeom") 
terrGN.addGeom(chunkGeom)
terrainNodePath = render.attachNewNode(terrGN)

Apparently there is a large discrepancy between the manual that I downloaded from panda3d.org/manual/index.php/Main_Page . Also, I do not find documentation of the data structures in the reference manual.

For example, the documentation I downloaded has the following Section P:


P. Procedurally Generating 3D Models 
   1. Vertices in Panda3D 
   2. Creating Geometry from Scratch 
   3. Tweaking an Existing Model 
   4. Creating New Textures from Scratch 
   5. Writing 3D Models out to Disk 
   6. Generating Heightfield Terrain 

While informative, I found the downloaded documentation left out a lot of information that I needed. The online documentation however, has the following section P:


P. Advanced operations with Panda's internal structures 
   1. How Panda3D Stores Vertices and Geometry 
      1. GeomVertexData 
      2. GeomVertexFormat 
      3. GeomPrimitive 
      4. Geom 
      5. GeomNode 
   2. Procedurally Generating 3D Models 
      1. Defining your own GeomVertexFormat 
      2. Pre-defined vertex formats 
      3. Creating and filling a GeomVertexData 
      4. Creating the GeomPrimitive objects 
      5. Putting your new geometry in the scene graph 
   3. Other Vertex and Model Manipulation 
      1. Reading existing geometry data 
      2. Modifying existing geometry data 
      3. More about GeomVertexReader, GeomVertexWriter, and GeomVertexRewriter 
      4. Creating New Textures from Scratch 
      5. Writing 3D Models out to Disk 
      6. Generating Heightfield Terrain

Obviously there is a lot more information in the “wiki” than in the downloadable manual. I didn’t realize that when I was learning Panda since I was using a non-networked PC and had to rely on the downloadable version. Now I read on the download page “These are snapshots of the online manuals. The online versions may be more recent,…”. I will start downloading the documentation and installing it on my non-network PC every other day or so … My eyes have been opened … thanks.

Wow, thanks for taking the time and effort to write and post that. No, it doesn’t compile directly but I am using it in an experiment and I will get it to work. When I do I will post it here for all to use …

Thanks again,
Paul

This section is actually quite recent, so it’s understandable that it’s not in the downloadable snapshot yet. But in general, the online manual doesn’t change that often, so downloading a new version every other day may be overkill–perhaps you can just download a new manual with each release, and then check the online version again if you find your downloaded snapshot doesn’t sufficiently answer your questions.

I apologize for the snippy tone of my first reply; I really didn’t mean it to sound that terse, but it’s one of the undeniable truths of the internet that rudeness can materialize in a post after the ‘submit’ button has been pressed. I think it has to do with the nature of the TCP/IP stack. :slight_smile:

David

LOL! No offense taken and no apology necessary … Thanks for doing all you do cause it makes my job a lot easier :smiley:

Paul

Edit to the above … it compiled but it did not run.

I worked with that code over and over for a day and could not get it to run without some sort of assertion error indicating that the data is not valid. So … I took the pseudo code in the wiki and tinkered with it for quite some time … I get the same error. I even simplified the code to vertex data only and I still got the error. So … I whittled it down to a single triangle … same error. I cannot for the life of me figure out why it isn’t working. That was with 1.2.2 … I have just installed 1.2.3 and I get the same error. Could someone give me some insight as to why?

My Source Code is :


def CreateGridNode() :

	# Define the vertex format (GVF)
	format = GeomVertexFormat.getV3()

	# Create the vertex data container (GVD) using the GVF
	vdata = GeomVertexData('GridVertices',format,Geom.UHStatic)

	# Create writers for the container, one for each column of the GVD
	vertex = GeomVertexWriter(vdata, 'vertex')

	# Use the writers to add data to the container
	vertex.addData3f(1, 0, 0)
	vertex.addData3f(1, 1, 0)
	vertex.addData3f(0, 1, 0)
	vertex.addData3f(0, 0, 0)

	# Create a GeomPrimitive object to use the vertices in the GVD.
	prim = GeomTriangles(Geom.UHStatic)

	prim.addVertex(0)
	prim.addVertex(1)
	prim.addVertex(2)
	prim.closePrimitive()

	geom = Geom(vdata)
	geom.addPrimitive(prim)

	node = GeomNode('gnode')
	node.addGeom(geom)

	return node

My Calling Code is :


		gNode = CreateGridNode()
		self.mGridNodePath = render.attachNewNode(gNode)

The Error Message I Get Is :


Assertion failed: primitive->check_valid(cdata->_data) at line 352 of c:\temp\mkpr\panda3d-1.2.3\panda\src\gobj\geom.cxx
Traceback (most recent call last):
  File "GeomTest.py", line 69, in ?
    world = World()
  File "GeomTest.py", line 42, in __init__
    gNode = CreateGridNode()
  File "H:\CodeBase\Research\Python\Grid\Grid.py", line 48, in CreateGridNode
    geom.addPrimitive(prim)
AssertionError: primitive->check_valid(cdata->_data) at line 352 of c:\temp\mkpr\panda3d-1.2.3\panda\src\gobj\geom.cxx

I came up with this tile code, it takes a path to a heightmap and generates a tile based on it.


from pandac.PandaModules import *

class TileNode(PandaNode):
    
    def __init__(self,
                 x,
                 y,
                 heightMapFilename,
                 scale,
                 height,
                 width):
        PandaNode.__init__(self,"tile")
        

        self.x = x
        self.y = y
        self.scale = scale
        self.height = height
        self.width = width
        
        self.heightMap = PNMImage.PNMImage()
        self.heightMap.read(Filename(heightMapFilename))
        self.addChild(self.makeTile(x, y, height, width, scale, self.heightMap))
        
    def makeTile(self,
                 startx,
                 starty,
                 height,
                 width,
                 scale,
                 heightMap):
        
        squareGN=GeomNode("quad")
        for x in range(startx, width, scale):
            for y in range(starty, height, scale):
                dx = x + scale
                dy = y + scale
                #create the vertex data format
                format=GeomVertexFormat.getV3t2()
                #vertex data object
                vdata=GeomVertexData("vertices", format, Geom.UHStatic)
                #writers for data
                vertexWriter=GeomVertexWriter(vdata, "vertex")
                texWriter=GeomVertexWriter(vdata, "texcoord")
                #write the vertex and uv at the same time
                vertexWriter.addData3f(x,y,heightMap.getBright(x,y))
                texWriter.addData2f(float( x)/float(width), float(y)/float(height))  
                vertexWriter.addData3f(dx,y,heightMap.getBright(dx,y))
                texWriter.addData2f(float(dx)/float(width),float(y)/float(height)) 
                vertexWriter.addData3f(dx,dy,heightMap.getBright(dx,dy))
                texWriter.addData2f(float(dx)/float(width),float(dy)/float(height)) 
                vertexWriter.addData3f(x,dy,heightMap.getBright(x,dy))
                texWriter.addData2f(float(x)/float(width), float(dy)/float(height))

                tris=GeomTriangles(Geom.UHStatic)
                
                tris.addVertex(0)
                tris.addVertex(1)
                tris.addVertex(3)
                
                tris.closePrimitive()
                
                tris.addVertex(1)
                tris.addVertex(2)
                tris.addVertex(3)
                tris.closePrimitive()
                
                squareGeom=Geom(vdata)
                squareGeom.addPrimitive(tris)
                
                squareGN.addGeom(squareGeom)
        
        return squareGN

This is the function that is failing in C++ when called from addPrimative


////////////////////////////////////////////////////////////////////
//     Function: GeomPrimitive::check_valid
//       Access: Published
//  Description: Verifies that the primitive only references vertices
//               that actually exist within the indicated
//               GeomVertexData.  Returns true if the primitive
//               appears to be valid, false otherwise.
////////////////////////////////////////////////////////////////////
bool GeomPrimitive::
check_valid(const GeomVertexData *vertex_data) const {
  return get_num_vertices() == 0 ||
    get_max_vertex() < vertex_data->get_num_rows();
}

There appears to be something screwy with your primative data. See what kind of results you get by debugging the primative


print prim.getNumPrimatives()
print prim.getMaxVertex()
...
	# ...
	print "NPrim : ",prim.getNumPrimitives()
	print "MxVert : ",prim.getMaxVertex()

> NPrim : 3
> MxVert : 6

Interesting … I created one primitive and I actually have 3 primitives. I added 4 vertices and I actually have 6 vertices.

I guess the last thing to check is vdata.getNumRows(), cause something seems weird about that data.

[edit] This works …


def CreateGridNode2() :

	# STEP 1: Define the vertex format (GVF). This simply describes the
	# vertex data we are gonna create in the next step as 3 element
	# position data only. The call to format.getNumArrays() should 
	# return 1 indicating 1 column.
	format = GeomVertexFormat.getV3()
	print "format.getNumArrays() : ",format.getNumArrays()

	# STEP 2: Create the vertex data container (GVD) using the GVF from
	# above to  describe its contents. Until we add vertices, the call to
	# vdata.getNumRows() should return 0 and vdata.getNumArrays() should
	# return 1 since the format describes position only.
	vdata = GeomVertexData('GridVertices',format,Geom.UHStatic)
	print "vdata.getNumRows() : ",vdata.getNumRows()
	print "vdata.getNumArrays() : ",vdata.getNumArrays()

	# STEP 3: Create a writer for the GVD that will allow us to write the
	# vertex positional data to the single column in the GVD.
	vWriter = GeomVertexWriter(vdata, 'vertex')

	# Use the writers to add data to the GVD. After each vertex is written
	# then call to vdata.getNumRows() should increment by 1.
	vWriter.addData3f(1, 0, 0)
	print "Add vertex, vdata.getNumRows() : ",vdata.getNumRows()
	vWriter.addData3f(1, 1, 0)
	print "Add vertex, vdata.getNumRows() : ",vdata.getNumRows()
	vWriter.addData3f(0, 1, 0)
	print "Add vertex, vdata.getNumRows() : ",vdata.getNumRows()
	vWriter.addData3f(0, 0, 0)
	print "Add vertex, vdata.getNumRows() : ",vdata.getNumRows()

	# Create a GeomPrimitive object to use the vertices in the GVD.
	prim = GeomTriangles(Geom.UHStatic)

	prim.addVertex(0)
	prim.addVertex(1)
	prim.addVertex(2)
	prim.closePrimitive()

	# ...
	print "NPrim : ",prim.getNumPrimitives()
	print "MxVert : ",prim.getMaxVertex()
	print "NRow : ",vdata.getNumRows()

	geom = Geom(vdata)
	geom.addPrimitive(prim)

	node = GeomNode('gnode')
	node.addGeom(geom)

	return node

Thanks!

Ok, I got it. Now it works absolutely perfectly … I have posted the code and the test driver for it in Code Snippets @ discourse.panda3d.org/viewtopic.php?p=6903#6903

Thanks Guys!
Paul

Do you know what was going wrong before? I don’t see it–in fact, I paste in your sample code with the one triangle, and it appears to work fine.

I’d like to convince myself there isn’t a bug in Panda somewhere.

David

Be convinced … As far as I can tell, Panda3D is flawless … explanation is somewhat complicated …

I have a high end PC here with no networking capability (e.g. no internet) and a low end PC that is on the net. Through a KVMA switch I toggle between the two. I swap material between them using a 1GB thumb drive. Somewhere along the way, what I posted and what I was running got out of synch … the problem was a row indexing error similar to the following that did not show up in what I posted.


   vertex.addData3f(1, 0, 0) 
   vertex.addData3f(1, 1, 0) 
   vertex.addData3f(0, 1, 0) 
   vertex.addData3f(0, 0, 0) 

   # Note : 4 vertices added, indices are 0,1,2,3

   prim.addVertex(2) 
   prim.addVertex(4) <= Ouch!
   prim.addVertex(3) 

Really, I don’t think I have detected a bug in Panda yet. Apparently the assertions are doing exactly what they were designed to do. Great work :slight_smile:

Nice pleopard.

Did you guys see this code:

discourse.panda3d.org/viewtopic.php?t=1200

Renders large heightfields and textures from file. It needs work, but it has some promise, if anyone had the time to finish it off.