Yes, this uses some classes that are not included here but you should still be able to see how to use HeightFieldTesselator by perusing this code. Just know that SceneNavigator is my own class that moves the camera around in the scene FPS style. At any time you can still get the camera position and orientation using base.camera.getPos() and base.camera.getHpr().
This example renders a texture mapped, lit terrain model. Instead of re-tesselating each frame, I re-tesselate every ‘self.mTerrainUpdateInterval’ seconds.
Next update will include multitexturing.
Enjoy!
# Standard imports
import sys
import math
# Panda imports
from direct.gui.OnscreenText import OnscreenText
from direct.showbase import DirectObject
from direct.showbase.DirectObject import DirectObject
import direct.directbase.DirectStart
from direct.task import Task
from pandac.PandaModules import *
from pandac.PandaModules import HeightfieldTesselator
# PHL imports
from PHL.Core.Properties import *
from PHL.P3D.Utilities import CreateTextLabel
from PHL.P3D.Shapes import P3DCreateGridXY,P3DCreateGridYZ,P3DCreateGridXZ
from PHL.P3D.TerraViz02.SceneNavigator import SceneNavigator
from PHL.Math.Bounds import BoundingVolume
from PHL.Math.Utilities import *
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
class World(DirectObject):
def getBoundingVolume(self):
return BoundingVolume(\
0,0,0,\
self.mTerrainWidth,\
self.mTerrainWidth,\
self.mTerrainHeight
)
def postStatusMessage(self,msg):
self.mTextDisplay2.setText(msg)
def toggleWireframe(self):
base.toggleWireframe()
def toggleTexture(self):
base.toggleTexture()
def joystick(self):
return None
def environmentModel(self):
return self
def __init__(self):
text = 'Panda3D HeightfieldTesselator Class Test Harness'
color = (1,1,0,1)
self.mTextDisplay1 = CreateTextLabel(text,color,1)
self.mTextDisplay2 = CreateTextLabel(' ',(0,1,0,1),2)
base.setBackgroundColor(0,0.1,0.6,1)
# Setup world size
self.mTerrainWidth = 5000
self.mTerrainHeight = 100
# Setup navigator
base.camLens.setFar(10000.0)
base.camLens.setFov(75, 95)
self.__Nav = SceneNavigator(self)
self.__Nav.setPitchGain(0.4)
self.__Nav.setYawGain(0.4)
self.__Nav.setRollGain(0.2)
self.__Nav.setMotionGain(self.mTerrainWidth*0.0005)
# Setup lighting
lightAttribute = LightAttrib.makeAllOff()
dirLight = DirectionalLight('DirLight')
dirLight.setColor(Vec4(0.6,0.6,0.6,1.0))
dirLightNP = render.attachNewNode(dirLight.upcastToPandaNode()) # crashes without upcast
dirLightNP.setPos(Vec3(0.0,-10.0,10.0))
dirLightNP.setHpr(Vec3(0.0,-26.0,0.0))
lightAttribute = lightAttribute.addLight(dirLight) # add to attribute
ambientLight = AmbientLight('ambientLight')
ambientLight.setColor(Vec4(0.25,0.25,0.25,1.0))
ambientLightNP = render.attachNewNode(ambientLight.upcastToPandaNode())
lightAttribute = lightAttribute.addLight(ambientLight)
render.node().setAttrib(lightAttribute)
# Prep terrain textures
coverTextureFile = "Dirt/Ground1.png"
self.mCoverTexture = loader.loadTexture(coverTextureFile)
Assert(\
self.mCoverTexture != None,\
"Failed loading terrain cover texture "+coverTextureFile)
# Setup heightfield
self.mHeightFieldTesselator = HeightfieldTesselator("Heightfield")
fName = "HeightField.png"
fileObj = Filename(fName)
self.mHorizontalScale = self.mTerrainWidth/2048.0
self.mVerticalScale = self.mTerrainHeight
self.mTerrainUpdateInterval = 0.1
self.mTerrainUScale = 0.001
self.mTerrainVScale = self.mTerrainUScale
self.mHeightFieldTesselator.setHeightfield(fileObj)
self.mHeightFieldTesselator.setVerticalScale(self.mVerticalScale)
self.mHeightFieldTesselator.setHorizontalScale(self.mHorizontalScale)
self.mLastTesselateTime = -1
self.mHeightFieldNode = None
self.updateHeightField()
# Setup keyboard events
self.setupKeyBindings()
# Done, setup a periodic update task
taskMgr.add(self.timeUpdate,'TimeUpdate')
# Re-tesselate the heightfield
def updateHeightField(self):
if self.mHeightFieldNode != None:
self.mHeightFieldNode.removeNode()
self.mHeightFieldNode = self.mHeightFieldTesselator.generate()
self.mHeightFieldNode.setTexGen(\
TextureStage.getDefault(),
TexGenAttrib.MWorldPosition
)
self.mHeightFieldNode.setTexture(\
TextureStage.getDefault(),
self.mCoverTexture,
1
)
self.mTerrainUScale = 0.001
self.mTerrainVScale = 0.001
self.mHeightFieldNode.setTexScale(\
TextureStage.getDefault(),
self.mTerrainUScale,
self.mTerrainVScale
);
self.mHeightFieldNode.reparentTo(render)
# Periodic update
def timeUpdate(self,task):
if (self.mLastTesselateTime == -1) or \
(task.time-self.mLastTesselateTime>self.mTerrainUpdateInterval):
self.mLastTesselateTime = task.time
cPos = base.camera.getPos()
ix = int(round(cPos[0]/self.mHorizontalScale))
iy = int(round(-cPos[1]/self.mHorizontalScale))
print task.time,ix,iy
self.mHeightFieldTesselator.setFocalPoint(ix,iy)
self.updateHeightField()
self.__Nav.tickUpdate()
return Task.cont
# Take a snapshot
def snapShot(self):
base.screenshot('Snap')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Method name : setupKeyBindings
#
# Description:
#
# Load and register key bindings
#
# Input(s):
#
# None
#
# Output(s):
#
# None
#
def setupKeyBindings(self) :
self.accept('h',self.__Nav.orientGodMode)
self.accept('p',self.snapShot)
self.accept('w',self.toggleWireframe)
self.accept('t',self.toggleTexture)
self.accept('m',self.__Nav.toggleAutoCentering)
self.accept('escape',sys.exit)
self.accept('.',self.__Nav.toggleDriving)
self.accept('n',self.__Nav.nextNavigationMode)
self.accept('+',self.__Nav.speedUp)
self.accept('-',self.__Nav.slowDown)
self.accept('home',self.__Nav.reset)
self.accept('space',self.__Nav.turboBoostOn)
self.accept('space-up',self.__Nav.turboBoostOff)
self.accept('f11',self.__Nav.toggleSuperTurboBoost)
self.accept('d',self.__Nav.drivingForwardOn)
self.accept('d-up',self.__Nav.drivingForwardOff)
self.accept('e',self.__Nav.slidingUpOn)
self.accept('e-up',self.__Nav.slidingUpOff)
self.accept('v',self.__Nav.slidingDownOn)
self.accept('v-up',self.__Nav.slidingDownOff)
self.accept('f',self.__Nav.slidingRightOn)
self.accept('f-up',self.__Nav.slidingRightOff)
self.accept('s',self.__Nav.slidingLeftOn)
self.accept('s-up',self.__Nav.slidingLeftOff)
self.accept('c',self.__Nav.drivingBackwardOn)
self.accept('c-up',self.__Nav.drivingBackwardOff)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
world = World()
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Run the program
run()