A Great LODNode Example

Here’s a great example of using LOD nodes. I cannot post the models I used from here at work since FTP is disallowed by our firewall. I will post the models in ZIP format to my website tonight. Thanks once again to David for helping me understand the concept (discourse.panda3d.org/viewtopic.php?t=2623).

This example is simple. I create 4 torus models as follows:

ID  Facets  TexSize  Filename               LOD-Range
1   32x256   1024    Torus_32_256.bam.pz   0-2
2   16x128   512     Torus_16_128.bam.pz   2-3
3     8x64    56     Torus_08_064.bam.pz   3-4
4     4x32    64     Torus_04_032.bam.pz   4-inf

Then I create a single NodePath that handles all four of these models and switches them in and out according to the specified LOD range. All you do is use the trackball to zoom in/out and watch the different resolution models being switched. Use the ‘w’ key to toggle wireframe on and off. Look at the wireframe to easily see the geometry differences, shaded to seel the texture differences.

Enjoy!
Paul

# LODNodeTest1.py

""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Description: DESCRIPTION_GOES_HERE

	$Author: pleopard $

	$Modtime: 04/17/07 1:15p $

	$Revision: 1 $ 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """

# Standard imports

import sys

# 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 *

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

def CreateTextLabel(\
		text,
		color,
		i,
		xStart=-1.3,
		yStart=0.95,
		yOffset = 0.1,
		tFont=None
	):
	if tFont==None:
		return OnscreenText(\
				text = text,
				pos = (xStart, yStart-yOffset*i),
				fg=color,
				mayChange = True,
				align=TextNode.ALeft
			)
	else:
		return OnscreenText(\
				text = text,
				pos = (xStart, yStart-yOffset*i),
				fg=color,
				mayChange = True,
				align=TextNode.ALeft,
				font=tFont
			)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

class World(DirectObject):

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def __init__(self):

		# *** Setup text displays
		textXStart=-1.3
		textYStart=0.95
		textYOffset=0.1
		self.mTitleDisplay = \
			CreateTextLabel(
				"LOD Node Test",
				(1.0,1.0,0.0,1.0),
				1,
				textXStart,
				textYStart,
				textYOffset
			)
		self.mMessageDisplay = \
			CreateTextLabel(
				'> ',
				(0.0,1.0,0.0,1.0),
				2,
				textXStart,
				textYStart,
				textYOffset
			)

		# *** Setup world dimensions
		maxWorldDim = 1000
		worldDims = [maxWorldDim,maxWorldDim,maxWorldDim]
		base.camLens.setNearFar(0.1,2.5*maxWorldDim)

		# *** Setup lighting
		lightLevel=0.7
		lightPos=(0.0,-10.0,10.0)
		lightHpr=(0.0,-26.0,0.0)
		dlight = DirectionalLight('dlight')
		dlight.setColor(VBase4(lightLevel, lightLevel, lightLevel, 1))
		dlnp = render.attachNewNode(dlight.upcastToPandaNode())
		dlnp.setHpr(lightHpr[0],lightHpr[1],lightHpr[2])
		dlnp.setPos(lightPos[0],lightPos[1],lightPos[2])
		render.setLight(dlnp)
 
		alight = AmbientLight('alight') 
		alight.setColor(VBase4(0.2, 0.2, 0.2, 1)) 
		alnp = render.attachNewNode(alight.upcastToPandaNode()) 

		# *** Setup scene
		base.setBackgroundColor(0.0,0.1,0.7,1.0)

		# *** Setup events
		self.setupKeyBindings()

		# *** Setup models
		lodNode = NodePath(FadeLODNode('lod'))
		lodNode.reparentTo(render)

		lod0 = loader.loadModel("Torii/Torus_04_032")
		lod0.reparentTo(lodNode)
		lodNode.node().addSwitch(999999, 4)

		lod1 = loader.loadModel("Torii/Torus_08_064")
		lod1.reparentTo(lodNode)
		lodNode.node().addSwitch(4, 3) 

		lod2 = loader.loadModel("Torii/Torus_16_128")
		lod2.reparentTo(lodNode)
		lodNode.node().addSwitch(3, 2) 

		lod3 = loader.loadModel("Torii/Torus_32_256")
		lod3.reparentTo(lodNode)
		lodNode.node().addSwitch(2, 0) 

		# *** Done, setup time update task
		taskMgr.add(self.timeUpdate,'TimeUpdate')

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	#
	def toggleWireFrame(self):
		base.toggleWireframe()

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	#
	def toggleTexture(self):
		base.toggleTexture()

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	def snapShot(self):
		base.screenshot("Snapshot")

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def timeUpdate(self,task):
		cPos = base.camera.getPos()
		s = "Camera Pos : %12.4f %12.4f %12.4f" % (cPos[0],cPos[1],cPos[2])
		self.mMessageDisplay.setText(s)
		return Task.cont

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	#
	# Method name : setupKeyBindings
	#
	# Description:
	#
	#	Load and register key bindings
	#
	# Input(s):
	#
	#	None
	#
	# Output(s):
	#
	#	None
	#
	def setupKeyBindings(self) :
		self.accept('p',self.snapShot)
		self.accept('w',self.toggleWireFrame)
		self.accept('t',self.toggleTexture)
		self.accept('escape',sys.exit)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

world = World()

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Run the program

run()

A suggestion: post the egg files in .egg format (or .egg.pz format) instead of .bam.pz format. This is always a good idea when you’re not sure which version of Panda the user of your code will be running.

David

Will do, thx :slight_smile:

Models are here : knology.net/~pleopard/Torii.zip

Just wanted to say,

Good work on this code. Thanks for providing it.

Thanks!

Link is broken now since I changed ISPs to Bellsouth. When I get a chance to setup access to ftp on Bellsouth I will repost the ZIP file.

–Paul

since the link is still broken I posted a new release on my website with different objects but almost the same code
grab it here and enjoy

Thanks, I like the monkey too :slight_smile:

Anyone else find setting up the LOD system confusing?

I expected to be able to the following:

lodNode = LODNode( "LODTest" )
lowQualityVisuals = loader.loadModel( "low" )
mediumQualityVisuals = loader.loadModel( "medium" )
highQualityVisuals = loader.loadModel( "high" )

lodNode.addSwitch( 0.0, 50.0, highQualityVisuals )
lodNode.addSwitch( 50.0, 200.0, mediumQualityVisuals )
lodNode.addSwitch( 200.0, 1000000.0, lowQualityVisuals )

Instead there is this odd decoupling of the switches and the NodePaths that correspond to those switches. Does the order in which you call addSwitch need to correspond to the order in which you add the visuals NodePaths to the NodePath that contains the LODNode? Or, does the order the NodePaths are added correspond to furthest distance to nearest distance?

Just do this:

np = NodePath(lodNode)
lodNode.addSwitch(50.0, 0.0)
highQualityVisuals.reparentTo(np)
lodNode.addSwitch(200.0, 50.0)
mediumQualityVisuals.reparentTo(np)
lodNode.addSwitch(1000000.0, 200.0)
lowQualityVisuals.reparentTo(np)

See, it’s not really that confusing. Just call reparentTo() in the same order that you call addSwitch(). If you interleave the calls like I did in the above, it’s even clearer.

The relative order from high-to-low means nothing. You can put them in either order, or in random order, as long as the calls to addSwitch() are in the same order as the calls to reparentTo().

Note that this decoupling gives you a lot more power over the LOD structure than if it were more tightly coupled. For instance, you could write some code to operate on one particular LOD of the node without having to require special support–the full set of ordinary scene graph operations are all available to you.

There is one thing about the LODNode which I find annoying, though, and which bites everyone: the order of the parameters in the addSwitch() call is contrary to intuition. The larger number should go first.

David

Thank you to pleopard and drwr for this informative thread.
I have added the relevant information to the manual:
http://www.panda3d.org/manual/index.php/Level_of_Detail
If someone would be so kind as to contribute the C++ version of the 5 lines of example code, it will be complete.