main.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #!/usr/bin/env python
  2. # Author: Kwasi Mensah ([email protected])
  3. # Date: 8/05/2005
  4. #
  5. # This demo shows how to make quasi-fractal trees in Panda.
  6. # Its primarily meant to be a more complex example on how to use
  7. # Panda's Geom interface.
  8. #
  9. from direct.showbase.ShowBase import ShowBase
  10. from panda3d.core import Filename, InternalName
  11. from panda3d.core import GeomVertexArrayFormat, GeomVertexFormat
  12. from panda3d.core import Geom, GeomNode, GeomTrifans, GeomTristrips
  13. from panda3d.core import GeomVertexReader, GeomVertexWriter
  14. from panda3d.core import GeomVertexRewriter, GeomVertexData
  15. from panda3d.core import PerspectiveLens, TextNode
  16. from panda3d.core import TransformState, CullFaceAttrib
  17. from panda3d.core import Light, AmbientLight, Spotlight
  18. from panda3d.core import NodePath
  19. from panda3d.core import LVector3, LMatrix4
  20. from direct.task.Task import Task
  21. from direct.gui.OnscreenText import OnscreenText
  22. from direct.showbase.DirectObject import DirectObject
  23. import math
  24. import random
  25. import time
  26. import sys
  27. import os
  28. random.seed()
  29. base = ShowBase()
  30. base.disableMouse()
  31. base.camera.setPos(0, -180, 30)
  32. numPrimitives = 0
  33. title = OnscreenText(text="Panda3D: Tutorial - Procedurally Making a Tree",
  34. style=1, fg=(1, 1, 1, 1), parent=base.a2dBottomCenter,
  35. pos=(0, 0.1), scale=.08)
  36. qEvent = OnscreenText(
  37. text="Q: Start Scene Over",
  38. parent=base.a2dTopLeft, align=TextNode.ALeft,
  39. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.10),
  40. scale=.05)
  41. wEvent = OnscreenText(
  42. text="W: Add Another Tree",
  43. parent=base.a2dTopLeft, align=TextNode.ALeft,
  44. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16),
  45. scale=.05)
  46. # this computes the new Axis which we'll make a branch grow alowng when we
  47. # split
  48. def randomAxis(vecList):
  49. fwd = vecList[0]
  50. perp1 = vecList[1]
  51. perp2 = vecList[2]
  52. nfwd = fwd + perp1 * (2 * random.random() - 1) + \
  53. perp2 * (2 * random.random() - 1)
  54. nfwd.normalize()
  55. nperp2 = nfwd.cross(perp1)
  56. nperp2.normalize()
  57. nperp1 = nfwd.cross(nperp2)
  58. nperp1.normalize()
  59. return [nfwd, nperp1, nperp2]
  60. # this makes smalle variations in direction when we are growing a branch
  61. # but not splitting
  62. def smallRandomAxis(vecList):
  63. fwd = vecList[0]
  64. perp1 = vecList[1]
  65. perp2 = vecList[2]
  66. nfwd = fwd + perp1 * (1 * random.random() - 0.5) + \
  67. perp2 * (1 * random.random() - 0.5)
  68. nfwd.normalize()
  69. nperp2 = nfwd.cross(perp1)
  70. nperp2.normalize()
  71. nperp1 = nfwd.cross(nperp2)
  72. nperp1.normalize()
  73. return [nfwd, nperp1, nperp2]
  74. # this draws the body of the tree. This draws a ring of vertices and connects the rings with
  75. # triangles to form the body.
  76. # this keepDrawing paramter tells the function wheter or not we're at an end
  77. # if the vertices before you were an end, dont draw branches to it
  78. def drawBody(nodePath, vdata, pos, vecList, radius=1, keepDrawing=True, numVertices=8):
  79. circleGeom = Geom(vdata)
  80. vertWriter = GeomVertexWriter(vdata, "vertex")
  81. colorWriter = GeomVertexWriter(vdata, "color")
  82. normalWriter = GeomVertexWriter(vdata, "normal")
  83. drawReWriter = GeomVertexRewriter(vdata, "drawFlag")
  84. texReWriter = GeomVertexRewriter(vdata, "texcoord")
  85. startRow = vdata.getNumRows()
  86. vertWriter.setRow(startRow)
  87. colorWriter.setRow(startRow)
  88. normalWriter.setRow(startRow)
  89. sCoord = 0
  90. if (startRow != 0):
  91. texReWriter.setRow(startRow - numVertices)
  92. sCoord = texReWriter.getData2f().getX() + 1
  93. drawReWriter.setRow(startRow - numVertices)
  94. if(drawReWriter.getData1f() == False):
  95. sCoord -= 1
  96. drawReWriter.setRow(startRow)
  97. texReWriter.setRow(startRow)
  98. angleSlice = 2 * math.pi / numVertices
  99. currAngle = 0
  100. #axisAdj=LMatrix4.rotateMat(45, axis)*LMatrix4.scaleMat(radius)*LMatrix4.translateMat(pos)
  101. perp1 = vecList[1]
  102. perp2 = vecList[2]
  103. # vertex information is written here
  104. for i in range(numVertices):
  105. adjCircle = pos + \
  106. (perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)) * \
  107. radius
  108. normal = perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)
  109. normalWriter.addData3f(normal)
  110. vertWriter.addData3f(adjCircle)
  111. texReWriter.addData2f(sCoord, (i + 0.001) / (numVertices - 1))
  112. colorWriter.addData4f(0.5, 0.5, 0.5, 1)
  113. drawReWriter.addData1f(keepDrawing)
  114. currAngle += angleSlice
  115. if startRow == 0:
  116. return
  117. drawReader = GeomVertexReader(vdata, "drawFlag")
  118. drawReader.setRow(startRow - numVertices)
  119. # we cant draw quads directly so we use Tristrips
  120. if drawReader.getData1i() != 0:
  121. lines = GeomTristrips(Geom.UHStatic)
  122. half = int(numVertices * 0.5)
  123. for i in range(numVertices):
  124. lines.addVertex(i + startRow)
  125. if i < half:
  126. lines.addVertex(i + startRow - half)
  127. else:
  128. lines.addVertex(i + startRow - half - numVertices)
  129. lines.addVertex(startRow)
  130. lines.addVertex(startRow - half)
  131. lines.closePrimitive()
  132. lines.decompose()
  133. circleGeom.addPrimitive(lines)
  134. circleGeomNode = GeomNode("Debug")
  135. circleGeomNode.addGeom(circleGeom)
  136. # I accidentally made the front-face face inwards. Make reverse makes the tree render properly and
  137. # should cause any surprises to any poor programmer that tries to use
  138. # this code
  139. circleGeomNode.setAttrib(CullFaceAttrib.makeReverse(), 1)
  140. global numPrimitives
  141. numPrimitives += numVertices * 2
  142. nodePath.attachNewNode(circleGeomNode)
  143. # this draws leafs when we reach an end
  144. 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):
  145. # use the vectors that describe the direction the branch grows to make the right
  146. # rotation matrix
  147. newCs = LMatrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  148. newCs.setRow(0, vecList[2]) # right
  149. newCs.setRow(1, vecList[1]) # up
  150. newCs.setRow(2, vecList[0]) # forward
  151. newCs.setRow(3, (0, 0, 0))
  152. newCs.setCol(3, (0, 0, 0, 1))
  153. axisAdj = LMatrix4.scaleMat(scale) * newCs * LMatrix4.translateMat(pos)
  154. # orginlly made the leaf out of geometry but that didnt look good
  155. # I also think there should be a better way to handle the leaf texture other than
  156. # hardcoding the filename
  157. leafModel = loader.loadModel('models/shrubbery')
  158. leafTexture = loader.loadTexture('models/material-10-cl.png')
  159. leafModel.reparentTo(nodePath)
  160. leafModel.setTexture(leafTexture, 1)
  161. leafModel.setTransform(TransformState.makeMat(axisAdj))
  162. # recursive algorthim to make the tree
  163. 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)]):
  164. if numIterations > 0:
  165. drawBody(nodePath, bodydata, pos, vecList, length.getX())
  166. # move foward along the right axis
  167. newPos = pos + vecList[0] * length.length()
  168. # only branch every third level (sorta)
  169. if numIterations % 3 == 0:
  170. # decrease dimensions when we branch
  171. length = LVector3(
  172. length.getX() / 2, length.getY() / 2, length.getZ() / 1.1)
  173. for i in range(numCopies):
  174. makeFractalTree(bodydata, nodePath, length, newPos,
  175. numIterations - 1, numCopies, randomAxis(vecList))
  176. else:
  177. # just make another branch connected to this one with a small
  178. # variation in direction
  179. makeFractalTree(bodydata, nodePath, length, newPos,
  180. numIterations - 1, numCopies, smallRandomAxis(vecList))
  181. else:
  182. drawBody(nodePath, bodydata, pos, vecList, length.getX(), False)
  183. drawLeaf(nodePath, bodydata, pos, vecList)
  184. alight = AmbientLight('alight')
  185. alight.setColor((0.5, 0.5, 0.5, 1))
  186. alnp = render.attachNewNode(alight)
  187. render.setLight(alnp)
  188. slight = Spotlight('slight')
  189. slight.setColor((1, 1, 1, 1))
  190. lens = PerspectiveLens()
  191. slight.setLens(lens)
  192. slnp = render.attachNewNode(slight)
  193. render.setLight(slnp)
  194. slnp.setPos(0, 0, 40)
  195. # rotating light to show that normals are calculated correctly
  196. def updateLight(task):
  197. global slnp
  198. currPos = slnp.getPos()
  199. currPos.setX(100 * math.cos(task.time) / 2)
  200. currPos.setY(100 * math.sin(task.time) / 2)
  201. slnp.setPos(currPos)
  202. slnp.lookAt(render)
  203. return Task.cont
  204. taskMgr.add(updateLight, "rotating Light")
  205. # add some interactivity to the program
  206. class MyTapper(DirectObject):
  207. def __init__(self):
  208. formatArray = GeomVertexArrayFormat()
  209. formatArray.addColumn(
  210. InternalName.make("drawFlag"), 1, Geom.NTUint8, Geom.COther)
  211. format = GeomVertexFormat(GeomVertexFormat.getV3n3cpt2())
  212. format.addArray(formatArray)
  213. self.format = GeomVertexFormat.registerFormat(format)
  214. bodydata = GeomVertexData("body vertices", format, Geom.UHStatic)
  215. self.barkTexture = loader.loadTexture("barkTexture.jpg")
  216. treeNodePath = NodePath("Tree Holder")
  217. makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7))
  218. treeNodePath.setTexture(self.barkTexture, 1)
  219. treeNodePath.reparentTo(render)
  220. self.accept("q", self.regenTree)
  221. self.accept("w", self.addTree)
  222. self.accept("arrow_up", self.upIterations)
  223. self.accept("arrow_down", self.downIterations)
  224. self.accept("arrow_right", self.upCopies)
  225. self.accept("arrow_left", self.downCopies)
  226. self.numIterations = 11
  227. self.numCopies = 4
  228. self.upDownEvent = OnscreenText(
  229. text="Up/Down: Increase/Decrease the number of iterations (" + str(
  230. self.numIterations) + ")",
  231. parent=base.a2dTopLeft, align=TextNode.ALeft,
  232. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.22),
  233. scale=.05, mayChange=True)
  234. self.leftRightEvent = OnscreenText(
  235. text="Left/Right: Increase/Decrease branching (" + str(
  236. self.numCopies) + ")",
  237. parent=base.a2dTopLeft, align=TextNode.ALeft,
  238. style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.28),
  239. scale=.05, mayChange=True)
  240. def upIterations(self):
  241. self.numIterations += 1
  242. self.upDownEvent.setText(
  243. "Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")")
  244. def downIterations(self):
  245. self.numIterations -= 1
  246. self.upDownEvent.setText(
  247. "Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")")
  248. def upCopies(self):
  249. self.numCopies += 1
  250. self.leftRightEvent.setText(
  251. "Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")")
  252. def downCopies(self):
  253. self.numCopies -= 1
  254. self.leftRightEvent.setText(
  255. "Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")")
  256. def regenTree(self):
  257. forest = render.findAllMatches("Tree Holder")
  258. forest.detach()
  259. bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic)
  260. treeNodePath = NodePath("Tree Holder")
  261. makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7), LVector3(
  262. 0, 0, 0), self.numIterations, self.numCopies)
  263. treeNodePath.setTexture(self.barkTexture, 1)
  264. treeNodePath.reparentTo(render)
  265. def addTree(self):
  266. bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic)
  267. randomPlace = LVector3(
  268. 200 * random.random() - 100, 200 * random.random() - 100, 0)
  269. # randomPlace.normalize()
  270. treeNodePath = NodePath("Tree Holder")
  271. makeFractalTree(bodydata, treeNodePath, LVector3(
  272. 4, 4, 7), randomPlace, self.numIterations, self.numCopies)
  273. treeNodePath.setTexture(self.barkTexture, 1)
  274. treeNodePath.reparentTo(render)
  275. t = MyTapper()
  276. print(numPrimitives)
  277. base.run()