MeshDrawer code snippet? [solved]

Does anyone have any code snippets demonstrating how to use MeshDrawer? I could really use an efficient particle system, but I can’t seem to find any examples at all that work with the built-in MeshDrawer class. Thanks in advance.

MeshDrawer is not more efficient, and its not really a particle system.

Its a way to draw meshes in python fast.

check out the example here
github.com/treeform/meshDrawer

you can also commit any changes you make back

Thanks for the link! I’m currently using plain old NodePaths which are set up as billboards, because I need more control over each particle than what the built-in particle system can offer. So MeshDrawer is almost certainly faster than what I’m using, anyway. :slight_smile:

Thanks again!

Yeah i used all billboard appreach too and it became too slow. You will find this method much faster, you just have to make gridded textures now.

Awesome, this has improved drastically improved performance! I ran into one problem, though: the particles disappear when viewed from certain angles. I turned off depth testing and put the generator node in a fixed render bin, but the problem persists. Is Panda3D doing some kind of occlusion culling?

Sounds like view-frustum cull. You’ll need to put an explicit bounding volume on your particle node, that’s large enough to include all of your particles.

particles.node().setBounds(BoundingSphere((0, 0, 0), 100))
particles.node().setFinal(True)

The setFinal() call tells Panda that the bounding volume you assign is definitive and applies to all sub-nodes too.

David

Thank you, that took care of it! I’ll post screenshots and source as soon as I can.

Bringing up this thread because I have trouble with MeshDrawer.
Just want to know if it’s me or the system.

  • Transparency
    Usually when I need transparency I use loadTexture(“texture_color”,“texture_alpha”). The manual and people in the forums encourage users to do so. However, in MeshDrawer the color = Vec4(0,0,0,alpha) does not seem to take effect, it depends only on the transparency of the texture.

  • blendedParticle
    I see no difference in using values between 0 and 1.0 for the blend value. The texture always looks like a 50/50 mix of the two texture areas specified.

  • random plate picks
    In the past, using plate numbers, it was easier to pick out a random plate from multiple lines. You would just use (181,207), as treeform did, to start picking in the middle of a line to the beginning of the next line. That is very hard to reproduce now.

Here is treeform’s sample that works with the .png file found under the link he posted earlier in the thread. I added some comments.

"""
    This example show how to use MeshDrawer to draw
    on the screen in any way shape or form you want

"""
import direct.directbase.DirectStart
from pandac.PandaModules import *
from random import *
from math import *

maxParticles = 400 # max number of triangles we will display, 2x 100 quads = 400 tris
#by = 16 # not in 1.7.0+

generator = MeshDrawer()
generator.setBudget(maxParticles)
#generator.setPlateSize(by) # not in 1.7.0+
generatorNode = generator.getRoot()
generatorNode.reparentTo(render)
generatorNode.setDepthWrite(False)
generatorNode.setTransparency(True)
generatorNode.setTwoSided(True)
generatorNode.setTexture(loader.loadTexture("radarplate.png"))
generatorNode.setBin("fixed",0)
generatorNode.setLightOff(True)

# load some thing into our scene
base.setFrameRateMeter(True)
base.setBackgroundColor(.1,.1,.1,1)
t = loader.loadModel('teapot')
t.reparentTo(render)
t.setPos(0,0,-1)

# very usefull function
def randVec():
    return Vec3(random()-.5,random()-.5,random()-.5)


#seed(1988)  # random seed - remove if you always want different random results

# store data for 100 particles
particles = []
for i in range(100):
    p = [randVec()*1, randVec()*100,Vec4(randint(0,15)/16.0,randint(3,4)/16.0,1/16.0,1/16.0),1,Vec4(random(),random(),random(),1)]
    particles.append(p)
"""
In the past you would state the plate number, now this is gone. You use a Vec4 format: Vec4(x,y,width,height), starting at the bottom left. Textures have to be square. The bottom left quadrant of a 2x2 texture would be Vec4(0,0,0.5,0.5), the top right would be Vec4(0.5,0.5,0.5,0.5).
 In this code, randint(0,15)/16.0 picks a random of 16 columns, randint(3,4) picks line 4 or 5 from the bottom.
"""
# store data for 100 lines
lines = []
for i in range(100):
    l = [randVec()*100,randVec()*100,Vec4(11/16.0,4/16.0,1/16.0,1/16.0),.1,Vec4(random(),random(),random(),1)]
    lines.append(l)

# In the radarplate.png, which is 16x16 fields, we want to use the 12. field in the 12. row for a constantly transparent texture.

def drawtask(taks):
    """ this is called every frame to regen the mesh """
    t = globalClock.getFrameTime()
    generator.begin(base.cam,render)
    for v,pos,frame,size,color in particles:        
        generator.billboard(pos+v*t,frame,size*sin(t*2)+3,color)
        # the v*t value is used to move the billboards slowly over time
        # sin is used to grow and shrink the size over time

    for start,stop,frame,size,color in lines:
        generator.segment(start,stop,frame,size*sin(t*3)+2,color)
    generator.end()
    return taks.cont

