Terragen file reader

Hi all,

This class read a TERRAGEN (.ter) file and converts it to a GeomNode

that can be displayed. Furthermore, you will be able to save it to a BAM
file. The code is based on the TERRAGEN Blender import.

This is the Terrain class:

#################################################################
#																#
# Ter2Blend  v4    TERRAIN                                      #
#																#
# Terragen's terrain file to Blender file converter				#
#																#
# copyright (c) 2003  Guy Van Rentergem 						#
# 																#
# email: guy.van.rentergem@skynet.be		                	#
#																#
#																#
# Script only works with Blender 2.28c or later and				#
# a Python 2.2.2												#
#																#

# Terragen stuff
# --------------
#
# standard terragen terrains can have the following sizes:
#   129 * 129 =    16641 vertices
#   257 * 257 =    66049
#   513 * 513 =   263169
# 1025 * 1025 =  1050625
# 2049 * 2049 =  4198401
# 4097 * 4097 = 16785409 (will blow the memory out of your computer!)
#
# Due to memory and speed considerations let's use only one mesh and
# keep the maximum number of vertices to 65535 (Blender limit)
# So to keep things simple we will only allow terrains up 257 * 257
# This means terrains of 129 * 129 and 257 * 257 (this one already needs some clipping...)
# Later we will do some coding so the mesh can be divided in pieces and all
# kind of formats will be allowed, but then a powerfull machine is needed.
#
# default settings in Terragen: sun heading -60 degrees (from north)
#							    sun altitude 25 degrees from groundlevel 
#
# in Terragen the (0,0) is located in the left bottom corner
#
# important: USE ONLY TERRAIN UNITS FROM TERRAGEN
#
# Blender camera lens 22.70 seems to fit the standard lens of Terragen

# Terragen script file
# --------------------
#
# script commands usefull for Ter2Blend:
#   InitAnim BaseName, StartFrame - only use for StartFrame, the first frame to rendered
#   CamPos x,y,z - sets the camera's position in Terrain Units
#   TarPos x,y,z - sets the target's position in Terrain Units
#   CamH head - sets the camera's heading in degrees
#   CamP pitch - sets the camera's pitch in degrees
#   CamB bank - sets the camera's bank in degrees
#   SunDir head, alt - sets the heading and altitude of the sun in degrees (for later...)
#   FRend - renders a frame
#
# Forbidden script commands in Ter2Blend:
#   Zoom z - sets the Zoom value
#   Exp e - sets the Exposure value
#
# Useless script commands in Ter2Blend:
#   CloudPos x,y
#   CloudVel Speed, Heading



# Import Panda3D modules
from pandac.PandaModules import * 

import struct


