toontown-just-works/build/nirai/panda3d/samples/fractal-plants/main.py
2024-07-07 18:08:39 -05:00

353 lines
12 KiB
Python

#!/usr/bin/env python
# Author: Kwasi Mensah (kmensah@andrew.cmu.edu)
# Date: 8/05/2005
#
# This demo shows how to make quasi-fractal trees in Panda.
# Its primarily meant to be a more complex example on how to use
# Panda's Geom interface.
#
from direct.showbase.ShowBase import ShowBase
from panda3d.core import Filename, InternalName
from panda3d.core import GeomVertexArrayFormat, GeomVertexFormat
from panda3d.core import Geom, GeomNode, GeomTrifans, GeomTristrips
from panda3d.core import GeomVertexReader, GeomVertexWriter
from panda3d.core import GeomVertexRewriter, GeomVertexData
from panda3d.core import PerspectiveLens, TextNode
from panda3d.core import TransformState, CullFaceAttrib
from panda3d.core import Light, AmbientLight, Spotlight
from panda3d.core import NodePath
from panda3d.core import LVector3, LMatrix4
from direct.task.Task import Task
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
import math
import random
import time
import sys
import os
random.seed()
base = ShowBase()
base.disableMouse()
base.camera.setPos(0, -180, 30)
numPrimitives = 0
title = OnscreenText(text="Panda3D: Tutorial - Procedurally Making a Tree",
style=1, fg=(1, 1, 1, 1), parent=base.a2dBottomCenter,
pos=(0, 0.1), scale=.08)
qEvent = OnscreenText(
text="Q: Start Scene Over",
parent=base.a2dTopLeft, align=TextNode.ALeft,
style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.10),
scale=.05)
wEvent = OnscreenText(
text="W: Add Another Tree",
parent=base.a2dTopLeft, align=TextNode.ALeft,
style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16),
scale=.05)
# this computes the new Axis which we'll make a branch grow alowng when we
# split
def randomAxis(vecList):
fwd = vecList[0]
perp1 = vecList[1]
perp2 = vecList[2]
nfwd = fwd + perp1 * (2 * random.random() - 1) + \
perp2 * (2 * random.random() - 1)
nfwd.normalize()
nperp2 = nfwd.cross(perp1)
nperp2.normalize()
nperp1 = nfwd.cross(nperp2)
nperp1.normalize()
return [nfwd, nperp1, nperp2]
# this makes smalle variations in direction when we are growing a branch
# but not splitting
def smallRandomAxis(vecList):
fwd = vecList[0]
perp1 = vecList[1]
perp2 = vecList[2]
nfwd = fwd + perp1 * (1 * random.random() - 0.5) + \
perp2 * (1 * random.random() - 0.5)
nfwd.normalize()
nperp2 = nfwd.cross(perp1)
nperp2.normalize()
nperp1 = nfwd.cross(nperp2)
nperp1.normalize()
return [nfwd, nperp1, nperp2]
# 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(nodePath, vdata, pos, vecList, radius=1, keepDrawing=True, numVertices=8):
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=LMatrix4.rotateMat(45, axis)*LMatrix4.scaleMat(radius)*LMatrix4.translateMat(pos)
perp1 = vecList[1]
perp2 = vecList[2]
# vertex information is written here
for i in range(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) & (drawReader.getData1f() != False):
lines = GeomTristrips(Geom.UHStatic)
half = int(numVertices * 0.5)
for i in range(numVertices):
lines.addVertex(i + startRow)
if i < half:
lines.addVertex(i + startRow - half)
else:
lines.addVertex(i + startRow - half - numVertices)
lines.addVertex(startRow)
lines.addVertex(startRow - half)
lines.closePrimitive()
lines.decompose()
circleGeom.addPrimitive(lines)
circleGeomNode = GeomNode("Debug")
circleGeomNode.addGeom(circleGeom)
# I accidentally made the front-face face inwards. Make reverse makes the tree render properly and
# should cause any surprises to any poor programmer that tries to use
# this code
circleGeomNode.setAttrib(CullFaceAttrib.makeReverse(), 1)
global numPrimitives
numPrimitives += numVertices * 2
nodePath.attachNewNode(circleGeomNode)
# this draws leafs when we reach an end
def drawLeaf(nodePath, vdata, pos=LVector3(0, 0, 0), vecList=[LVector3(0, 0, 1), LVector3(1, 0, 0), LVector3(0, -1, 0)], scale=0.125):
# use the vectors that describe the direction the branch grows to make the right
# rotation matrix
newCs = LMatrix4(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, (0, 0, 0))
newCs.setCol(3, (0, 0, 0, 1))
axisAdj = LMatrix4.scaleMat(scale) * newCs * LMatrix4.translateMat(pos)
# orginlly made the leaf out of geometry but that didnt look good
# I also think there should be a better way to handle the leaf texture other than
# hardcoding the filename
leafModel = loader.loadModel('models/shrubbery')
leafTexture = loader.loadTexture('models/material-10-cl.png')
leafModel.reparentTo(nodePath)
leafModel.setTexture(leafTexture, 1)
leafModel.setTransform(TransformState.makeMat(axisAdj))
# recursive algorthim to make the tree
def makeFractalTree(bodydata, nodePath, length, pos=LVector3(0, 0, 0), numIterations=11, numCopies=4, vecList=[LVector3(0, 0, 1), LVector3(1, 0, 0), LVector3(0, -1, 0)]):
if numIterations > 0:
drawBody(nodePath, bodydata, pos, vecList, length.getX())
# move foward along the right axis
newPos = pos + vecList[0] * length.length()
# only branch every third level (sorta)
if numIterations % 3 == 0:
# decrease dimensions when we branch
length = LVector3(
length.getX() / 2, length.getY() / 2, length.getZ() / 1.1)
for i in range(numCopies):
makeFractalTree(bodydata, nodePath, length, newPos,
numIterations - 1, numCopies, randomAxis(vecList))
else:
# just make another branch connected to this one with a small
# variation in direction
makeFractalTree(bodydata, nodePath, length, newPos,
numIterations - 1, numCopies, smallRandomAxis(vecList))
else:
drawBody(nodePath, bodydata, pos, vecList, length.getX(), False)
drawLeaf(nodePath, bodydata, pos, vecList)
alight = AmbientLight('alight')
alight.setColor((0.5, 0.5, 0.5, 1))
alnp = render.attachNewNode(alight)
render.setLight(alnp)
slight = Spotlight('slight')
slight.setColor((1, 1, 1, 1))
lens = PerspectiveLens()
slight.setLens(lens)
slnp = render.attachNewNode(slight)
render.setLight(slnp)
slnp.setPos(0, 0, 40)
# rotating light to show that normals are calculated correctly
def updateLight(task):
global slnp
currPos = slnp.getPos()
currPos.setX(100 * math.cos(task.time) / 2)
currPos.setY(100 * math.sin(task.time) / 2)
slnp.setPos(currPos)
slnp.lookAt(render)
return Task.cont
taskMgr.add(updateLight, "rotating Light")
# add some interactivity to the program
class MyTapper(DirectObject):
def __init__(self):
formatArray = GeomVertexArrayFormat()
formatArray.addColumn(
InternalName.make("drawFlag"), 1, Geom.NTUint8, Geom.COther)
format = GeomVertexFormat(GeomVertexFormat.getV3n3cpt2())
format.addArray(formatArray)
self.format = GeomVertexFormat.registerFormat(format)
bodydata = GeomVertexData("body vertices", format, Geom.UHStatic)
self.barkTexture = loader.loadTexture("barkTexture.jpg")
treeNodePath = NodePath("Tree Holder")
makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7))
treeNodePath.setTexture(self.barkTexture, 1)
treeNodePath.reparentTo(render)
self.accept("q", self.regenTree)
self.accept("w", self.addTree)
self.accept("arrow_up", self.upIterations)
self.accept("arrow_down", self.downIterations)
self.accept("arrow_right", self.upCopies)
self.accept("arrow_left", self.downCopies)
self.numIterations = 11
self.numCopies = 4
self.upDownEvent = OnscreenText(
text="Up/Down: Increase/Decrease the number of iterations (" + str(
self.numIterations) + ")",
parent=base.a2dTopLeft, align=TextNode.ALeft,
style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.22),
scale=.05, mayChange=True)
self.leftRightEvent = OnscreenText(
text="Left/Right: Increase/Decrease branching (" + str(
self.numCopies) + ")",
parent=base.a2dTopLeft, align=TextNode.ALeft,
style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.28),
scale=.05, mayChange=True)
def upIterations(self):
self.numIterations += 1
self.upDownEvent.setText(
"Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")")
def downIterations(self):
self.numIterations -= 1
self.upDownEvent.setText(
"Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")")
def upCopies(self):
self.numCopies += 1
self.leftRightEvent.setText(
"Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")")
def downCopies(self):
self.numCopies -= 1
self.leftRightEvent.setText(
"Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")")
def regenTree(self):
forest = render.findAllMatches("Tree Holder")
forest.detach()
bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic)
treeNodePath = NodePath("Tree Holder")
makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7), LVector3(
0, 0, 0), self.numIterations, self.numCopies)
treeNodePath.setTexture(self.barkTexture, 1)
treeNodePath.reparentTo(render)
def addTree(self):
bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic)
randomPlace = LVector3(
200 * random.random() - 100, 200 * random.random() - 100, 0)
# randomPlace.normalize()
treeNodePath = NodePath("Tree Holder")
makeFractalTree(bodydata, treeNodePath, LVector3(
4, 4, 7), randomPlace, self.numIterations, self.numCopies)
treeNodePath.setTexture(self.barkTexture, 1)
treeNodePath.reparentTo(render)
t = MyTapper()
print(numPrimitives)
base.run()