# add the draw task to be drawn every frame
taskMgr.add(drawtask, "draw task")

# run the sample
run()

Another sample that show how to a segment from one nodepath to another, looking like a beam weapon that pinpoints the target even if it moves.
Note that I use tabs in the code for indentation, not spaces.

self.accept("shift", self.handleShift, [1])
self.accept("shift-up", self.handleShift, [0])
		
def handleShift(self, arg):
	if arg == 1:
		source = SomeNodepath
		target = AnotherNodepath
		self.createBeam(source, target)
	else:
		self.deleteBeam()

def createBeam(self, source, target):
	self.beammd = MeshDrawer()
	self.beammd.setBudget(2)
	self.mdbeamgennode = self.beammd.getRoot()
	self.mdbeamgennode.reparentTo(render)
	beamtex = loader.loadTexture("../beam_color.png","../beam_alpha.png")
	self.mdbeamgennode.setTexture(beamtex)
	self.mdbeamgennode.setTransparency(True)
	self.mdbeamgennode.setPos(0,0,0)
	self.mdbeamgennode.setDepthWrite(False)
	self.mdbeamgennode.setTwoSided(True)
	self.mdbeamgennode.setLightOff(True)
	
	taskMgr.add(self.drawBeam, "drawBeamTask", extraArgs=[source,target], appendTask=True)

def drawBeam(self, source, target, task):
	t = globalClock.getFrameTime()
	self.beammd.begin(base.cam,render)
	# MeshDrawer expects Vec3(x,y,z), but getPos() returns a VBase3, that's why we use Vec3(object.getPos()) to convert the format. self.beammd.segment(Vec3(source.getPos()),Vec3(target.getPos()),Vec4(0,0,1.0,1.0),0.05*sin(t*20)+0.1,Vec4(0.2,1.0,1.0,1.0))
	# segment: start, stop, frame, size, color
	self.beammd.end()
	return task.cont
	
def deleteBeam(self):
	self.mdbeamgennode.removeNode()
	taskMgr.remove("drawBeamTask")

Here’s my MeshDrawer setup code:

ParticleGroup.frames.append(Vec4(0, 0.6666, 0.3333, 0.3333)) # Smoke
ParticleGroup.frames.append(Vec4(0.3333, 0.6666, 0.3333, 0.3333)) # Highlight
ParticleGroup.frames.append(Vec4(0.6666, 0.6666, 0.3333, 0.3333)) # Damage
ParticleGroup.frames.append(Vec4(0, 0.3333, 0.3333, 0.3333)) # Crosshair
ParticleGroup.generator = MeshDrawer()
ParticleGroup.generator.setBudget(5000)
ParticleGroup.generatorNode = ParticleGroup.generator.getRoot()
ParticleGroup.generatorNode.reparentTo(render)
ParticleGroup.generatorNode.setDepthWrite(False)
ParticleGroup.generatorNode.setTransparency(True)
ParticleGroup.generatorNode.setTwoSided(False)
ParticleGroup.generatorNode.setTexture(loader.loadTexture("maps/plate.png"))
ParticleGroup.generatorNode.setBin("fixed", 100)
ParticleGroup.generatorNode.setLightOff(True)
ParticleGroup.generatorNode.node().setBounds(BoundingSphere((0, 0, 0), 500)) 
ParticleGroup.generatorNode.node().setFinal(True)

Where ParticleGroup.frames is a list of UV coordinates. Then to get a random image, you just pass random.choice(ParticleGroup.frames) as the UV coordinates.

  • Transparency, is used like any other panda3d node. Nothing mesh drawer specific here.

  • blendedParticle - might be a bug I will take a look.

  • random plate picks - just do a little math, its not hard at all but the new method is far more flexible because your images might differ in size. Do it like et1337 did.

The beam example above is exactly what I’m trying to do, so I added some panda models and a beam.tga file I had, but when I run the code below - I don’t see anything when I hold down shift - am I missing something? (most of the code is the beam code above).

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from direct.task import Task
from direct.actor.Actor import Actor
from pandac.PandaModules import *