"""
This class imports a terrain from the TERRAGEN tool. File with
extension '.ter'
This code is based on the blender import for TERRAGEN terrain files
"""
class Terrain:
    def __init__(self):
        self.terrainGN = None
        
    def load(self, iFileName):
        """This method reads a terrain file and returns a Panda node
        """
        if iFileName == "":
            print "Empty file name provided"
            return None
        
        # Open the file
        try:
            ter = open(iFileName, 'rb')
        except IOError:
            print "Unable to open %s file" % (iFileName)
            return None
            
        # Process the file
        
        # Read the header
        if ter.read(8) == 'TERRAGEN':
            if ter.read(8) == 'TERRAIN ':
                print 'Terragen terrain file: continue'
            else:
                print "TERRAIN keyword not found"
                return None
        else:
            print "TERRAGEN keyword not found"
            return None

        keys=['SIZE','XPTS','YPTS','SCAL','CRAD','CRVM','ALTW']

        totest = ter.read(4)
              
        while 1:
            if totest in keys:
                if totest == 'SIZE':
                    (size,) = struct.unpack('h',ter.read(2))
                    garbage = ter.read(2)

                if totest == 'XPTS':
                    (xpts,) = struct.unpack('h',ter.read(2))
                    garbage = ter.read(2)

                if totest == 'YPTS':
                    (ypts,) = struct.unpack('h',ter.read(2))
                    garbage = ter.read(2)

                if totest == 'SCAL':
                    (scalx,) = struct.unpack('f',ter.read(4))
                    (scaly,) = struct.unpack('f',ter.read(4))
                    (scalz,) = struct.unpack('f',ter.read(4))

                if totest == 'CRAD':
                    (crad,) = struct.unpack('f',ter.read(4))

                if totest == 'CRVM':
                    (crvm,) = struct.unpack('H',ter.read(2))
                    garbage = ter.read(2)

                if totest == 'ALTW':
                    (heightscale,) = struct.unpack('h',ter.read(2))
                    (baseheight,) = struct.unpack('h',ter.read(2))
                    break
                
                totest = ter.read(4)	
            else:
                break

        # print "xpts = %d   ypts = %d" % (xpts, ypts)
        if xpts==0:
            xpts = size+1
        if ypts==0:
            ypts = size+1
            
        # Print some debug information
        print "size         = %d" % (size)
        print "xpts         = %d" % (xpts)
        print "ypts         = %d" % (ypts)
        print "scalx        = %d" % (scalx)
        print "scaly        = %d" % (scaly)
        print "scalz        = %d" % (scalz)
        print "crad         = %d" % (crad)
        print "crvm         = %d" % (crvm)
        print "heightscale  = %d" % (heightscale)
        print "baseheight   = %d" % (baseheight)


        # Read all the points
        points = []
        
        # Create node        
        #step 1) create GeomVertexData and add vertex information
        format=GeomVertexFormat.getV3()
        vdata=GeomVertexData("vertices", format, Geom.UHStatic)
        
        vertexWriter=GeomVertexWriter(vdata, "vertex")
        
        # read them all...
        for y in range(0,ypts):
            for x in range(0,xpts):
                (h,) = struct.unpack('h',ter.read(2))
                
                z = 0.0
                z = baseheight + h*heightscale/65536.0
 
                #print "x = %f  y = %f  z = %f" % (x, y, z)
                vertexWriter.addData3f(x*scalx, y*scaly, z*scalz)
                
                xmax=x+1
                ymax=y+1
        ter.close() 

        # Create triangles
        # ----------------

        #step 2) make primitives and assign vertices to them
        tris=GeomTriangles(Geom.UHStatic)

        for y in range(0,ymax-1):
            for x in range (0,xmax-1):
                a=x+y*(ymax)
                ## f=NMesh.Face()
                ## f.v.append(terrainName.verts[a])
                ## f.v.append(terrainName.verts[a+ymax])
                ## f.v.append(terrainName.verts[a+ymax+1])
                ## f.v.append(terrainName.verts[a+1])		
                ## terrainName.faces.append(f)
                
                # TERRAGEN defines squares, so, we are going to divide them
                # into two triangles

                # First triangle
                tris.addVertex(a)
                tris.addVertex(a + ymax)
                tris.addVertex(a + ymax + 1)

                #indicates that we have finished adding vertices for the first triangle.
                tris.closePrimitive()

                # Second triangle
                tris.addVertex(a + ymax + 1)
                tris.addVertex(a + 1)
                tris.addVertex(a)

                #indicates that we have finished adding vertices for the first triangle.
                tris.closePrimitive()
                

        #step 3) make a Geom object to hold the primitives
        terrainGeom = Geom(vdata)
        terrainGeom.addPrimitive(tris)

        #now put squareGeom in a GeomNode. You can now position your geometry 
        # in the scene graph.
        self.terrainGN=GeomNode("terrain")
        self.terrainGN.addGeom(terrainGeom)

        return self.terrainGN

    def getGeomNode(self):
        return self.terrainGN
    
    def writeToFile(self, iFileName):
        if iFileName == "":
            print "Empty file name provided"
            return
        
        if self.terrainGN != None:
            # Convert the node from GeomNode to NodePath
            np = NodePath(self.terrainGN)
            
            # Write to a bam file
            np.writeBamFile(iFileName)
            
        return
 

And, this is a small example of usage:

import direct.directbase.DirectStart

# Import Panda3D modules
from pandac.PandaModules import * 

from terrain import Terrain

tn = Terrain()
newNode = tn.load("prueba.ter")

base.camera.setPos(100, -30, 50)

render.attachNewNode(newNode)

tn.writeToFile("prueba.bam")

run()

I hope it will be useful.

Regards.

Alberto
[/code]

Man that looks awesome and I would absolutely love to tinker with it. I created a .ter file and tried to load it and it failed though. Here is the error I get, ani diea what the problem is?
knology.net/~pleopard/Terragen/Terragen.jpg

Here is the .ter file I created : knology.net/~pleopard/Terrag … errain.ter

Thanks man,
Paul

Ah, I recognize that error message. That happens when you try to create a single GeomTriangles object that references more than 65,536 vertices at once (i.e. it contains more than 21845 triangles).

Two possibile solutions:
(1) make a smaller mesh, or
(2) modify the code above to replace:

tris=GeomTriangles(Geom.UHStatic)

with:

tris=GeomTriangles(Geom.UHStatic)
tris.setIndexType(Geom.NTUint32)

This change may adversely affect render performance a bit on some hardware, so ideally it would only do this if the number of vertices demanded it.

Incidentally, future versions of Panda will set this wide index type automatically when necessary.

David

That did the trick David, Thanks!

how can I set an avatar on this chat board??

Hello,

First at all, this importer will only read the file and create a GeomNode.

So basically, it performs the same function as loadModel(). What you
obtain is a ‘mesh’.

For placing an actor/avatar on it, you must set up a vertical ray and

look for the collision point between the vertical ray and the GeomNode.
That will give you the Z value of your actor/avatar.

I recommend you to do a search in forums on how to place an actor

over the ground or a mesh.

I hope this help.

Alberto

something is telling me that this guy asked how to add a avatar-image in this forum :unamused: judging from the links hidden in his points… well. never mind.

Yeah. That was a spam post. It was from one of the sneakier spambots, which tries to pose as a newbie asking a typical forum question, but the post itself contains several hidden link to the spammer’s website.

The spammer is presumably hoping to drive up link counts to his website, to increase his Google ranking.

Rather than deleting the post algother, which would just make the following posts make no sense at all, I edited the post to remove the links.

David

Our spammer got what he deserved :slight_smile: - look at his rank XD

Regards, Bigfoot29