I took the code from The Fractal Plants Sample Program, removed the inside-out-bug and made it into a class.
Although it is based on the example-code most of the code is rewritten.
Now its much more flexible, I think, and you can grow trees step-by-step.
'''
Created on 11.12.2010
Based on Kwasi Mensah's (kmensah@andrew.cmu.edu) "The Fractal Plants Sample Program" from 8/05/2005
@author: Praios
'''
from panda3d.core import NodePath, Geom, GeomNode, GeomVertexArrayFormat, TransformState, GeomVertexWriter, GeomTristrips, GeomVertexRewriter, GeomVertexReader, GeomVertexData, GeomVertexFormat, InternalName
from panda3d.core import Mat4, Vec3, Vec4, CollisionNode, CollisionTube, Point3
import math, random
#this is for making the tree not too straight
def _randomAxis(vecList, scale=2):
fwd = vecList[0]
perp1 = vecList[1]
perp2 = vecList[2]
nfwd = fwd + perp1 * (scale * random.random() - scale / 2.0) + perp2 * (scale * random.random() - scale / 2.0)
nfwd.normalize()
nperp2 = nfwd.cross(perp1)
nperp2.normalize()
nperp1 = nfwd.cross(nperp2)
nperp1.normalize()
return [nfwd, nperp1, nperp2]
#this is for branching
def _angleRandomAxis(vecList, angle):
fwd = vecList[0]
perp1 = vecList[1]
perp2 = vecList[2]
nangle = angle + math.pi * (0.125 * random.random() - 0.0625)
nfwd = fwd * (0.5 + 2 * random.random()) + perp1 * math.sin(nangle) + perp2 * math.cos(nangle)
nfwd.normalize()
nperp2 = nfwd.cross(perp1)
nperp2.normalize()
nperp1 = nfwd.cross(nperp2)
nperp1.normalize()
return [nfwd, nperp1, nperp2]
class FractalTree(NodePath):
__format = None
def __init__(self, barkTexture, leafModel, lengthList, numCopiesList, radiusList):
NodePath.__init__(self, "Tree Holder")
self.numPrimitives = 0
self.leafModel = leafModel
self.barkTexture = barkTexture
self.bodies = NodePath("Bodies")
self.leaves = NodePath("Leaves")
self.coll = self.attachNewNode(CollisionNode("Collision"))
self.bodydata = GeomVertexData("body vertices", self.__format, Geom.UHStatic)
numCopiesList = list(numCopiesList)
self.numCopiesList = numCopiesList
lengthList = list(lengthList)
radiusList = list(radiusList)
self.radiusList = radiusList
self.lengthList = lengthList
self.iterations = 1
self.makeEnds()
self.makeFromStack(True)
#self.coll.show()
self.bodies.setTexture(barkTexture)
self.coll.reparentTo(self)
self.bodies.reparentTo(self)
self.leaves.reparentTo(self)
#this makes a flattened version of the tree for faster rendering...
def getStatic(self):
np = NodePath(self.node().copySubgraph())
np.flattenStrong()
return np
#this should make only one instance
@classmethod
def makeFMT(cls):
if cls.__format is not None:
return
formatArray = GeomVertexArrayFormat()
formatArray.addColumn(InternalName.make("drawFlag"), 1, Geom.NTUint8, Geom.COther)
format = GeomVertexFormat(GeomVertexFormat.getV3n3t2())
format.addArray(formatArray)
cls.__format = GeomVertexFormat.registerFormat(format)
def makeEnds(self, pos=Vec3(0, 0, 0), vecList=[Vec3(0, 0, 1), Vec3(1, 0, 0), Vec3(0, -1, 0)]):
self.ends = [(pos, vecList, 0)]
def makeFromStack(self, makeColl=False):
stack = self.ends
to = self.iterations
lengthList = self.lengthList
numCopiesList = self.numCopiesList
radiusList = self.radiusList
ends = []
while stack:
pos, vecList, depth = stack.pop()
length = lengthList[depth]
if depth != to and depth + 1 < len(lengthList):
self.drawBody(pos, vecList, radiusList[depth])
#move foward along the right axis
newPos = pos + vecList[0] * length.length()
if makeColl:
self.makeColl(pos, newPos, radiusList[depth])
numCopies = numCopiesList[depth]
if numCopies:
for i in xrange(numCopies):
stack.append((newPos, _angleRandomAxis(vecList, 2 * math.pi * i / numCopies), depth + 1))
#stack.append((newPos, _randomAxis(vecList,3), depth + 1))
else:
#just make another branch connected to this one with a small variation in direction
stack.append((newPos, _randomAxis(vecList, 0.25), depth + 1))
else:
ends.append((pos, vecList, depth))
self.drawBody(pos, vecList, radiusList[depth], False)
self.drawLeaf(pos, vecList)
self.ends = ends
def makeColl(self, pos, newPos, radius):
tube = CollisionTube(Point3(pos), Point3(newPos), radius)
self.coll.node().addSolid(tube)
#this draws the body of the tree. This draws a ring of vertices and connects the rings with
#triangles to form the body.
#this keepDrawing paramter tells the function wheter or not we're at an end
#if the vertices before you were an end, dont draw branches to it
def drawBody(self, pos, vecList, radius=1, keepDrawing=True, numVertices=16):
vdata = self.bodydata
circleGeom = Geom(vdata)
vertWriter = GeomVertexWriter(vdata, "vertex")
#colorWriter = GeomVertexWriter(vdata, "color")
normalWriter = GeomVertexWriter(vdata, "normal")
drawReWriter = GeomVertexRewriter(vdata, "drawFlag")
texReWriter = GeomVertexRewriter(vdata, "texcoord")
startRow = vdata.getNumRows()
vertWriter.setRow(startRow)
#colorWriter.setRow(startRow)
normalWriter.setRow(startRow)
sCoord = 0
if (startRow != 0):
texReWriter.setRow(startRow - numVertices)
sCoord = texReWriter.getData2f().getX() + 1
drawReWriter.setRow(startRow - numVertices)
if(drawReWriter.getData1f() == False):
sCoord -= 1
drawReWriter.setRow(startRow)
texReWriter.setRow(startRow)
angleSlice = 2 * math.pi / numVertices
currAngle = 0
#axisAdj=Mat4.rotateMat(45, axis)*Mat4.scaleMat(radius)*Mat4.translateMat(pos)
perp1 = vecList[1]
perp2 = vecList[2]
#vertex information is written here
for i in xrange(numVertices):
adjCircle = pos + (perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)) * radius
normal = perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)
normalWriter.addData3f(normal)
vertWriter.addData3f(adjCircle)
texReWriter.addData2f(sCoord, (i + 0.001) / (numVertices - 1))
#colorWriter.addData4f(0.5, 0.5, 0.5, 1)
drawReWriter.addData1f(keepDrawing)
currAngle += angleSlice
drawReader = GeomVertexReader(vdata, "drawFlag")
drawReader.setRow(startRow - numVertices)
#we cant draw quads directly so we use Tristrips
if (startRow != 0) and (drawReader.getData1f() != False):
lines = GeomTristrips(Geom.UHStatic)
half = numVertices / 2
lines.addVertex(startRow)
lines.addVertex(startRow - half)
for i in xrange(numVertices - 1, -1, -1):
lines.addVertex(i + startRow)
if i < half:
lines.addVertex(i + startRow - half)
else:
lines.addVertex(i + startRow - half - numVertices)
lines.closePrimitive()
lines.decompose()
circleGeom.addPrimitive(lines)
circleGeomNode = GeomNode("Debug")
circleGeomNode.addGeom(circleGeom)
self.numPrimitives += numVertices * 2
self.bodies.attachNewNode(circleGeomNode)
#this draws leafs when we reach an end
def drawLeaf(self, pos=Vec3(0, 0, 0), vecList=[Vec3(0, 0, 1), Vec3(1, 0, 0), Vec3(0, -1, 0)], scale=0.125):
#use the vectors that describe the direction the branch grows to make the right
#rotation matrix
newCs = Mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
newCs.setRow(0, vecList[2]) #right
newCs.setRow(1, vecList[1]) #up
newCs.setRow(2, vecList[0]) #forward
newCs.setRow(3, Vec3(0, 0, 0))
newCs.setCol(3, Vec4(0, 0, 0, 1))
axisAdj = Mat4.scaleMat(scale) * newCs * Mat4.translateMat(pos)
leafModel = NodePath("leaf")
self.leafModel.instanceTo(leafModel)
leafModel.reparentTo(self.leaves)
leafModel.setTransform(TransformState.makeMat(axisAdj))
def grow(self, num=1, removeLeaves=True, leavesScale=1):
self.iterations += num
while num > 0:
self.setScale(self, 1.1)
self.leafModel.setScale(self.leafModel, leavesScale / 1.1)
if removeLeaves:
for c in self.leaves.getChildren():
c.removeNode()
self.makeFromStack()
self.bodies.setTexture(self.barkTexture)
num -= 1
FractalTree.makeFMT()
class DefaultTree(FractalTree):
def __init__(self):
barkTexture = base.loader.loadTexture("models/tree/barkTexture.jpg")
leafModel = base.loader.loadModel('models/tree/shrubbery')
leafModel.clearModelNodes()
leafModel.flattenStrong()
leafTexture = base.loader.loadTexture('models/tree/material-10-cl.png')
leafModel.setTexture(leafTexture, 1)
lengthList = self.makeLengthList(Vec3(1, 1, 1), 64)
numCopiesList = self.makeNumCopiesList(4, 3, 64)
radiusList = self.makeRadiusList(0.5, 64, numCopiesList)
FractalTree.__init__(self, barkTexture, leafModel, lengthList, numCopiesList, radiusList)
@staticmethod
def makeRadiusList(radius, iterations, numCopiesList, scale=1.5):
l = [radius]
for i in xrange(1, iterations):
#if i % 3 == 0:
if i != 1 and numCopiesList[i - 2]:
radius /= numCopiesList[i - 2] ** 0.5
else:
radius /= scale ** (1.0 / 3)
l.append(radius)
return l
@staticmethod
def makeLengthList(length, iterations, sx=2.0, sy=2.0, sz=1.25):
l = [length]
for i in xrange(1, iterations):
#if i % 3 == 0:
#decrease dimensions when we branch
#length = Vec3(length.getX() / 2, length.getY() / 2, length.getZ() / 1.1)
length = Vec3(length.getX() / sx ** (1.0 / 3), length.getY() / sy ** (1.0 / 3), length.getZ() / sz ** (1.0 / 3))
l.append(length)
return l
@staticmethod
def makeNumCopiesList(numCopies, branchat, iterations):
l = list()
for i in xrange(iterations):
if i % int(branchat) == 0:
l.append(numCopies)
else:
l.append(0)
return l
#this grows a tree
if __name__ == "__main__":
from direct.showbase.ShowBase import ShowBase
base = ShowBase()
base.cam.setPos(0, -80, 10)
t = DefaultTree()
t.reparentTo(base.render)
#make an optimized snapshot of the current tree
np = t.getStatic()
np.setPos(10, 10, 0)
np.reparentTo(base.render)
#demonstrate growing
last = [0] # a bit hacky
def grow(task):
if task.time > last[0] + 1:
t.grow()
last[0] = task.time
#t.leaves.detachNode()
if last[0] > 10:
return task.done
return task.cont
base.taskMgr.add(grow, "growTask")
base.run()
edit: asynchronous flattening makes trouble - removed