Smoke Trail Rendering
I am experimenting with several methods of rendering missile smoke trails and each of these methods has limitations or deficiencies that make them unusable. I am posting this thread in hopes that someone can enlighten me on how to improve these methods or give me another method entirely. I have create a test harness P3D application for testing the methods so I can make changes or additions and quickly assess the feasibility of potential improvements so … please feel free to brainstorm here.
Note … I have a missile model with a trajectory stored in memory as a table with interpolation capabilities. I can retrieve each discrete state stored in the table or I can specify a time and let the table class interpolate the appropriate state. Trajectory states are stored as (x,y,z,h,p,r,velocity,thrust).
The basic concept for each method is to place billboards along the trajectory and texture map transparent smoke puff images on them. I use one of two methods for placement of the billboards; ‘By Sample’ or ‘By Distance’. In the ‘By Sample’ method, I place a billboard at every ‘i’th discrete state in the table. In the ‘By Distance’ method, I place billboards at equally spaced distances along the trajectory.
I am using two different methods for representing the billboards; ‘Sprite Rendering’ and ‘NodePath Rendering’. In ‘Sprite Rendering’ I create a GeomNode containing sprites for all of the placed billboards along the trajectory. In ‘NodePath Rendering’ I create a NodePath for every placed billboard.
Now, ‘NodePath Rendering’ gives me a beautiful exhaust trail but brings my PC to it’s knees when the trajectory is long enough to contain a few thousand samples. The ‘Sprite Rendering’ method performs well for even the longest trajectories but the particle sizes stay constant regardless of the distance they are from your eye. The net effect is the fattening effect as in the following figure.
Ideas anyone? I am posting my smoke trail code below and can post the rest if needed.
Thanks,
Paul
# SmokeTrails.py
""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Description: Smoke trail models
$Author: pleopard $
$Modtime: 10/19/06 8:46p $
$Revision: 1 $
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """
# Standard imports
import sys
# Panda imports
from pandac.PandaModules import *
# PHL imports
from PHL.Core.Diagnostics import *
from PHL.Math.TimeSeriesTable import *
from PHL.Core.Properties import *
from PHL.P3D.Shapes import *
# Local imports
#from Missile import Missile
# Other stuff
Time=0
X=1
Y=2
Z=3
PhiRoll=4
ThetaPitch=5
PsiYaw=6
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Base class
class SmokeTrail:
PlaceByDistance = 1
PlaceBySample = 2
RenderSprites = 1
RenderNodePaths = 2
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def timeUpdate(self,t):
xyz = self.mParentMissile.getPos()
for n in self.mNodes:
n.removeNode()
if self.mPlacementMethod == SmokeTrail.PlaceByDistance:
self.placeByDistance(t)
else:
self.placeBySample(t)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def __init__(\
self,
parentMsl,
texture,
placementMethod,
placementArg,
renderMethod
):
Assert(texture!=None,"Invalid smoke trail texture")
Assert(\
placementMethod==SmokeTrail.PlaceByDistance or
placementMethod==SmokeTrail.PlaceBySample,
"Invalid smoke trail placement method : "+str(placementMethod)
)
Assert(\
renderMethod==SmokeTrail.RenderSprites or
renderMethod==SmokeTrail.RenderNodePaths,
"Invalid smoke trail render method : "+str(renderMethod)
)
self.mParentMissile = parentMsl
self.mTexture = texture
self.mPlacementMethod = placementMethod
self.mRenderDistance = float(placementArg)
self.mRenderSpacing = int(placementArg)
self.mRenderMethod = renderMethod
self.mSpriteNode = None
self.mNodes = []
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def placeBySample(self,t):
# Create sample points list
traj = self.mParentMissile.getTrajectory()
n = traj.sampleCount()
points = []
for i in range(0,n,self.mRenderSpacing):
s = traj.getDiscreteState(i)
ts = s[Time]
if ts>t:
break
xyz = (s[X],s[Y],s[Z])
points.append(xyz)
# Render sprites
if self.mRenderMethod==SmokeTrail.RenderSprites:
pass
# Render NodePaths
elif self.mRenderMethod==SmokeTrail.RenderNodePaths:
for p in points:
n = NodePath(P3DCreateQuadXY())
n.setBillboardPointEye()
n.setScale(10,10,10)
n.setColor(1,1,1)
n.setPos(p[0],p[1],p[2])
n.setTexture(self.mTexture)
n.setTwoSided(True)
n.setTransparency(1)
self.mNodes.append(n)
n.reparentTo(render)
else:
Assert(False,"Invalid smoke trail rendering method")
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def placeByDistance(self,t):
# Create sample points list
traj = self.mParentMissile.getTrajectory()
n = traj.sampleCount()
points = []
s = traj.getDiscreteState(0)
xyzLast = (s[X],s[Y],s[Z])
for i in range(0,n,self.mRenderSpacing):
s = traj.getDiscreteState(i)
ts = s[Time]
if ts>t:
break
xyz = (s[X],s[Y],s[Z])
dx = xyzLast[0]-xyz[0]
dy = xyzLast[1]-xyz[1]
dz = xyzLast[2]-xyz[2]
r2 = dx*dx + dy*dy + dz*dz
r = math.sqrt(r2)
if r>=self.mRenderDistance:
points.append(xyz)
xyzLast = xyz
# Render sprites
if self.mRenderMethod==SmokeTrail.RenderSprites:
gvf = GeomVertexFormat.getV3()
vertexData = GeomVertexData('SpriteVertices',gvf,Geom.UHStatic)
vtxWriter = GeomVertexWriter(vertexData,'vertex')
for p in points:
vtxWriter.addData3f(p[0],p[1],p[2])
geom = Geom(vertexData)
gPoints = GeomPoints(Geom.UHStatic)
gPoints.setIndexType(Geom.NTUint32)
n = len(points)
for i in range(0,n):
gPoints.addVertex(i)
gPoints.closePrimitive()
geom.addPrimitive(gPoints)
geomNode = GeomNode('Sprites')
geomNode.addGeom(geom)
if self.mSpriteNode!=None:
self.mSpriteNode.removeNode()
billboardSize = 20
spriteColor = (1,1,1)
self.mSpriteNode = NodePath(geomNode)
self.mSpriteNode.setColor(\
spriteColor[0],
spriteColor[1],
spriteColor[2]
)
self.mSpriteNode.setTwoSided(True)
self.mSpriteNode.setRenderModePerspective(True)
self.mSpriteNode.setRenderModeThickness(billboardSize)
self.mSpriteNode.setTexGen(\
TextureStage.getDefault(),
TexGenAttrib.MPointSprite
)
self.mSpriteNode.setTexture(self.mTexture,1)
self.mSpriteNode.setTransparency(TransparencyAttrib.MAlpha)
self.mSpriteNode.setDepthTest(0)
self.mSpriteNode.reparentTo(render)
# Render NodePaths
else:
for p in points:
n = NodePath(P3DCreateQuadXY())
n.setBillboardPointEye()
n.setScale(10,10,10)
n.setColor(1,1,1)
n.setPos(p[0],p[1],p[2])
n.setTexture(self.mTexture)
n.setTwoSided(True)
n.setTransparency(1)
self.mNodes.append(n)
n.reparentTo(render)