class world(DirectObject.DirectObject):
	def __init__(self):
		self.accept("shift", self.handleShift, [1]) 
		self.accept("shift-up", self.handleShift, [0]) 
		
		self.panda1 = Actor("models/panda-model",{"walk": "models/panda-walk4"})
		self.panda1.setScale(0.005, 0.005, 0.005)
		self.panda1.setPos(-8, 42, 0)
		
		self.panda2 = Actor("models/panda-model",{"walk": "models/panda-walk4"})
		self.panda2.setScale(0.005, 0.005, 0.005)
		self.panda2.setPos(8, 42, 0)
		
		self.panda1.reparentTo(render)
		self.panda2.reparentTo(render)

	def handleShift(self, arg):
		if arg == 1: 
			source = self.panda1
			target = self.panda2
			self.createBeam(source, target) 
		else:
			self.deleteBeam() 

	def createBeam(self, source, target): 
		self.beammd = MeshDrawer() 
		self.beammd.setBudget(2) 
		self.mdbeamgennode = self.beammd.getRoot() 
		self.mdbeamgennode.reparentTo(render) 
		beamtex = loader.loadTexture("beam.tga")
		self.mdbeamgennode.setTexture(beamtex) 
		self.mdbeamgennode.setTransparency(True) 
		self.mdbeamgennode.setPos(0,0,0) 
		self.mdbeamgennode.setDepthWrite(False) 
		self.mdbeamgennode.setTwoSided(True) 
		self.mdbeamgennode.setLightOff(True) 
		taskMgr.add(self.drawBeam, "drawBeamTask", extraArgs=[source,target], appendTask=True) 

	def drawBeam(self, source, target, task): 
		t = globalClock.getFrameTime() 
		self.beammd.begin(base.cam,render) 
		# MeshDrawer expects Vec3(x,y,z), but getPos() returns a VBase3, that's why we use Vec3(object.getPos()) to convert the format. self.beammd.segment(Vec3(source.getPos()),Vec3(target.getPos()),Vec4(0,0,1.0,1.0),0.05*sin(t*20)+0.1,Vec4(0.2,1.0,1.0,1.0)) 
		# segment: start, stop, frame, size, color 
		self.beammd.end() 
		return task.cont 

	def deleteBeam(self): 
		self.mdbeamgennode.removeNode() 
		taskMgr.remove("drawBeamTask") 

# run the sample
g = world()
run()

Well first of all, you shouldn’t need to create the MeshDrawer every time you hit shift. Just create it once and in createBeam just save the source and target values. Then in your drawBeam function, if the source and target values are not None, draw the beam. You would always call MeshDrawer.begin and end, though.

Also, I can’t tell right away, but it looks like you’re using local variables in handleShift for “source” and “target”. Change those to “self.source” and “self.target” in all instances, and that should help fix the problem.

[edit: Whoops, looks like your way should work. I still recommend making the above change though.]

Thx for answering.

At what point (Where) would I create the meshdrawer object?

Like I said, this isn’t my code, I just added the pandas and world class to test it - and I have no idea how to use meshdrawer yet. I was hoping this example would get me started on undersdtanding this.

Here, I think I got it working. Try this:

import direct.directbase.DirectStart 
from direct.showbase import DirectObject 
from direct.task import Task
from direct.actor.Actor import Actor
from pandac.PandaModules import *

class world(DirectObject.DirectObject):
	def __init__(self):
		self.accept("shift", self.handleShift, [1])
		self.accept("shift-up", self.handleShift, [0])

		self.panda1 = Actor("models/panda-model",{"walk": "models/panda-walk4"})
		self.panda1.setScale(0.005, 0.005, 0.005)
		self.panda1.setPos(-8, 42, 0)

		self.panda2 = Actor("models/panda-model",{"walk": "models/panda-walk4"})
		self.panda2.setScale(0.005, 0.005, 0.005)
		self.panda2.setPos(8, 42, 0)

		self.panda1.reparentTo(render)
		self.panda2.reparentTo(render)
		self.beammd = MeshDrawer()
		self.beammd.setBudget(20)
		self.mdbeamgennode = self.beammd.getRoot()
		self.mdbeamgennode.reparentTo(render)
		beamtex = loader.loadTexture("beam.tga") 
		self.mdbeamgennode.setTexture(beamtex) 
		self.mdbeamgennode.setTransparency(True)
		self.mdbeamgennode.setPos(0,0,0)
		self.mdbeamgennode.setDepthWrite(False)
		self.mdbeamgennode.setTwoSided(True)
		self.mdbeamgennode.setLightOff(True)
		self.mdbeamgennode.setBin("fixed", 100) 
		self.mdbeamgennode.node().setBounds(BoundingSphere((0, 0, 0), 500)) 
		self.mdbeamgennode.node().setFinal(True)

		taskMgr.add(self.drawBeam, "drawBeamTask")
		# Initial values
		self.source = None
		self.target = None

	def handleShift(self, arg):
		if arg == 1:
			self.source = self.panda1
			self.target = self.panda2
		else:
			self.source = None
			self.target = None

	def drawBeam(self, task):
		t = globalClock.getFrameTime()
		self.beammd.begin(base.cam, render)
		
		# All particle drawing operations have to go here in between begin() and end()
		
		if self.source != None and self.target != None:
			# Draw the beam
			# Parameters: start position, stop position, UV coordinates, segment thickness, color
			self.beammd.segment(Vec3(self.source.getPos()), Vec3(self.target.getPos()), Vec4(0, 0, 1, 1), 0.3, Vec4(1, 1, 1, 1))

		self.beammd.end()
		return task.cont

# run the sample
g = world()
run()

Thanks! that change did the trick.