Pārlūkot izejas kodu

Add sample programs to the repository

rdb 10 gadi atpakaļ
vecāks
revīzija
78c3593d2c
100 mainītis faili ar 9874 papildinājumiem un 1 dzēšanām
  1. 0 1
      .gitignore
  2. 399 0
      samples/asteroids/main.py
  3. 39 0
      samples/asteroids/models/plane.egg
  4. BIN
      samples/asteroids/textures/asteroid1.png
  5. BIN
      samples/asteroids/textures/asteroid2.png
  6. BIN
      samples/asteroids/textures/asteroid3.png
  7. BIN
      samples/asteroids/textures/bullet.png
  8. BIN
      samples/asteroids/textures/ship.png
  9. BIN
      samples/asteroids/textures/stars.jpg
  10. 338 0
      samples/ball-in-maze/main.py
  11. BIN
      samples/ball-in-maze/models/ball.egg.pz
  12. BIN
      samples/ball-in-maze/models/iron05.jpg
  13. BIN
      samples/ball-in-maze/models/limba.jpg
  14. BIN
      samples/ball-in-maze/models/maze.egg.pz
  15. 199 0
      samples/boxing-robots/main.py
  16. BIN
      samples/boxing-robots/models/ring.egg.pz
  17. BIN
      samples/boxing-robots/models/robot.egg.pz
  18. BIN
      samples/boxing-robots/models/robot_head_down.egg.pz
  19. BIN
      samples/boxing-robots/models/robot_head_up.egg.pz
  20. BIN
      samples/boxing-robots/models/robot_left_punch.egg.pz
  21. BIN
      samples/boxing-robots/models/robot_right_punch.egg.pz
  22. 188 0
      samples/bump-mapping/main.py
  23. 1671 0
      samples/bump-mapping/models/abstractroom.egg
  24. BIN
      samples/bump-mapping/models/abstractroom.mb
  25. BIN
      samples/bump-mapping/models/brick-c.jpg
  26. BIN
      samples/bump-mapping/models/brick-n.jpg
  27. BIN
      samples/bump-mapping/models/fieldstone-c.jpg
  28. BIN
      samples/bump-mapping/models/fieldstone-n.jpg
  29. 497 0
      samples/bump-mapping/models/icosphere.egg
  30. BIN
      samples/bump-mapping/models/layingrock-c.jpg
  31. BIN
      samples/bump-mapping/models/layingrock-h.jpg
  32. BIN
      samples/bump-mapping/models/layingrock-n.jpg
  33. 196 0
      samples/carousel/main.py
  34. BIN
      samples/carousel/models/carousel_base.egg.pz
  35. BIN
      samples/carousel/models/carousel_base.jpg
  36. BIN
      samples/carousel/models/carousel_lights.egg.pz
  37. BIN
      samples/carousel/models/carousel_lights_off.jpg
  38. BIN
      samples/carousel/models/carousel_lights_on.jpg
  39. BIN
      samples/carousel/models/carousel_panda.egg.pz
  40. BIN
      samples/carousel/models/carousel_panda.jpg
  41. BIN
      samples/carousel/models/env.egg.pz
  42. BIN
      samples/carousel/models/env_ground.jpg
  43. BIN
      samples/carousel/models/env_sky.jpg
  44. 160 0
      samples/cartoon-shader/advanced.py
  45. 122 0
      samples/cartoon-shader/basic.py
  46. 35 0
      samples/cartoon-shader/inkGen.sha
  47. 29 0
      samples/cartoon-shader/lightingGen.sha
  48. BIN
      samples/cartoon-shader/models/nik-dragon.egg.pz
  49. 25 0
      samples/cartoon-shader/normalGen.sha
  50. 278 0
      samples/chessboard/main.py
  51. BIN
      samples/chessboard/models/bishop.egg.pz
  52. BIN
      samples/chessboard/models/king.egg.pz
  53. BIN
      samples/chessboard/models/knight.egg.pz
  54. BIN
      samples/chessboard/models/pawn.egg.pz
  55. BIN
      samples/chessboard/models/queen.egg.pz
  56. BIN
      samples/chessboard/models/rook.egg.pz
  57. BIN
      samples/chessboard/models/square.egg.pz
  58. 586 0
      samples/culling/models/cells.egg
  59. 2435 0
      samples/culling/models/level.egg
  60. 562 0
      samples/culling/models/occluders.egg
  61. 674 0
      samples/culling/models/portals.egg
  62. BIN
      samples/culling/models/tex_0.png
  63. BIN
      samples/culling/models/tex_1.png
  64. BIN
      samples/culling/models/tex_2.png
  65. BIN
      samples/culling/models/tex_3.png
  66. BIN
      samples/culling/models/tex_4.png
  67. BIN
      samples/culling/models/tex_5.png
  68. BIN
      samples/culling/models/tex_6.png
  69. BIN
      samples/culling/models/tex_7.png
  70. 159 0
      samples/culling/occluder_culling.py
  71. 306 0
      samples/culling/portal_culling.py
  72. 305 0
      samples/disco-lights/main.py
  73. BIN
      samples/disco-lights/models/disco_hall.egg.pz
  74. BIN
      samples/disco-lights/models/sphere.egg.pz
  75. 53 0
      samples/distortion/distortion.sha
  76. 120 0
      samples/distortion/main.py
  77. BIN
      samples/distortion/models/boat.egg.pz
  78. BIN
      samples/distortion/models/ocean.jpg
  79. 2 0
      samples/distortion/models/plane.egg.pz
  80. BIN
      samples/distortion/models/water.png
  81. 45 0
      samples/fireflies/light.sha
  82. 416 0
      samples/fireflies/main.py
  83. 35 0
      samples/fireflies/model.sha
  84. BIN
      samples/fireflies/models/Bark.jpg
  85. BIN
      samples/fireflies/models/Bark03.jpg
  86. BIN
      samples/fireflies/models/background.egg.pz
  87. BIN
      samples/fireflies/models/bigleaf3.png
  88. BIN
      samples/fireflies/models/elmleaf.png
  89. BIN
      samples/fireflies/models/firefly.png
  90. BIN
      samples/fireflies/models/foliage01.egg.pz
  91. BIN
      samples/fireflies/models/foliage02.egg.pz
  92. BIN
      samples/fireflies/models/foliage03.egg.pz
  93. BIN
      samples/fireflies/models/foliage04.egg.pz
  94. BIN
      samples/fireflies/models/foliage05.egg.pz
  95. BIN
      samples/fireflies/models/foliage06.egg.pz
  96. BIN
      samples/fireflies/models/foliage07.egg.pz
  97. BIN
      samples/fireflies/models/foliage08.egg.pz
  98. BIN
      samples/fireflies/models/foliage09.egg.pz
  99. BIN
      samples/fireflies/models/indt04S.jpg
  100. BIN
      samples/fireflies/models/noise.jpg

+ 0 - 1
.gitignore

@@ -1,4 +1,3 @@
 /built_x64
 /built
-/samples
 /thirdparty

+ 399 - 0
samples/asteroids/main.py

@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+
+# Author: Shao Zhang, Phil Saltzman, and Greg Lindley
+# Last Updated: 2015-03-13
+#
+# This tutorial demonstrates the use of tasks. A task is a function that
+# gets called once every frame. They are good for things that need to be
+# updated very often. In the case of asteroids, we use tasks to update
+# the positions of all the objects, and to check if the bullets or the
+# ship have hit the asteroids.
+#
+# Note: This definitely a complicated example. Tasks are the cores of
+# most games so it seemed appropriate to show what a full game in Panda
+# could look like.
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import TextNode, TransparencyAttrib
+from panda3d.core import LPoint3, LVector3
+from direct.gui.OnscreenText import OnscreenText
+from direct.task.Task import Task
+from math import sin, cos, pi
+from random import randint, choice, random
+from direct.interval.MetaInterval import Sequence
+from direct.interval.FunctionInterval import Wait, Func
+import sys
+
+# Constants that will control the behavior of the game. It is good to
+# group constants like this so that they can be changed once without
+# having to find everywhere they are used in code
+SPRITE_POS = 55     # At default field of view and a depth of 55, the screen
+# dimensions is 40x30 units
+SCREEN_X = 20       # Screen goes from -20 to 20 on X
+SCREEN_Y = 15       # Screen goes from -15 to 15 on Y
+TURN_RATE = 360     # Degrees ship can turn in 1 second
+ACCELERATION = 10   # Ship acceleration in units/sec/sec
+MAX_VEL = 6         # Maximum ship velocity in units/sec
+MAX_VEL_SQ = MAX_VEL ** 2  # Square of the ship velocity
+DEG_TO_RAD = pi / 180  # translates degrees to radians for sin and cos
+BULLET_LIFE = 2     # How long bullets stay on screen before removed
+BULLET_REPEAT = .2  # How often bullets can be fired
+BULLET_SPEED = 10   # Speed bullets move
+AST_INIT_VEL = 1    # Velocity of the largest asteroids
+AST_INIT_SCALE = 3  # Initial asteroid scale
+AST_VEL_SCALE = 2.2  # How much asteroid speed multiplies when broken up
+AST_SIZE_SCALE = .6  # How much asteroid scale changes when broken up
+AST_MIN_SCALE = 1.1  # If and asteroid is smaller than this and is hit,
+# it disapears instead of splitting up
+
+
+# This helps reduce the amount of code used by loading objects, since all of
+# the objects are pretty much the same.
+def loadObject(tex=None, pos=LPoint3(0, 0), depth=SPRITE_POS, scale=1,
+               transparency=True):
+    # Every object uses the plane model and is parented to the camera
+    # so that it faces the screen.
+    obj = loader.loadModel("models/plane")
+    obj.reparentTo(camera)
+
+    # Set the initial position and scale.
+    obj.setPos(pos.getX(), depth, pos.getY())
+    obj.setScale(scale)
+
+    # This tells Panda not to worry about the order that things are drawn in
+    # (ie. disable Z-testing).  This prevents an effect known as Z-fighting.
+    obj.setBin("unsorted", 0)
+    obj.setDepthTest(False)
+
+    if transparency:
+        # Enable transparency blending.
+        obj.setTransparency(TransparencyAttrib.MAlpha)
+
+    if tex:
+        # Load and set the requested texture.
+        tex = loader.loadTexture("textures/" + tex)
+        obj.setTexture(tex, 1)
+
+    return obj
+
+
+# Macro-like function used to reduce the amount to code needed to create the
+# on screen instructions
+def genLabelText(text, i):
+    return OnscreenText(text=text, parent=base.a2dTopLeft, pos=(0.07, -.06 * i - 0.1),
+                        fg=(1, 1, 1, 1), align=TextNode.ALeft, shadow=(0, 0, 0, 0.5), scale=.05)
+
+
+class AsteroidsDemo(ShowBase):
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # This code puts the standard title and instruction text on screen
+        self.title = OnscreenText(text="Panda3D: Tutorial - Tasks",
+                                  parent=base.a2dBottomRight, scale=.07,
+                                  align=TextNode.ARight, pos=(-0.1, 0.1),
+                                  fg=(1, 1, 1, 1), shadow=(0, 0, 0, 0.5))
+        self.escapeText = genLabelText("ESC: Quit", 0)
+        self.leftkeyText = genLabelText("[Left Arrow]: Turn Left (CCW)", 1)
+        self.rightkeyText = genLabelText("[Right Arrow]: Turn Right (CW)", 2)
+        self.upkeyText = genLabelText("[Up Arrow]: Accelerate", 3)
+        self.spacekeyText = genLabelText("[Space Bar]: Fire", 4)
+
+        # Disable default mouse-based camera control.  This is a method on the
+        # ShowBase class from which we inherit.
+        self.disableMouse()
+
+        # Load the background starfield.
+        self.setBackgroundColor((0, 0, 0, 1))
+        self.bg = loadObject("stars.jpg", scale=146, depth=200,
+                             transparency=False)
+
+        # Load the ship and set its initial velocity.
+        self.ship = loadObject("ship.png")
+        self.setVelocity(self.ship, LVector3.zero())
+
+        # A dictionary of what keys are currently being pressed
+        # The key events update this list, and our task will query it as input
+        self.keys = {"turnLeft": 0, "turnRight": 0,
+                     "accel": 0, "fire": 0}
+
+        self.accept("escape", sys.exit)  # Escape quits
+        # Other keys events set the appropriate value in our key dictionary
+        self.accept("arrow_left",     self.setKey, ["turnLeft", 1])
+        self.accept("arrow_left-up",  self.setKey, ["turnLeft", 0])
+        self.accept("arrow_right",    self.setKey, ["turnRight", 1])
+        self.accept("arrow_right-up", self.setKey, ["turnRight", 0])
+        self.accept("arrow_up",       self.setKey, ["accel", 1])
+        self.accept("arrow_up-up",    self.setKey, ["accel", 0])
+        self.accept("space",          self.setKey, ["fire", 1])
+
+        # Now we create the task. taskMgr is the task manager that actually
+        # calls the function each frame. The add method creates a new task.
+        # The first argument is the function to be called, and the second
+        # argument is the name for the task.  It returns a task object which
+        # is passed to the function each frame.
+        self.gameTask = taskMgr.add(self.gameLoop, "gameLoop")
+
+        # Stores the time at which the next bullet may be fired.
+        self.nextBullet = 0.0
+
+        # This list will stored fired bullets.
+        self.bullets = []
+
+        # Complete initialization by spawning the asteroids.
+        self.spawnAsteroids()
+
+    # As described earlier, this simply sets a key in the self.keys dictionary
+    # to the given value.
+    def setKey(self, key, val):
+        self.keys[key] = val
+
+    def setVelocity(self, obj, val):
+        obj.setPythonTag("velocity", val)
+
+    def getVelocity(self, obj):
+        return obj.getPythonTag("velocity")
+
+    def setExpires(self, obj, val):
+        obj.setPythonTag("expires", val)
+
+    def getExpires(self, obj):
+        return obj.getPythonTag("expires")
+
+    def spawnAsteroids(self):
+        # Control variable for if the ship is alive
+        self.alive = True
+        self.asteroids = []  # List that will contain our asteroids
+
+        for i in range(10):
+            # This loads an asteroid. The texture chosen is random
+            # from "asteroid1.png" to "asteroid3.png".
+            asteroid = loadObject("asteroid%d.png" % (randint(1, 3)),
+                                  scale=AST_INIT_SCALE)
+            self.asteroids.append(asteroid)
+
+            # This is kind of a hack, but it keeps the asteroids from spawning
+            # near the player.  It creates the list (-20, -19 ... -5, 5, 6, 7,
+            # ... 20) and chooses a value from it. Since the player starts at 0
+            # and this list doesn't contain anything from -4 to 4, it won't be
+            # close to the player.
+            asteroid.setX(choice(range(-SCREEN_X, -5) + range(5, SCREEN_X)))
+            # Same thing for Y, but from -15 to 15
+            asteroid.setZ(choice(range(-SCREEN_Y, -5) + range(5, SCREEN_Y)))
+
+            # Heading is a random angle in radians
+            heading = random() * 2 * pi
+
+            # Converts the heading to a vector and multiplies it by speed to
+            # get a velocity vector
+            v = LVector3(sin(heading), 0, cos(heading)) * AST_INIT_VEL
+            self.setVelocity(self.asteroids[i], v)
+
+    # This is our main task function, which does all of the per-frame
+    # processing.  It takes in self like all functions in a class, and task,
+    # the task object returned by taskMgr.
+    def gameLoop(self, task):
+        # Get the time elapsed since the next frame.  We need this for our
+        # distance and velocity calculations.
+        dt = globalClock.getDt()
+
+        # If the ship is not alive, do nothing.  Tasks return Task.cont to
+        # signify that the task should continue running. If Task.done were
+        # returned instead, the task would be removed and would no longer be
+        # called every frame.
+        if not self.alive:
+            return Task.cont
+
+        # update ship position
+        self.updateShip(dt)
+
+        # check to see if the ship can fire
+        if self.keys["fire"] and task.time > self.nextBullet:
+            self.fire(task.time)  # If so, call the fire function
+            # And disable firing for a bit
+            self.nextBullet = task.time + BULLET_REPEAT
+        # Remove the fire flag until the next spacebar press
+        self.keys["fire"] = 0
+
+        # update asteroids
+        for obj in self.asteroids:
+            self.updatePos(obj, dt)
+
+        # update bullets
+        newBulletArray = []
+        for obj in self.bullets:
+            self.updatePos(obj, dt)  # Update the bullet
+            # Bullets have an experation time (see definition of fire)
+            # If a bullet has not expired, add it to the new bullet list so
+            # that it will continue to exist.
+            if self.getExpires(obj) > task.time:
+                newBulletArray.append(obj)
+            else:
+                obj.removeNode()  # Otherwise, remove it from the scene.
+        # Set the bullet array to be the newly updated array
+        self.bullets = newBulletArray
+
+        # Check bullet collision with asteroids
+        # In short, it checks every bullet against every asteroid. This is
+        # quite slow.  A big optimization would be to sort the objects left to
+        # right and check only if they overlap.  Framerate can go way down if
+        # there are many bullets on screen, but for the most part it's okay.
+        for bullet in self.bullets:
+            # This range statement makes it step though the asteroid list
+            # backwards.  This is because if an asteroid is removed, the
+            # elements after it will change position in the list.  If you go
+            # backwards, the length stays constant.
+            for i in range(len(self.asteroids) - 1, -1, -1):
+                asteroid = self.asteroids[i]
+                # Panda's collision detection is more complicated than we need
+                # here.  This is the basic sphere collision check. If the
+                # distance between the object centers is less than sum of the
+                # radii of the two objects, then we have a collision. We use
+                # lengthSquared() since it is faster than length().
+                if ((bullet.getPos() - asteroid.getPos()).lengthSquared() <
+                    (((bullet.getScale().getX() + asteroid.getScale().getX())
+                      * .5) ** 2)):
+                    # Schedule the bullet for removal
+                    self.setExpires(bullet, 0)
+                    self.asteroidHit(i)      # Handle the hit
+
+        # Now we do the same collision pass for the ship
+        shipSize = self.ship.getScale().getX()
+        for ast in self.asteroids:
+            # Same sphere collision check for the ship vs. the asteroid
+            if ((self.ship.getPos() - ast.getPos()).lengthSquared() <
+                    (((shipSize + ast.getScale().getX()) * .5) ** 2)):
+                # If there is a hit, clear the screen and schedule a restart
+                self.alive = False         # Ship is no longer alive
+                # Remove every object in asteroids and bullets from the scene
+                for i in self.asteroids + self.bullets:
+                    i.removeNode()
+                self.bullets = []          # Clear the bullet list
+                self.ship.hide()           # Hide the ship
+                # Reset the velocity
+                self.setVelocity(self.ship, LVector3(0, 0, 0))
+                Sequence(Wait(2),          # Wait 2 seconds
+                         Func(self.ship.setR, 0),  # Reset heading
+                         Func(self.ship.setX, 0),  # Reset position X
+                         # Reset position Y (Z for Panda)
+                         Func(self.ship.setZ, 0),
+                         Func(self.ship.show),     # Show the ship
+                         Func(self.spawnAsteroids)).start()  # Remake asteroids
+                return Task.cont
+
+        # If the player has successfully destroyed all asteroids, respawn them
+        if len(self.asteroids) == 0:
+            self.spawnAsteroids()
+
+        return Task.cont    # Since every return is Task.cont, the task will
+        # continue indefinitely
+
+    # Updates the positions of objects
+    def updatePos(self, obj, dt):
+        vel = self.getVelocity(obj)
+        newPos = obj.getPos() + (vel * dt)
+
+        # Check if the object is out of bounds. If so, wrap it
+        radius = .5 * obj.getScale().getX()
+        if newPos.getX() - radius > SCREEN_X:
+            newPos.setX(-SCREEN_X)
+        elif newPos.getX() + radius < -SCREEN_X:
+            newPos.setX(SCREEN_X)
+        if newPos.getZ() - radius > SCREEN_Y:
+            newPos.setZ(-SCREEN_Y)
+        elif newPos.getZ() + radius < -SCREEN_Y:
+            newPos.setZ(SCREEN_Y)
+
+        obj.setPos(newPos)
+
+    # The handler when an asteroid is hit by a bullet
+    def asteroidHit(self, index):
+        # If the asteroid is small it is simply removed
+        if self.asteroids[index].getScale().getX() <= AST_MIN_SCALE:
+            self.asteroids[index].removeNode()
+            # Remove the asteroid from the list of asteroids.
+            del self.asteroids[index]
+        else:
+            # If it is big enough, divide it up into little asteroids.
+            # First we update the current asteroid.
+            asteroid = self.asteroids[index]
+            newScale = asteroid.getScale().getX() * AST_SIZE_SCALE
+            asteroid.setScale(newScale)  # Rescale it
+
+            # The new direction is chosen as perpendicular to the old direction
+            # This is determined using the cross product, which returns a
+            # vector perpendicular to the two input vectors.  By crossing
+            # velocity with a vector that goes into the screen, we get a vector
+            # that is orthagonal to the original velocity in the screen plane.
+            vel = self.getVelocity(asteroid)
+            speed = vel.length() * AST_VEL_SCALE
+            vel.normalize()
+            vel = LVector3(0, 1, 0).cross(vel)
+            vel *= speed
+            self.setVelocity(asteroid, vel)
+
+            # Now we create a new asteroid identical to the current one
+            newAst = loadObject(scale=newScale)
+            self.setVelocity(newAst, vel * -1)
+            newAst.setPos(asteroid.getPos())
+            newAst.setTexture(asteroid.getTexture(), 1)
+            self.asteroids.append(newAst)
+
+    # This updates the ship's position. This is similar to the general update
+    # but takes into account turn and thrust
+    def updateShip(self, dt):
+        heading = self.ship.getR()  # Heading is the roll value for this model
+        # Change heading if left or right is being pressed
+        if self.keys["turnRight"]:
+            heading += dt * TURN_RATE
+            self.ship.setR(heading % 360)
+        elif self.keys["turnLeft"]:
+            heading -= dt * TURN_RATE
+            self.ship.setR(heading % 360)
+
+        # Thrust causes acceleration in the direction the ship is currently
+        # facing
+        if self.keys["accel"]:
+            heading_rad = DEG_TO_RAD * heading
+            # This builds a new velocity vector and adds it to the current one
+            # relative to the camera, the screen in Panda is the XZ plane.
+            # Therefore all of our Y values in our velocities are 0 to signify
+            # no change in that direction.
+            newVel = \
+                LVector3(sin(heading_rad), 0, cos(heading_rad)) * ACCELERATION * dt
+            newVel += self.getVelocity(self.ship)
+            # Clamps the new velocity to the maximum speed. lengthSquared() is
+            # used again since it is faster than length()
+            if newVel.lengthSquared() > MAX_VEL_SQ:
+                newVel.normalize()
+                newVel *= MAX_VEL
+            self.setVelocity(self.ship, newVel)
+
+        # Finally, update the position as with any other object
+        self.updatePos(self.ship, dt)
+
+    # Creates a bullet and adds it to the bullet list
+    def fire(self, time):
+        direction = DEG_TO_RAD * self.ship.getR()
+        pos = self.ship.getPos()
+        bullet = loadObject("bullet.png", scale=.2)  # Create the object
+        bullet.setPos(pos)
+        # Velocity is in relation to the ship
+        vel = (self.getVelocity(self.ship) +
+               (LVector3(sin(direction), 0, cos(direction)) *
+                BULLET_SPEED))
+        self.setVelocity(bullet, vel)
+        # Set the bullet expiration time to be a certain amount past the
+        # current time
+        self.setExpires(bullet, time + BULLET_LIFE)
+
+        # Finally, add the new bullet to the list
+        self.bullets.append(bullet)
+
+# We now have everything we need. Make an instance of the class and start
+# 3D rendering
+demo = AsteroidsDemo()
+demo.run()

+ 39 - 0
samples/asteroids/models/plane.egg

@@ -0,0 +1,39 @@
+<CoordinateSystem> { Y-Up }
+
+<Comment> {
+  "maya2egg plane.mb plane.egg"
+}
+<Group> groundPlane_transform {
+}
+<Group> pPlane1 {
+  <VertexPool> pPlaneShape1.verts {
+    <Vertex> 1 {
+      -0.5 -0.5 0
+      <Normal> { 0 0 -1 }
+      <UV> { 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -0.5 0.5 0
+      <Normal> { 0 0 -1 }
+      <UV> { 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      0.5 -0.5 0
+      <Normal> { 0 0 -1 }
+      <UV> { 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      0.5 0.5 0
+      <Normal> { 0 0 -1 }
+      <UV> { 1 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <Normal> { 0 0 -1 }
+    <VertexRef>{ 3 4 2 1 <Ref> { pPlaneShape1.verts } }
+  }
+}

BIN
samples/asteroids/textures/asteroid1.png


BIN
samples/asteroids/textures/asteroid2.png


BIN
samples/asteroids/textures/asteroid3.png


BIN
samples/asteroids/textures/bullet.png


BIN
samples/asteroids/textures/ship.png


BIN
samples/asteroids/textures/stars.jpg


+ 338 - 0
samples/ball-in-maze/main.py

@@ -0,0 +1,338 @@
+#!/usr/bin/env python
+
+# Author: Shao Zhang, Phil Saltzman
+# Last Updated: 2015-03-13
+#
+# This tutorial shows how to detect and respond to collisions. It uses solids
+# create in code and the egg files, how to set up collision masks, a traverser,
+# and a handler, how to detect collisions, and how to dispatch function based
+# on the collisions. All of this is put together to simulate a labyrinth-style
+# game
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import CollisionTraverser, CollisionNode
+from panda3d.core import CollisionHandlerQueue, CollisionRay
+from panda3d.core import Material, LRotationf, NodePath
+from panda3d.core import AmbientLight, DirectionalLight
+from panda3d.core import TextNode
+from panda3d.core import LVector3, BitMask32
+from direct.gui.OnscreenText import OnscreenText
+from direct.interval.MetaInterval import Sequence, Parallel
+from direct.interval.LerpInterval import LerpFunc
+from direct.interval.FunctionInterval import Func, Wait
+from direct.task.Task import Task
+import sys
+
+# Some constants for the program
+ACCEL = 70         # Acceleration in ft/sec/sec
+MAX_SPEED = 5      # Max speed in ft/sec
+MAX_SPEED_SQ = MAX_SPEED ** 2  # Squared to make it easier to use lengthSquared
+# Instead of length
+
+
+class BallInMazeDemo(ShowBase):
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # This code puts the standard title and instruction text on screen
+        self.title = \
+            OnscreenText(text="Panda3D: Tutorial - Collision Detection",
+                         parent=base.a2dBottomRight, align=TextNode.ARight,
+                         fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.08,
+                         shadow=(0, 0, 0, 0.5))
+        self.instructions = \
+            OnscreenText(text="Mouse pointer tilts the board",
+                         parent=base.a2dTopLeft, align=TextNode.ALeft,
+                         pos=(0.05, -0.08), fg=(1, 1, 1, 1), scale=.06,
+                         shadow=(0, 0, 0, 0.5))
+
+        self.accept("escape", sys.exit)  # Escape quits
+
+        # Disable default mouse-based camera control.  This is a method on the
+        # ShowBase class from which we inherit.
+        self.disableMouse()
+        camera.setPosHpr(0, 0, 25, 0, -90, 0)  # Place the camera
+
+        # Load the maze and place it in the scene
+        self.maze = loader.loadModel("models/maze")
+        self.maze.reparentTo(render)
+
+        # Most times, you want collisions to be tested against invisible geometry
+        # rather than every polygon. This is because testing against every polygon
+        # in the scene is usually too slow. You can have simplified or approximate
+        # geometry for the solids and still get good results.
+        #
+        # Sometimes you'll want to create and position your own collision solids in
+        # code, but it's often easier to have them built automatically. This can be
+        # done by adding special tags into an egg file. Check maze.egg and ball.egg
+        # and look for lines starting with <Collide>. The part is brackets tells
+        # Panda exactly what to do. Polyset means to use the polygons in that group
+        # as solids, while Sphere tells panda to make a collision sphere around them
+        # Keep means to keep the polygons in the group as visable geometry (good
+        # for the ball, not for the triggers), and descend means to make sure that
+        # the settings are applied to any subgroups.
+        #
+        # Once we have the collision tags in the models, we can get to them using
+        # NodePath's find command
+
+        # Find the collision node named wall_collide
+        self.walls = self.maze.find("**/wall_collide")
+
+        # Collision objects are sorted using BitMasks. BitMasks are ordinary numbers
+        # with extra methods for working with them as binary bits. Every collision
+        # solid has both a from mask and an into mask. Before Panda tests two
+        # objects, it checks to make sure that the from and into collision masks
+        # have at least one bit in common. That way things that shouldn't interact
+        # won't. Normal model nodes have collision masks as well. By default they
+        # are set to bit 20. If you want to collide against actual visable polygons,
+        # set a from collide mask to include bit 20
+        #
+        # For this example, we will make everything we want the ball to collide with
+        # include bit 0
+        self.walls.node().setIntoCollideMask(BitMask32.bit(0))
+        # CollisionNodes are usually invisible but can be shown. Uncomment the next
+        # line to see the collision walls
+        #self.walls.show()
+
+        # We will now find the triggers for the holes and set their masks to 0 as
+        # well. We also set their names to make them easier to identify during
+        # collisions
+        self.loseTriggers = []
+        for i in range(6):
+            trigger = self.maze.find("**/hole_collide" + str(i))
+            trigger.node().setIntoCollideMask(BitMask32.bit(0))
+            trigger.node().setName("loseTrigger")
+            self.loseTriggers.append(trigger)
+            # Uncomment this line to see the triggers
+            # trigger.show()
+
+        # Ground_collide is a single polygon on the same plane as the ground in the
+        # maze. We will use a ray to collide with it so that we will know exactly
+        # what height to put the ball at every frame. Since this is not something
+        # that we want the ball itself to collide with, it has a different
+        # bitmask.
+        self.mazeGround = self.maze.find("**/ground_collide")
+        self.mazeGround.node().setIntoCollideMask(BitMask32.bit(1))
+
+        # Load the ball and attach it to the scene
+        # It is on a root dummy node so that we can rotate the ball itself without
+        # rotating the ray that will be attached to it
+        self.ballRoot = render.attachNewNode("ballRoot")
+        self.ball = loader.loadModel("models/ball")
+        self.ball.reparentTo(self.ballRoot)
+
+        # Find the collison sphere for the ball which was created in the egg file
+        # Notice that it has a from collision mask of bit 0, and an into collison
+        # mask of no bits. This means that the ball can only cause collisions, not
+        # be collided into
+        self.ballSphere = self.ball.find("**/ball")
+        self.ballSphere.node().setFromCollideMask(BitMask32.bit(0))
+        self.ballSphere.node().setIntoCollideMask(BitMask32.allOff())
+
+        # No we create a ray to start above the ball and cast down. This is to
+        # Determine the height the ball should be at and the angle the floor is
+        # tilting. We could have used the sphere around the ball itself, but it
+        # would not be as reliable
+        self.ballGroundRay = CollisionRay()     # Create the ray
+        self.ballGroundRay.setOrigin(0, 0, 10)    # Set its origin
+        self.ballGroundRay.setDirection(0, 0, -1)  # And its direction
+        # Collision solids go in CollisionNode
+        # Create and name the node
+        self.ballGroundCol = CollisionNode('groundRay')
+        self.ballGroundCol.addSolid(self.ballGroundRay)  # Add the ray
+        self.ballGroundCol.setFromCollideMask(
+            BitMask32.bit(1))  # Set its bitmasks
+        self.ballGroundCol.setIntoCollideMask(BitMask32.allOff())
+        # Attach the node to the ballRoot so that the ray is relative to the ball
+        # (it will always be 10 feet over the ball and point down)
+        self.ballGroundColNp = self.ballRoot.attachNewNode(self.ballGroundCol)
+        # Uncomment this line to see the ray
+        #self.ballGroundColNp.show()
+
+        # Finally, we create a CollisionTraverser. CollisionTraversers are what
+        # do the job of walking the scene graph and calculating collisions.
+        # For a traverser to actually do collisions, you need to call
+        # traverser.traverse() on a part of the scene. Fortunately, ShowBase
+        # has a task that does this for the entire scene once a frame.  By
+        # assigning it to self.cTrav, we designate that this is the one that
+        # it should call traverse() on each frame.
+        self.cTrav = CollisionTraverser()
+
+        # Collision traversers tell collision handlers about collisions, and then
+        # the handler decides what to do with the information. We are using a
+        # CollisionHandlerQueue, which simply creates a list of all of the
+        # collisions in a given pass. There are more sophisticated handlers like
+        # one that sends events and another that tries to keep collided objects
+        # apart, but the results are often better with a simple queue
+        self.cHandler = CollisionHandlerQueue()
+        # Now we add the collision nodes that can create a collision to the
+        # traverser. The traverser will compare these to all others nodes in the
+        # scene. There is a limit of 32 CollisionNodes per traverser
+        # We add the collider, and the handler to use as a pair
+        self.cTrav.addCollider(self.ballSphere, self.cHandler)
+        self.cTrav.addCollider(self.ballGroundColNp, self.cHandler)
+
+        # Collision traversers have a built in tool to help visualize collisions.
+        # Uncomment the next line to see it.
+        #self.cTrav.showCollisions(render)
+
+        # This section deals with lighting for the ball. Only the ball was lit
+        # because the maze has static lighting pregenerated by the modeler
+        ambientLight = AmbientLight("ambientLight")
+        ambientLight.setColor((.55, .55, .55, 1))
+        directionalLight = DirectionalLight("directionalLight")
+        directionalLight.setDirection(LVector3(0, 0, -1))
+        directionalLight.setColor((0.375, 0.375, 0.375, 1))
+        directionalLight.setSpecularColor((1, 1, 1, 1))
+        self.ballRoot.setLight(render.attachNewNode(ambientLight))
+        self.ballRoot.setLight(render.attachNewNode(directionalLight))
+
+        # This section deals with adding a specular highlight to the ball to make
+        # it look shiny.  Normally, this is specified in the .egg file.
+        m = Material()
+        m.setSpecular((1, 1, 1, 1))
+        m.setShininess(96)
+        self.ball.setMaterial(m, 1)
+
+        # Finally, we call start for more initialization
+        self.start()
+
+    def start(self):
+        # The maze model also has a locator in it for where to start the ball
+        # To access it we use the find command
+        startPos = self.maze.find("**/start").getPos()
+        # Set the ball in the starting position
+        self.ballRoot.setPos(startPos)
+        self.ballV = LVector3(0, 0, 0)         # Initial velocity is 0
+        self.accelV = LVector3(0, 0, 0)        # Initial acceleration is 0
+
+        # Create the movement task, but first make sure it is not already
+        # running
+        taskMgr.remove("rollTask")
+        self.mainLoop = taskMgr.add(self.rollTask, "rollTask")
+
+    # This function handles the collision between the ray and the ground
+    # Information about the interaction is passed in colEntry
+    def groundCollideHandler(self, colEntry):
+        # Set the ball to the appropriate Z value for it to be exactly on the
+        # ground
+        newZ = colEntry.getSurfacePoint(render).getZ()
+        self.ballRoot.setZ(newZ + .4)
+
+        # Find the acceleration direction. First the surface normal is crossed with
+        # the up vector to get a vector perpendicular to the slope
+        norm = colEntry.getSurfaceNormal(render)
+        accelSide = norm.cross(LVector3.up())
+        # Then that vector is crossed with the surface normal to get a vector that
+        # points down the slope. By getting the acceleration in 3D like this rather
+        # than in 2D, we reduce the amount of error per-frame, reducing jitter
+        self.accelV = norm.cross(accelSide)
+
+    # This function handles the collision between the ball and a wall
+    def wallCollideHandler(self, colEntry):
+        # First we calculate some numbers we need to do a reflection
+        norm = colEntry.getSurfaceNormal(render) * -1  # The normal of the wall
+        curSpeed = self.ballV.length()                # The current speed
+        inVec = self.ballV / curSpeed                 # The direction of travel
+        velAngle = norm.dot(inVec)                    # Angle of incidance
+        hitDir = colEntry.getSurfacePoint(render) - self.ballRoot.getPos()
+        hitDir.normalize()
+        # The angle between the ball and the normal
+        hitAngle = norm.dot(hitDir)
+
+        # Ignore the collision if the ball is either moving away from the wall
+        # already (so that we don't accidentally send it back into the wall)
+        # and ignore it if the collision isn't dead-on (to avoid getting caught on
+        # corners)
+        if velAngle > 0 and hitAngle > .995:
+            # Standard reflection equation
+            reflectVec = (norm * norm.dot(inVec * -1) * 2) + inVec
+
+            # This makes the velocity half of what it was if the hit was dead-on
+            # and nearly exactly what it was if this is a glancing blow
+            self.ballV = reflectVec * (curSpeed * (((1 - velAngle) * .5) + .5))
+            # Since we have a collision, the ball is already a little bit buried in
+            # the wall. This calculates a vector needed to move it so that it is
+            # exactly touching the wall
+            disp = (colEntry.getSurfacePoint(render) -
+                    colEntry.getInteriorPoint(render))
+            newPos = self.ballRoot.getPos() + disp
+            self.ballRoot.setPos(newPos)
+
+    # This is the task that deals with making everything interactive
+    def rollTask(self, task):
+        # Standard technique for finding the amount of time since the last
+        # frame
+        dt = globalClock.getDt()
+
+        # If dt is large, then there has been a # hiccup that could cause the ball
+        # to leave the field if this functions runs, so ignore the frame
+        if dt > .2:
+            return Task.cont
+
+        # The collision handler collects the collisions. We dispatch which function
+        # to handle the collision based on the name of what was collided into
+        for i in range(self.cHandler.getNumEntries()):
+            entry = self.cHandler.getEntry(i)
+            name = entry.getIntoNode().getName()
+            if name == "wall_collide":
+                self.wallCollideHandler(entry)
+            elif name == "ground_collide":
+                self.groundCollideHandler(entry)
+            elif name == "loseTrigger":
+                self.loseGame(entry)
+
+        # Read the mouse position and tilt the maze accordingly
+        if base.mouseWatcherNode.hasMouse():
+            mpos = base.mouseWatcherNode.getMouse()  # get the mouse position
+            self.maze.setP(mpos.getY() * -10)
+            self.maze.setR(mpos.getX() * 10)
+
+        # Finally, we move the ball
+        # Update the velocity based on acceleration
+        self.ballV += self.accelV * dt * ACCEL
+        # Clamp the velocity to the maximum speed
+        if self.ballV.lengthSquared() > MAX_SPEED_SQ:
+            self.ballV.normalize()
+            self.ballV *= MAX_SPEED
+        # Update the position based on the velocity
+        self.ballRoot.setPos(self.ballRoot.getPos() + (self.ballV * dt))
+
+        # This block of code rotates the ball. It uses something called a quaternion
+        # to rotate the ball around an arbitrary axis. That axis perpendicular to
+        # the balls rotation, and the amount has to do with the size of the ball
+        # This is multiplied on the previous rotation to incrimentally turn it.
+        prevRot = LRotationf(self.ball.getQuat())
+        axis = LVector3.up().cross(self.ballV)
+        newRot = LRotationf(axis, 45.5 * dt * self.ballV.length())
+        self.ball.setQuat(prevRot * newRot)
+
+        return Task.cont       # Continue the task indefinitely
+
+    # If the ball hits a hole trigger, then it should fall in the hole.
+    # This is faked rather than dealing with the actual physics of it.
+    def loseGame(self, entry):
+        # The triggers are set up so that the center of the ball should move to the
+        # collision point to be in the hole
+        toPos = entry.getInteriorPoint(render)
+        taskMgr.remove('rollTask')  # Stop the maze task
+
+        # Move the ball into the hole over a short sequence of time. Then wait a
+        # second and call start to reset the game
+        Sequence(
+            Parallel(
+                LerpFunc(self.ballRoot.setX, fromData=self.ballRoot.getX(),
+                         toData=toPos.getX(), duration=.1),
+                LerpFunc(self.ballRoot.setY, fromData=self.ballRoot.getY(),
+                         toData=toPos.getY(), duration=.1),
+                LerpFunc(self.ballRoot.setZ, fromData=self.ballRoot.getZ(),
+                         toData=self.ballRoot.getZ() - .9, duration=.2)),
+            Wait(1),
+            Func(self.start)).start()
+
+# Finally, create an instance of our class and start 3d rendering
+demo = BallInMazeDemo()
+demo.run()

BIN
samples/ball-in-maze/models/ball.egg.pz


BIN
samples/ball-in-maze/models/iron05.jpg


BIN
samples/ball-in-maze/models/limba.jpg


BIN
samples/ball-in-maze/models/maze.egg.pz


+ 199 - 0
samples/boxing-robots/main.py

@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+
+# Author: Shao Zhang, Phil Saltzman, and Eddie Caanan
+# Last Updated: 2015-03-13
+#
+# This tutorial shows how to play animations on models aka "actors".
+# It is based on the popular game of "Rock 'em Sock 'em Robots".
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import AmbientLight, DirectionalLight
+from panda3d.core import TextNode
+from panda3d.core import LVector3
+from direct.gui.OnscreenText import OnscreenText
+from direct.interval.MetaInterval import Sequence
+from direct.interval.FunctionInterval import Func, Wait
+from direct.actor import Actor
+from random import random
+import sys
+
+
+class BoxingRobotDemo(ShowBase):
+    # Macro-like function used to reduce the amount to code needed to create the
+    # on screen instructions
+
+    def genLabelText(self, text, i):
+        return OnscreenText(text=text, parent=base.a2dTopLeft, scale=.05,
+                            pos=(0.1, - 0.1 -.07 * i), fg=(1, 1, 1, 1),
+                            align=TextNode.ALeft)
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # This code puts the standard title and instruction text on screen
+        self.title = OnscreenText(text="Panda3D: Tutorial - Actors",
+                                  parent=base.a2dBottomRight, style=1,
+                                  fg=(0, 0, 0, 1), pos=(-0.2, 0.1),
+                                  align=TextNode.ARight, scale=.09)
+
+        self.escapeEventText = self.genLabelText("ESC: Quit", 0)
+        self.akeyEventText = self.genLabelText("[A]: Robot 1 Left Punch", 1)
+        self.skeyEventText = self.genLabelText("[S]: Robot 1 Right Punch", 2)
+        self.kkeyEventText = self.genLabelText("[K]: Robot 2 Left Punch", 3)
+        self.lkeyEventText = self.genLabelText("[L]: Robot 2 Right Punch", 4)
+
+        # Set the camera in a fixed position
+        self.disableMouse()
+        camera.setPosHpr(14.5, -15.4, 14, 45, -14, 0)
+        self.setBackgroundColor(0, 0, 0)
+
+        # Add lighting so that the objects are not drawn flat
+        self.setupLights()
+
+        # Load the ring
+        self.ring = loader.loadModel('models/ring')
+        self.ring.reparentTo(render)
+
+        # Models that use skeletal animation are known as Actors instead of models
+        # Instead of just one file, the have one file for the main model, and an
+        # additional file for each playable animation.
+        # They are loaded using Actor.Actor instead of loader.LoadModel.
+        # The constructor takes the location of the main object as with a normal model
+        # and a dictionary (A fancy python structure that is like a lookup table)
+        # that contains names for animations, and paths to the appropriate
+        # files
+        self.robot1 = Actor.Actor('models/robot',
+                                  {'leftPunch': 'models/robot_left_punch',
+                                   'rightPunch': 'models/robot_right_punch',
+                                   'headUp': 'models/robot_head_up',
+                                   'headDown': 'models/robot_head_down'})
+
+        # Actors need to be positioned and parented like normal objects
+        self.robot1.setPosHprScale(-1, -2.5, 4, 45, 0, 0, 1.25, 1.25, 1.25)
+        self.robot1.reparentTo(render)
+
+        # We'll repeat the process for the second robot. The only thing that changes
+        # here is the robot's color and position
+        self.robot2 = Actor.Actor('models/robot',
+                                  {'leftPunch': 'models/robot_left_punch',
+                                   'rightPunch': 'models/robot_right_punch',
+                                   'headUp': 'models/robot_head_up',
+                                   'headDown': 'models/robot_head_down'})
+
+        # Set the properties of this robot
+        self.robot2.setPosHprScale(1, 1.5, 4, 225, 0, 0, 1.25, 1.25, 1.25)
+        self.robot2.setColor((.7, 0, 0, 1))
+        self.robot2.reparentTo(render)
+
+        # Now we define how the animated models will move. Animations are played
+        # through special intervals. In this case we use actor intervals in a
+        # sequence to play the part of the punch animation where the arm extends,
+        # call a function to check if the punch landed, and then play the part of the
+        # animation where the arm retracts
+
+        # Punch sequence for robot 1's left arm
+        self.robot1.punchLeft = Sequence(
+            # Interval for the outstreched animation
+            self.robot1.actorInterval('leftPunch', startFrame=1, endFrame=10),
+            # Function to check if the punch was successful
+            Func(self.checkPunch, 2),
+            # Interval for the retract animation
+            self.robot1.actorInterval('leftPunch', startFrame=11, endFrame=32))
+
+        # Punch sequence for robot 1's right arm
+        self.robot1.punchRight = Sequence(
+            self.robot1.actorInterval('rightPunch', startFrame=1, endFrame=10),
+            Func(self.checkPunch, 2),
+            self.robot1.actorInterval('rightPunch', startFrame=11, endFrame=32))
+
+        # Punch sequence for robot 2's left arm
+        self.robot2.punchLeft = Sequence(
+            self.robot2.actorInterval('leftPunch', startFrame=1, endFrame=10),
+            Func(self.checkPunch, 1),
+            self.robot2.actorInterval('leftPunch', startFrame=11, endFrame=32))
+
+        # Punch sequence for robot 2's right arm
+        self.robot2.punchRight = Sequence(
+            self.robot2.actorInterval('rightPunch', startFrame=1, endFrame=10),
+            Func(self.checkPunch, 1),
+            self.robot2.actorInterval('rightPunch', startFrame=11, endFrame=32))
+
+        # We use the same techinique to create a sequence for when a robot is knocked
+        # out where the head pops up, waits a while, and then resets
+
+        # Head animation for robot 1
+        self.robot1.resetHead = Sequence(
+            # Interval for the head going up. Since no start or end frames were given,
+            # the entire animation is played.
+            self.robot1.actorInterval('headUp'),
+            Wait(1.5),
+            # The head down animation was animated a little too quickly, so this will
+            # play it at 75% of it's normal speed
+            self.robot1.actorInterval('headDown', playRate=.75))
+
+        # Head animation for robot 2
+        self.robot2.resetHead = Sequence(
+            self.robot2.actorInterval('headUp'),
+            Wait(1.5),
+            self.robot2.actorInterval('headDown', playRate=.75))
+
+        # Now that we have defined the motion, we can define our key input.
+        # Each fist is bound to a key. When a key is pressed, self.tryPunch checks to
+        # make sure that the both robots have their heads down, and if they do it
+        # plays the given interval
+        self.accept('escape', sys.exit)
+        self.accept('a', self.tryPunch, [self.robot1.punchLeft])
+        self.accept('s', self.tryPunch, [self.robot1.punchRight])
+        self.accept('k', self.tryPunch, [self.robot2.punchLeft])
+        self.accept('l', self.tryPunch, [self.robot2.punchRight])
+
+    # tryPunch will play the interval passed to it only if
+    # neither robot has 'resetHead' playing (a head is up) AND
+    # the punch interval passed to it is not already playing
+    def tryPunch(self, interval):
+        if (not self.robot1.resetHead.isPlaying() and
+                not self.robot2.resetHead.isPlaying() and
+                not interval.isPlaying()):
+            interval.start()
+
+    # checkPunch will determine if a successful punch has been thrown
+    def checkPunch(self, robot):
+        if robot == 1:
+            # punch is directed to robot 1
+            # if robot 1 is playing'resetHead', do nothing
+            if self.robot1.resetHead.isPlaying():
+                return
+            # if robot 1 is not punching...
+            if (not self.robot1.punchLeft.isPlaying() and
+                    not self.robot1.punchRight.isPlaying()):
+                # ...15% chance of successful hit
+                if random() > .85:
+                    self.robot1.resetHead.start()
+            # Otherwise, only 5% chance of sucessful hit
+            elif random() > .95:
+                self.robot1.resetHead.start()
+        else:
+            # punch is directed to robot 2, same as above
+            if self.robot2.resetHead.isPlaying():
+                return
+            if (not self.robot2.punchLeft.isPlaying() and
+                    not self.robot2.punchRight.isPlaying()):
+                if random() > .85:
+                    self.robot2.resetHead.start()
+            elif random() > .95:
+                self.robot2.resetHead.start()
+
+    # This function sets up the lighting
+    def setupLights(self):
+        ambientLight = AmbientLight("ambientLight")
+        ambientLight.setColor((.8, .8, .75, 1))
+        directionalLight = DirectionalLight("directionalLight")
+        directionalLight.setDirection(LVector3(0, 0, -2.5))
+        directionalLight.setColor((0.9, 0.8, 0.9, 1))
+        render.setLight(render.attachNewNode(ambientLight))
+        render.setLight(render.attachNewNode(directionalLight))
+
+demo = BoxingRobotDemo()
+demo.run()

BIN
samples/boxing-robots/models/ring.egg.pz


BIN
samples/boxing-robots/models/robot.egg.pz


BIN
samples/boxing-robots/models/robot_head_down.egg.pz


BIN
samples/boxing-robots/models/robot_head_up.egg.pz


BIN
samples/boxing-robots/models/robot_left_punch.egg.pz


BIN
samples/boxing-robots/models/robot_right_punch.egg.pz


+ 188 - 0
samples/bump-mapping/main.py

@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+#
+# Bump mapping is a way of making polygonal surfaces look
+# less flat.  This sample uses normal mapping for all
+# surfaces, and also parallax mapping for the column.
+#
+# This is a tutorial to show how to do normal mapping
+# in panda3d using the Shader Generator.
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import loadPrcFileData
+from panda3d.core import WindowProperties
+from panda3d.core import Filename, Shader
+from panda3d.core import AmbientLight, PointLight
+from panda3d.core import TextNode
+from panda3d.core import LPoint3, LVector3
+from direct.task.Task import Task
+from direct.actor.Actor import Actor
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from direct.filter.CommonFilters import *
+import sys
+import os
+
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), scale=.05,
+                        shadow=(0, 0, 0, 1), parent=base.a2dTopLeft,
+                        pos=(0.08, -pos - 0.04), align=TextNode.ALeft)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        pos=(-0.1, 0.09), shadow=(0, 0, 0, 1))
+
+
+class BumpMapDemo(ShowBase):
+
+    def __init__(self):
+        # Configure the parallax mapping settings (these are just the defaults)
+        loadPrcFileData("", "parallax-mapping-samples 3\n"
+                            "parallax-mapping-scale 0.1")
+
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # Check video card capabilities.
+        if not self.win.getGsg().getSupportsBasicShaders():
+            addTitle("Bump Mapping: "
+                "Video driver reports that Cg shaders are not supported.")
+            return
+
+        # Post the instructions
+        self.title = addTitle("Panda3D: Tutorial - Bump Mapping")
+        self.inst1 = addInstructions(0.06, "Press ESC to exit")
+        self.inst2 = addInstructions(0.12, "Move mouse to rotate camera")
+        self.inst3 = addInstructions(0.18, "Left mouse button: Move forwards")
+        self.inst4 = addInstructions(0.24, "Right mouse button: Move backwards")
+        self.inst5 = addInstructions(0.30, "Enter: Turn bump maps Off")
+
+        # Load the 'abstract room' model.  This is a model of an
+        # empty room containing a pillar, a pyramid, and a bunch
+        # of exaggeratedly bumpy textures.
+
+        self.room = loader.loadModel("models/abstractroom")
+        self.room.reparentTo(render)
+
+        # Make the mouse invisible, turn off normal mouse controls
+        self.disableMouse()
+        props = WindowProperties()
+        props.setCursorHidden(True)
+        self.win.requestProperties(props)
+        self.camLens.setFov(60)
+
+        # Set the current viewing target
+        self.focus = LVector3(55, -55, 20)
+        self.heading = 180
+        self.pitch = 0
+        self.mousex = 0
+        self.mousey = 0
+        self.last = 0
+        self.mousebtn = [0, 0, 0]
+
+        # Start the camera control task:
+        taskMgr.add(self.controlCamera, "camera-task")
+        self.accept("escape", sys.exit, [0])
+        self.accept("mouse1", self.setMouseBtn, [0, 1])
+        self.accept("mouse1-up", self.setMouseBtn, [0, 0])
+        self.accept("mouse2", self.setMouseBtn, [1, 1])
+        self.accept("mouse2-up", self.setMouseBtn, [1, 0])
+        self.accept("mouse3", self.setMouseBtn, [2, 1])
+        self.accept("mouse3-up", self.setMouseBtn, [2, 0])
+        self.accept("enter", self.toggleShader)
+        self.accept("j", self.rotateLight, [-1])
+        self.accept("k", self.rotateLight, [1])
+        self.accept("arrow_left", self.rotateCam, [-1])
+        self.accept("arrow_right", self.rotateCam, [1])
+
+        # Add a light to the scene.
+        self.lightpivot = render.attachNewNode("lightpivot")
+        self.lightpivot.setPos(0, 0, 25)
+        self.lightpivot.hprInterval(10, LPoint3(360, 0, 0)).loop()
+        plight = PointLight('plight')
+        plight.setColor((1, 1, 1, 1))
+        plight.setAttenuation(LVector3(0.7, 0.05, 0))
+        plnp = self.lightpivot.attachNewNode(plight)
+        plnp.setPos(45, 0, 0)
+        self.room.setLight(plnp)
+
+        # Add an ambient light
+        alight = AmbientLight('alight')
+        alight.setColor((0.2, 0.2, 0.2, 1))
+        alnp = render.attachNewNode(alight)
+        self.room.setLight(alnp)
+
+        # Create a sphere to denote the light
+        sphere = loader.loadModel("models/icosphere")
+        sphere.reparentTo(plnp)
+
+        # Tell Panda that it should generate shaders performing per-pixel
+        # lighting for the room.
+        self.room.setShaderAuto()
+
+        self.shaderenable = 1
+
+    def setMouseBtn(self, btn, value):
+        self.mousebtn[btn] = value
+
+    def rotateLight(self, offset):
+        self.lightpivot.setH(self.lightpivot.getH() + offset * 20)
+
+    def rotateCam(self, offset):
+        self.heading = self.heading - offset * 10
+
+    def toggleShader(self):
+        self.inst5.destroy()
+        if (self.shaderenable):
+            self.inst5 = addInstructions(0.30, "Enter: Turn bump maps On")
+            self.shaderenable = 0
+            self.room.setShaderOff()
+        else:
+            self.inst5 = addInstructions(0.30, "Enter: Turn bump maps Off")
+            self.shaderenable = 1
+            self.room.setShaderAuto()
+
+    def controlCamera(self, task):
+        # figure out how much the mouse has moved (in pixels)
+        md = self.win.getPointer(0)
+        x = md.getX()
+        y = md.getY()
+        if self.win.movePointer(0, 100, 100):
+            self.heading = self.heading - (x - 100) * 0.2
+            self.pitch = self.pitch - (y - 100) * 0.2
+        if self.pitch < -45:
+            self.pitch = -45
+        if self.pitch > 45:
+            self.pitch = 45
+        self.camera.setHpr(self.heading, self.pitch, 0)
+        dir = self.camera.getMat().getRow3(1)
+        elapsed = task.time - self.last
+        if self.last == 0:
+            elapsed = 0
+        if self.mousebtn[0]:
+            self.focus = self.focus + dir * elapsed * 30
+        if self.mousebtn[1] or self.mousebtn[2]:
+            self.focus = self.focus - dir * elapsed * 30
+        self.camera.setPos(self.focus - (dir * 5))
+        if self.camera.getX() < -59.0:
+            self.camera.setX(-59)
+        if self.camera.getX() > 59.0:
+            self.camera.setX(59)
+        if self.camera.getY() < -59.0:
+            self.camera.setY(-59)
+        if self.camera.getY() > 59.0:
+            self.camera.setY(59)
+        if self.camera.getZ() < 5.0:
+            self.camera.setZ(5)
+        if self.camera.getZ() > 45.0:
+            self.camera.setZ(45)
+        self.focus = self.camera.getPos() + (dir * 5)
+        self.last = task.time
+        return Task.cont
+
+demo = BumpMapDemo()
+demo.run()

+ 1671 - 0
samples/bump-mapping/models/abstractroom.egg

@@ -0,0 +1,1671 @@
+<CoordinateSystem> { Y-Up }
+
+<Comment> {
+  "maya2egg85 -o room.egg room.mb"
+}
+<Texture> phong3SG.tref3 {
+  layingrock-n.jpg
+  <Scalar> format { rgba }
+  <Scalar> alpha-file { layingrock-h.jpg }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+  <Scalar> envtype { normal_height }
+}
+<Texture> phong3SG {
+  layingrock-c.jpg
+  <Scalar> format { rgb }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+}
+<Texture> phong2SG.tref1 {
+  brick-n.jpg
+  <Scalar> format { rgb }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+  <Scalar> envtype { normal }
+}
+<Texture> phong2SG {
+  brick-c.jpg
+  <Scalar> format { rgb }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+}
+<Texture> phong1SG.tref2 {
+  fieldstone-n.jpg
+  <Scalar> format { rgb }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+  <Scalar> envtype { normal }
+}
+<Texture> phong1SG {
+  fieldstone-c.jpg
+  <Scalar> format { rgb }
+  <Scalar> wrapu { repeat }
+  <Scalar> wrapv { repeat }
+  <Scalar> minfilter { linear_mipmap_linear }
+  <Scalar> magfilter { linear }
+}
+<Group> groundPlane_transform {
+}
+<Group> polySurface2 {
+  <VertexPool> polySurfaceShape2.verts {
+    <Vertex> 0 {
+      -60 0 -60
+      <UV> {
+        3 3
+        <Tangent> { 0 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 0 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 1 {
+      -60 0 -60
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -60 0 -60
+      <UV> {
+        1.5 -0.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -60 0 60
+      <UV> {
+        1.5 -0.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      -60 0 60
+      <UV> {
+        -2 3
+        <Tangent> { -5.15653e-008 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 5.15653e-008 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      -60 0 60
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 6 {
+      -60 50 -60
+      <UV> {
+        -0.5 1.5
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 7 {
+      -60 50 -60
+      <UV> {
+        1.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 8 {
+      -60 50 60
+      <UV> {
+        1.5 1.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 9 {
+      -60 50 60
+      <UV> {
+        -0.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 10 {
+      -37.5 0 25
+      <UV> {
+        -1.49994 -1
+        <Tangent> { 0 0 1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 11 {
+      -37.5 0 25
+      <UV> {
+        2.50006 -1
+        <Tangent> { 0 0 1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 12 {
+      -37.5 50 25
+      <UV> {
+        -1.49994 2
+        <Tangent> { 0 0 1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 13 {
+      -37.5 50 25
+      <UV> {
+        2.50006 2
+        <Tangent> { 0 0 1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 14 {
+      -37.5 50 25
+      <UV> {
+        -0.541667 2.0625
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -1 0 0 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 15 {
+      -36.8882 0 21.1373
+      <UV> {
+        2.30006 -1
+        <Tangent> { -0.309017 0 0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.951057 0 -0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 16 {
+      -36.8882 0 28.8627
+      <UV> {
+        -1.29994 -1
+        <Tangent> { 0.309017 0 0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.951057 0 0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 17 {
+      -36.8882 50 21.1373
+      <UV> {
+        2.30006 2
+        <Tangent> { -0.309017 0 0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.951057 0 -0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 18 {
+      -36.8882 50 21.1373
+      <UV> {
+        -0.38072 2.03701
+        <Tangent> { 6.17012e-006 0 -1 }
+        <Binormal> { -1 0 -6.17012e-006 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 19 {
+      -36.8882 50 28.8627
+      <UV> {
+        -1.29994 2
+        <Tangent> { 0.309017 0 0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.951057 0 0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 20 {
+      -36.8882 50 28.8627
+      <UV> {
+        -0.702613 2.03701
+        <Tangent> { -6.17012e-006 0 -1 }
+        <Binormal> { -1 0 6.17012e-006 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 21 {
+      -35.1127 0 17.6527
+      <UV> {
+        2.10006 -1
+        <Tangent> { -0.587785 0 0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.809017 0 -0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 22 {
+      -35.1127 0 32.3473
+      <UV> {
+        -1.09994 -1
+        <Tangent> { 0.587785 0 0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.809017 0 0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 23 {
+      -35.1127 50 17.6527
+      <UV> {
+        2.10006 2
+        <Tangent> { -0.587785 0 0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.809017 0 -0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 24 {
+      -35.1127 50 17.6527
+      <UV> {
+        -0.235528 1.96303
+        <Tangent> { -1.03074e-007 0 -1 }
+        <Binormal> { -1 0 1.03074e-007 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 25 {
+      -35.1127 50 32.3473
+      <UV> {
+        -1.09994 2
+        <Tangent> { 0.587785 0 0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.809017 0 0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 26 {
+      -35.1127 50 32.3473
+      <UV> {
+        -0.847805 1.96303
+        <Tangent> { 1.03075e-007 0 -1 }
+        <Binormal> { -1 0 -1.03075e-007 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 27 {
+      -32.3473 0 14.8873
+      <UV> {
+        1.90006 -1
+        <Tangent> { -0.809017 0 0.587786 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.587786 0 -0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 28 {
+      -32.3473 0 35.1127
+      <UV> {
+        -0.899936 -1
+        <Tangent> { 0.809017 0 0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.587785 0 0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 29 {
+      -32.3473 0 35.1127
+      <UV> {
+        -0.96303 1.84781
+        <Tangent> { 1.52608e-007 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 -1.52608e-007 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 30 {
+      -32.3473 50 14.8873
+      <UV> {
+        1.90006 2
+        <Tangent> { -0.809017 0 0.587786 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.587786 0 -0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 31 {
+      -32.3473 50 14.8873
+      <UV> {
+        -0.120303 1.84781
+        <Tangent> { -8.0634e-006 0 -1 }
+        <Binormal> { -1 0 8.0634e-006 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 32 {
+      -32.3473 50 35.1127
+      <UV> {
+        -0.899936 2
+        <Tangent> { 0.809017 0 0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.587785 0 0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 33 {
+      -28.8627 0 13.1118
+      <UV> {
+        1.70006 -1
+        <Tangent> { -0.951057 0 0.309017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.309017 0 -0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 34 {
+      -28.8627 0 36.8882
+      <UV> {
+        -0.699936 -1
+        <Tangent> { 0.951057 0 0.309016 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.309016 0 0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 35 {
+      -28.8627 50 13.1118
+      <UV> {
+        1.70006 2
+        <Tangent> { -0.951057 0 0.309017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.309017 0 -0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 36 {
+      -28.8627 50 13.1118
+      <UV> {
+        -0.0463246 1.70261
+        <Tangent> { 3.81471e-007 0 -1 }
+        <Binormal> { -1 0 -3.81471e-007 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 37 {
+      -28.8627 50 36.8882
+      <UV> {
+        -0.699936 2
+        <Tangent> { 0.951057 0 0.309016 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -0.309016 0 0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 38 {
+      -28.8627 50 36.8882
+      <UV> {
+        -1.03701 1.70261
+        <Tangent> { -3.81475e-007 0 -1 }
+        <Binormal> { -1 -4.89652e-008 3.81475e-007 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 39 {
+      -25 0 12.5
+      <UV> {
+        1.50006 -1
+        <Tangent> { -1 0 -6.03476e-007 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 6.03476e-007 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 40 {
+      -25 0 37.5
+      <UV> {
+        -0.499936 -1
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 41 {
+      -25 50 12.5
+      <UV> {
+        -0.0208334 1.54167
+        <Tangent> { 1.16912e-005 0 -1 }
+        <Binormal> { -1 0 -1.16912e-005 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 42 {
+      -25 50 12.5
+      <UV> {
+        1.50006 2
+        <Tangent> { -1 0 -6.03476e-007 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 6.03476e-007 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 43 {
+      -25 50 37.5
+      <UV> {
+        -0.499936 2
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 44 {
+      -25 50 37.5
+      <UV> {
+        -1.0625 1.54167
+        <Tangent> { -1.16911e-005 0 -1 }
+        <Binormal> { -1 -4.89652e-008 1.16911e-005 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 45 {
+      -21.1373 0 13.1118
+      <UV> {
+        1.30006 -1
+        <Tangent> { -0.951057 0 -0.309017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.309017 0 -0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 46 {
+      -21.1373 0 36.8882
+      <UV> {
+        -0.299936 -1
+        <Tangent> { 0.951057 0 -0.309016 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.309016 0 0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 47 {
+      -21.1373 50 13.1118
+      <UV> {
+        -0.0463246 1.38072
+        <Tangent> { -1.09281e-005 0 -1 }
+        <Binormal> { -1 0 1.09281e-005 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 48 {
+      -21.1373 50 13.1118
+      <UV> {
+        1.30006 2
+        <Tangent> { -0.951057 0 -0.309017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.309017 0 -0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 49 {
+      -21.1373 50 36.8882
+      <UV> {
+        -1.03701 1.38072
+        <Tangent> { 1.09282e-005 0 -1 }
+        <Binormal> { -1 -4.89652e-008 -1.09282e-005 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 50 {
+      -21.1373 50 36.8882
+      <UV> {
+        -0.299936 2
+        <Tangent> { 0.951057 0 -0.309016 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.309016 0 0.951057 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 51 {
+      -17.6527 0 14.8873
+      <UV> {
+        1.10006 -1
+        <Tangent> { -0.809017 0 -0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.587785 0 -0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 52 {
+      -17.6527 0 35.1127
+      <UV> {
+        -0.0999364 -1
+        <Tangent> { 0.809017 0 -0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.587785 0 0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 53 {
+      -17.6527 50 14.8873
+      <UV> {
+        -0.120304 1.23553
+        <Tangent> { -7.25597e-007 0 -1 }
+        <Binormal> { -1 0 7.25597e-007 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 54 {
+      -17.6527 50 14.8873
+      <UV> {
+        1.10006 2
+        <Tangent> { -0.809017 0 -0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.587785 0 -0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 55 {
+      -17.6527 50 35.1127
+      <UV> {
+        -0.96303 1.23553
+        <Tangent> { 7.25593e-007 0 -1 }
+        <Binormal> { -1 -4.89652e-008 -7.25593e-007 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 56 {
+      -17.6527 50 35.1127
+      <UV> {
+        -0.0999364 2
+        <Tangent> { 0.809017 0 -0.587785 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.587785 0 0.809017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 57 {
+      -14.8873 0 17.6527
+      <UV> {
+        -0.235528 1.1203
+        <Tangent> { -5.45099e-007 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 5.45099e-007 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 58 {
+      -14.8873 0 17.6527
+      <UV> {
+        0.900064 -1
+        <Tangent> { -0.587785 0 -0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.809017 0 -0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 59 {
+      -14.8873 0 32.3473
+      <UV> {
+        0.100064 -1
+        <Tangent> { 0.587785 0 -0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.809017 0 0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 60 {
+      -14.8873 50 17.6527
+      <UV> {
+        0.900064 2
+        <Tangent> { -0.587785 0 -0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.809017 0 -0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 61 {
+      -14.8873 50 32.3473
+      <UV> {
+        -0.847805 1.1203
+        <Tangent> { -1.79128e-006 0 -1 }
+        <Binormal> { -1 -4.89652e-008 1.79128e-006 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 62 {
+      -14.8873 50 32.3473
+      <UV> {
+        0.100064 2
+        <Tangent> { 0.587785 0 -0.809017 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.809017 0 0.587785 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 63 {
+      -13.1118 0 21.1373
+      <UV> {
+        0.700064 -1
+        <Tangent> { -0.309017 0 -0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.951057 0 -0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 64 {
+      -13.1118 0 28.8627
+      <UV> {
+        0.300064 -1
+        <Tangent> { 0.309017 0 -0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.951057 0 0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 65 {
+      -13.1118 50 21.1373
+      <UV> {
+        -0.38072 1.04633
+        <Tangent> { 3.58256e-006 0 -1 }
+        <Binormal> { -1 -4.89652e-008 -3.58256e-006 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 66 {
+      -13.1118 50 21.1373
+      <UV> {
+        0.700064 2
+        <Tangent> { -0.309017 0 -0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.951057 0 -0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 67 {
+      -13.1118 50 28.8627
+      <UV> {
+        -0.702613 1.04633
+        <Tangent> { -3.58256e-006 0 -1 }
+        <Binormal> { -1 -4.89652e-008 3.58256e-006 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 68 {
+      -13.1118 50 28.8627
+      <UV> {
+        0.300064 2
+        <Tangent> { 0.309017 0 -0.951057 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0.951057 0 0.309017 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 69 {
+      -12.5 0 25
+      <UV> {
+        0.500064 -1
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 70 {
+      -12.5 50 25
+      <UV> {
+        -0.541667 1.02083
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -1 -4.89652e-008 0 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 71 {
+      -12.5 50 25
+      <UV> {
+        0.500064 2
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 72 {
+      5 0 -5
+      <UV> {
+        0.708333 0.291667
+        <Tangent> { -1.84039e-007 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 1.84039e-007 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 73 {
+      5 1.19209e-006 -45
+      <UV> {
+        1.5 -0.5
+        <Tangent> { -1 -1.49012e-008 0 }
+        <Binormal> { -1.49012e-008 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 74 {
+      5 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 75 {
+      5 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 0.707107 }
+      }
+      <Normal> { 0 0.707107 -0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 76 {
+      25 30 -25
+      <UV> {
+        0.5 1.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 0.707107 }
+      }
+      <Normal> { 0 0.707107 -0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 77 {
+      25 30 -25
+      <UV> {
+        0.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -0.707107 0.707107 0 }
+      }
+      <Normal> { 0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 78 {
+      45 0 -45
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { -1 -1.49012e-008 0 }
+        <Binormal> { -1.49012e-008 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 79 {
+      45 0 -45
+      <UV> {
+        2.375 -1.375
+        <Tangent> { -2.98023e-008 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 2.98023e-008 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 80 {
+      45 0 -45
+      <UV> {
+        1.5 -0.5
+        <Tangent> { 0 1.49012e-008 -1 }
+        <Binormal> { 0 1 1.49012e-008 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 81 {
+      45 1.19209e-006 -5
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { 0 1.49012e-008 -1 }
+        <Binormal> { 0 1 1.49012e-008 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 82 {
+      45 10 -45
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 83 {
+      45 10 -45
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 0.707107 }
+      }
+      <Normal> { 0 0.707107 -0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 84 {
+      45 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -0.707107 0.707107 0 }
+      }
+      <Normal> { 0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 85 {
+      45 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 86 {
+      45 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -0.707107 0.707107 0 }
+      }
+      <Normal> { 0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 87 {
+      45 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 88 {
+      60 0 -60
+      <UV> {
+        1.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 -1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 89 {
+      60 0 -60
+      <UV> {
+        1.5 -0.5
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 90 {
+      60 0 -60
+      <UV> {
+        3 -2
+        <Tangent> { -1.19209e-008 4.13254e-011 -1 }
+        <Binormal> { -1 4.13254e-011 1.19209e-008 }
+      }
+      <Normal> { 4.13254e-011 1 4.13254e-011 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 91 {
+      60 0 60
+      <UV> {
+        -0.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 -1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 92 {
+      60 0 60
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 93 {
+      60 0 60
+      <UV> {
+        -2 -2
+        <Tangent> { 0 3.91309e-009 -1 }
+        <Binormal> { -1 3.91309e-009 0 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 94 {
+      60 50 -60
+      <UV> {
+        1.5 -0.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 -1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 95 {
+      60 50 -60
+      <UV> {
+        1.5 1.5
+        <Tangent> { 1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 96 {
+      60 50 60
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 -1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 97 {
+      60 50 60
+      <UV> {
+        -0.5 1.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 98 {
+      -60 50 -60
+      <UV> {
+        3 3
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -1 0 0 }
+      }
+      <Normal> { 0 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 99 {
+      -60 50 60
+      <UV> {
+        -2 3
+        <Tangent> { -1.18287e-008 0 -1 }
+        <Binormal> { -1 -2.44826e-008 1.18287e-008 }
+      }
+      <Normal> { 2.44826e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 100 {
+      -37.5 0 25
+      <UV> {
+        -0.541667 2.0625
+        <Tangent> { 0 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 0 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 101 {
+      -36.8882 0 21.1373
+      <UV> {
+        -0.38072 2.03701
+        <Tangent> { -2.784e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 2.784e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 102 {
+      -36.8882 0 28.8627
+      <UV> {
+        -0.702613 2.03701
+        <Tangent> { 2.784e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 -2.784e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 103 {
+      -35.1127 0 17.6527
+      <UV> {
+        -0.235528 1.96303
+        <Tangent> { -1.03074e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 1.03074e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 104 {
+      -35.1127 0 32.3473
+      <UV> {
+        -0.847805 1.96303
+        <Tangent> { 1.03075e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 -1.03075e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 105 {
+      -32.3473 0 14.8873
+      <UV> {
+        -0.120303 1.84781
+        <Tangent> { -8.0634e-006 -3.83047e-009 -1 }
+        <Binormal> { -1 -3.83041e-009 8.0634e-006 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 106 {
+      -32.3473 50 35.1127
+      <UV> {
+        -0.96303 1.84781
+        <Tangent> { 1.52608e-007 0 -1 }
+        <Binormal> { -1 -2.44826e-008 -1.52608e-007 }
+      }
+      <Normal> { 2.44826e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 107 {
+      -28.8627 0 13.1118
+      <UV> {
+        -0.0463246 1.70261
+        <Tangent> { 3.81471e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 -3.81471e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 108 {
+      -28.8627 0 36.8882
+      <UV> {
+        -1.03701 1.70261
+        <Tangent> { -3.81475e-007 3.91309e-009 -1 }
+        <Binormal> { -1 3.91309e-009 3.81475e-007 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 109 {
+      -25 0 12.5
+      <UV> {
+        -0.0208334 1.54167
+        <Tangent> { 1.16912e-005 -3.83039e-009 -1 }
+        <Binormal> { -1 -3.83048e-009 -1.16912e-005 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 110 {
+      -25 0 37.5
+      <UV> {
+        -1.0625 1.54167
+        <Tangent> { -1.16911e-005 3.91314e-009 -1 }
+        <Binormal> { -1 3.91304e-009 1.16911e-005 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 111 {
+      -21.1373 0 13.1118
+      <UV> {
+        -0.0463246 1.38072
+        <Tangent> { -1.09281e-005 -3.83048e-009 -1 }
+        <Binormal> { -1 -3.8304e-009 1.09281e-005 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 112 {
+      -21.1373 0 36.8882
+      <UV> {
+        -1.03701 1.38072
+        <Tangent> { 1.09282e-005 3.91305e-009 -1 }
+        <Binormal> { -1 3.91313e-009 -1.09282e-005 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 113 {
+      -17.6527 0 14.8873
+      <UV> {
+        -0.120304 1.23553
+        <Tangent> { -7.25597e-007 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 7.25597e-007 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 114 {
+      -17.6527 0 35.1127
+      <UV> {
+        -0.96303 1.23553
+        <Tangent> { 7.25593e-007 3.91309e-009 -1 }
+        <Binormal> { -1 3.91309e-009 -7.25593e-007 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 115 {
+      -14.8873 0 32.3473
+      <UV> {
+        -0.847805 1.1203
+        <Tangent> { -1.79128e-006 3.9131e-009 -1 }
+        <Binormal> { -1 3.91308e-009 1.79128e-006 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 116 {
+      -14.8873 50 17.6527
+      <UV> {
+        -0.235528 1.1203
+        <Tangent> { -4.96743e-007 0 -1 }
+        <Binormal> { -1 -2.44826e-008 4.96743e-007 }
+      }
+      <Normal> { 2.44826e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 117 {
+      -13.1118 0 21.1373
+      <UV> {
+        -0.38072 1.04633
+        <Tangent> { 3.58256e-006 3.91308e-009 -1 }
+        <Binormal> { -1 3.9131e-009 -3.58256e-006 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 118 {
+      -13.1118 0 28.8627
+      <UV> {
+        -0.702613 1.04633
+        <Tangent> { -3.58256e-006 3.9131e-009 -1 }
+        <Binormal> { -1 3.91308e-009 3.58256e-006 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 119 {
+      -12.5 0 25
+      <UV> {
+        -0.541667 1.02083
+        <Tangent> { 0 3.91309e-009 -1 }
+        <Binormal> { -1 3.91309e-009 0 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 120 {
+      5 0 -5
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { 0 -1.49012e-008 -1 }
+        <Binormal> { 0 1 -1.49012e-008 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 121 {
+      5 0 -5
+      <UV> {
+        1.5 -0.5
+        <Tangent> { -1 1.49012e-008 0 }
+        <Binormal> { 1.49012e-008 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 122 {
+      5 1.19209e-006 -45
+      <UV> {
+        1.5 -0.5
+        <Tangent> { 0 -1.49012e-008 -1 }
+        <Binormal> { 0 1 -1.49012e-008 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 123 {
+      5 1.19209e-006 -45
+      <UV> {
+        2.375 0.291667
+        <Tangent> { 0 -3.83044e-009 -1 }
+        <Binormal> { -1 -3.83044e-009 0 }
+      }
+      <Normal> { -3.83044e-009 1 -3.83044e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 124 {
+      5 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 125 {
+      5 10 -45
+      <UV> {
+        1.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0.707107 0.707107 0 }
+      }
+      <Normal> { -0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 126 {
+      5 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 127 {
+      5 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0.707107 0.707107 0 }
+      }
+      <Normal> { -0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 128 {
+      5 10 -5
+      <UV> {
+        1.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 129 {
+      5 10 -5
+      <UV> {
+        1.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 -0.707107 }
+      }
+      <Normal> { 0 0.707107 0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 130 {
+      25 30 -25
+      <UV> {
+        0.5 1.5
+        <Tangent> { 0 0 -1 }
+        <Binormal> { 0.707107 0.707107 0 }
+      }
+      <Normal> { -0.707107 0.707107 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 131 {
+      25 30 -25
+      <UV> {
+        0.5 1.5
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 -0.707107 }
+      }
+      <Normal> { 0 0.707107 0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 132 {
+      45 1.19209e-006 -5
+      <UV> {
+        -0.5 -0.5
+        <Tangent> { -1 1.49012e-008 0 }
+        <Binormal> { 1.49012e-008 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 133 {
+      45 1.19209e-006 -5
+      <UV> {
+        0.708333 -1.375
+        <Tangent> { 0 3.91309e-009 -1 }
+        <Binormal> { -1 3.91309e-009 0 }
+      }
+      <Normal> { 3.91309e-009 1 3.91309e-009 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 134 {
+      45 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 1 0 }
+      }
+      <Normal> { 0 0 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 135 {
+      45 10 -5
+      <UV> {
+        -0.5 0.166667
+        <Tangent> { -1 0 0 }
+        <Binormal> { 0 0.707107 -0.707107 }
+      }
+      <Normal> { 0 0.707107 0.707107 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 136 {
+      60 50 -60
+      <UV> {
+        3 -2
+        <Tangent> { -6.29762e-008 0 -1 }
+        <Binormal> { -1 -2.44826e-008 6.29762e-008 }
+      }
+      <Normal> { 2.44826e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 137 {
+      60 50 60
+      <UV> {
+        -2 -2
+        <Tangent> { 0 0 -1 }
+        <Binormal> { -1 -4.89652e-008 0 }
+      }
+      <Normal> { 4.89652e-008 -1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <Normal> { 0 0.707107 -0.707107 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 75 76 83 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.707107 0.707107 0 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 127 130 125 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0 0.707107 0.707107 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 135 131 129 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.707107 0.707107 0 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 84 77 86 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0 0 -1 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 78 73 74 82 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -1 0 0 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 122 120 126 124 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0 0 1 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 121 132 134 128 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 1 0 0 }
+    <TRef> { phong2SG }
+    <TRef> { phong2SG.tref1 }
+    <VertexRef> { 81 80 85 87 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -1 0 0 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> { 91 96 94 88 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 4.89652e-008 -1 0 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> {
+      49 55 61 67 70 65 116 136 137 99 106 38 44
+      <Ref> { polySurfaceShape2.verts }
+    }
+  }
+  <Polygon> {
+    <Normal> { 0 -1 0 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> {
+      99 98 136 116 53 47 41 36 31 24 18 14 20 26 106
+      <Ref> { polySurfaceShape2.verts }
+    }
+  }
+  <Polygon> {
+    <Normal> { 1 0 0 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> { 9 5 2 7 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 3.91309e-009 1 3.91309e-009 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> {
+      93 90 79 133 72 57 117 119 118 115 114 112 110 108 29 4
+      <Ref> { polySurfaceShape2.verts }
+    }
+  }
+  <Polygon> {
+    <Normal> { -3.83044e-009 1 -3.83044e-009 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> {
+      123 79 90 0 4 29 104 102 100 101 103 105 107 109 111 113 57 72
+      <Ref> { polySurfaceShape2.verts }
+    }
+  }
+  <Polygon> {
+    <Normal> { 0 0 1 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> { 89 95 6 1 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0 0 -1 }
+    <TRef> { phong1SG }
+    <TRef> { phong1SG.tref2 }
+    <VertexRef> { 3 8 97 92 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.987688 0 0.156435 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 10 16 19 12 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.987688 0 -0.156435 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 15 11 13 17 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.891007 0 -0.45399 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 21 15 17 23 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.707107 0 -0.707107 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 27 21 23 30 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.453991 0 -0.891006 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 33 27 30 35 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.156433 0 -0.987689 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 39 33 35 42 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.156434 0 -0.987688 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 45 39 42 48 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.453991 0 -0.891007 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 51 45 48 54 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.707107 0 -0.707107 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 58 51 54 60 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.891007 0 -0.453991 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 63 58 60 66 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.987688 0 -0.156434 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 69 63 66 71 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.987688 0 0.156434 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 64 69 71 68 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.891007 0 0.453991 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 59 64 68 62 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.707107 0 0.707106 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 52 59 62 56 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.45399 0 0.891007 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 46 52 56 50 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { 0.156434 0 0.987688 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 40 46 50 43 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.156434 0 0.987688 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 34 40 43 37 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.45399 0 0.891007 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 28 34 37 32 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.707107 0 0.707107 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 22 28 32 25 <Ref> { polySurfaceShape2.verts } }
+  }
+  <Polygon> {
+    <Normal> { -0.891007 0 0.45399 }
+    <TRef> { phong3SG }
+    <TRef> { phong3SG.tref3 }
+    <VertexRef> { 16 22 25 19 <Ref> { polySurfaceShape2.verts } }
+  }
+}

BIN
samples/bump-mapping/models/abstractroom.mb


BIN
samples/bump-mapping/models/brick-c.jpg


BIN
samples/bump-mapping/models/brick-n.jpg


BIN
samples/bump-mapping/models/fieldstone-c.jpg


BIN
samples/bump-mapping/models/fieldstone-n.jpg


+ 497 - 0
samples/bump-mapping/models/icosphere.egg

@@ -0,0 +1,497 @@
+<CoordinateSystem> { Z-Up }
+
+<Comment> {
+  "egg-trans -F icosphere.egg -o icosphere.egg"
+}
+<Group> Icosphere {
+  <VertexPool> Icosphere {
+    <Vertex> 0 {
+      0 0 -1
+      <Normal> { 0 0 -1 }
+    }
+    <Vertex> 1 {
+      0.425323 -0.309011 -0.850654
+      <Normal> { 0.425306 -0.309 -0.850642 }
+    }
+    <Vertex> 2 {
+      -0.162456 -0.499995 -0.850654
+      <Normal> { -0.16245 -0.499985 -0.850642 }
+    }
+    <Vertex> 3 {
+      0.723607 -0.525725 -0.44722
+      <Normal> { 0.723594 -0.525712 -0.447188 }
+    }
+    <Vertex> 4 {
+      0.850648 0 -0.525736
+      <Normal> { 0.850642 0 -0.525712 }
+    }
+    <Vertex> 5 {
+      -0.52573 0 -0.850652
+      <Normal> { -0.525712 0 -0.850642 }
+    }
+    <Vertex> 6 {
+      -0.162456 0.499995 -0.850654
+      <Normal> { -0.16245 0.499985 -0.850642 }
+    }
+    <Vertex> 7 {
+      0.425323 0.309011 -0.850654
+      <Normal> { 0.425306 0.309 -0.850642 }
+    }
+    <Vertex> 8 {
+      0.951058 -0.309013 0
+      <Normal> { 0.951048 -0.309 0 }
+    }
+    <Vertex> 9 {
+      -0.276388 -0.850649 -0.44722
+      <Normal> { -0.276376 -0.850642 -0.447218 }
+    }
+    <Vertex> 10 {
+      0.262869 -0.809012 -0.525738
+      <Normal> { 0.262856 -0.808985 -0.525712 }
+    }
+    <Vertex> 11 {
+      0 -1 0
+      <Normal> { 0 -1 0 }
+    }
+    <Vertex> 12 {
+      -0.894426 0 -0.447216
+      <Normal> { -0.894406 0 -0.447188 }
+    }
+    <Vertex> 13 {
+      -0.688189 -0.499997 -0.525736
+      <Normal> { -0.688162 -0.499985 -0.525712 }
+    }
+    <Vertex> 14 {
+      -0.951058 -0.309013 0
+      <Normal> { -0.951048 -0.309 0 }
+    }
+    <Vertex> 15 {
+      -0.276388 0.850649 -0.44722
+      <Normal> { -0.276376 0.850642 -0.447218 }
+    }
+    <Vertex> 16 {
+      -0.688189 0.499997 -0.525736
+      <Normal> { -0.688162 0.499985 -0.525712 }
+    }
+    <Vertex> 17 {
+      -0.587786 0.809017 0
+      <Normal> { -0.587756 0.809015 0 }
+    }
+    <Vertex> 18 {
+      0.723607 0.525725 -0.44722
+      <Normal> { 0.723594 0.525712 -0.447188 }
+    }
+    <Vertex> 19 {
+      0.262869 0.809012 -0.525738
+      <Normal> { 0.262856 0.808985 -0.525712 }
+    }
+    <Vertex> 20 {
+      0.587786 0.809017 0
+      <Normal> { 0.587756 0.809015 0 }
+    }
+    <Vertex> 21 {
+      0.587786 -0.809017 0
+      <Normal> { 0.587756 -0.809015 0 }
+    }
+    <Vertex> 22 {
+      -0.587786 -0.809017 0
+      <Normal> { -0.587756 -0.809015 0 }
+    }
+    <Vertex> 23 {
+      -0.951058 0.309013 0
+      <Normal> { -0.951048 0.309 0 }
+    }
+    <Vertex> 24 {
+      0 1 0
+      <Normal> { 0 1 0 }
+    }
+    <Vertex> 25 {
+      0.951058 0.309013 0
+      <Normal> { 0.951048 0.309 0 }
+    }
+    <Vertex> 26 {
+      0.276388 -0.850649 0.44722
+      <Normal> { 0.276376 -0.850642 0.447218 }
+    }
+    <Vertex> 27 {
+      0.688189 -0.499997 0.525736
+      <Normal> { 0.688162 -0.499985 0.525712 }
+    }
+    <Vertex> 28 {
+      0.162456 -0.499995 0.850654
+      <Normal> { 0.16245 -0.499985 0.850642 }
+    }
+    <Vertex> 29 {
+      -0.723607 -0.525725 0.44722
+      <Normal> { -0.723594 -0.525712 0.447188 }
+    }
+    <Vertex> 30 {
+      -0.262869 -0.809012 0.525738
+      <Normal> { -0.262856 -0.808985 0.525712 }
+    }
+    <Vertex> 31 {
+      -0.425323 -0.309011 0.850654
+      <Normal> { -0.425306 -0.309 0.850642 }
+    }
+    <Vertex> 32 {
+      -0.723607 0.525725 0.44722
+      <Normal> { -0.723594 0.525712 0.447188 }
+    }
+    <Vertex> 33 {
+      -0.850648 0 0.525736
+      <Normal> { -0.850642 0 0.525712 }
+    }
+    <Vertex> 34 {
+      -0.425323 0.309011 0.850654
+      <Normal> { -0.425306 0.309 0.850642 }
+    }
+    <Vertex> 35 {
+      0.276388 0.850649 0.44722
+      <Normal> { 0.276376 0.850642 0.447218 }
+    }
+    <Vertex> 36 {
+      -0.262869 0.809012 0.525738
+      <Normal> { -0.262856 0.808985 0.525712 }
+    }
+    <Vertex> 37 {
+      0.162456 0.499995 0.850654
+      <Normal> { 0.16245 0.499985 0.850642 }
+    }
+    <Vertex> 38 {
+      0.894426 0 0.447216
+      <Normal> { 0.894406 0 0.447188 }
+    }
+    <Vertex> 39 {
+      0.688189 0.499997 0.525736
+      <Normal> { 0.688162 0.499985 0.525712 }
+    }
+    <Vertex> 40 {
+      0.52573 0 0.850652
+      <Normal> { 0.525712 0 0.850642 }
+    }
+    <Vertex> 41 {
+      0 0 1
+      <Normal> { 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <Normal> { 0.102381 -0.31509 -0.943523 }
+    <VertexRef> { 0 1 2 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.700224 -0.268032 -0.661699 }
+    <VertexRef> { 3 1 4 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.268034 -0.194736 -0.943523 }
+    <VertexRef> { 0 2 5 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.268034 0.194737 -0.943523 }
+    <VertexRef> { 0 5 6 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.102381 0.31509 -0.943523 }
+    <VertexRef> { 0 6 7 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.904989 -0.268032 -0.330385 }
+    <VertexRef> { 3 4 8 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.024747 -0.943521 -0.330386 }
+    <VertexRef> { 9 10 11 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.889697 -0.315095 -0.330385 }
+    <VertexRef> { 12 13 14 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.574602 0.748784 -0.330388 }
+    <VertexRef> { 15 16 17 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.534576 0.777865 -0.330387 }
+    <VertexRef> { 18 19 20 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.802609 -0.583126 -0.125627 }
+    <VertexRef> { 3 8 21 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.306569 -0.943522 -0.125629 }
+    <VertexRef> { 9 11 22 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.992077 0 -0.125628 }
+    <VertexRef> { 12 14 23 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.306569 0.943522 -0.125629 }
+    <VertexRef> { 15 17 24 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.802609 0.583126 -0.125627 }
+    <VertexRef> { 18 20 25 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.408946 -0.628425 0.661698 }
+    <VertexRef> { 26 27 28 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.4713 -0.583122 0.661699 }
+    <VertexRef> { 29 30 31 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.700224 0.268032 0.661699 }
+    <VertexRef> { 32 33 34 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.03853 0.748779 0.661699 }
+    <VertexRef> { 35 36 37 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.724042 0.194736 0.661695 }
+    <VertexRef> { 38 39 40 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.268034 0.194737 0.943523 }
+    <VertexRef> { 40 37 41 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.491119 0.356821 0.794657 }
+    <VertexRef> { 40 39 37 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.408946 0.628425 0.661699 }
+    <VertexRef> { 39 35 37 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.102381 0.31509 0.943523 }
+    <VertexRef> { 37 34 41 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.187594 0.577345 0.794658 }
+    <VertexRef> { 37 36 34 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.4713 0.583122 0.661699 }
+    <VertexRef> { 36 32 34 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.331305 0 0.943524 }
+    <VertexRef> { 34 31 41 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.60706 0 0.794656 }
+    <VertexRef> { 34 33 31 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.700224 -0.268032 0.661699 }
+    <VertexRef> { 33 29 31 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.102381 -0.31509 0.943523 }
+    <VertexRef> { 31 28 41 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.187594 -0.577345 0.794658 }
+    <VertexRef> { 31 30 28 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.03853 -0.748779 0.661699 }
+    <VertexRef> { 30 26 28 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.268034 -0.194737 0.943523 }
+    <VertexRef> { 28 40 41 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.491119 -0.356821 0.794657 }
+    <VertexRef> { 28 27 40 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.724042 -0.194736 0.661695 }
+    <VertexRef> { 27 38 40 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.889697 0.315095 0.330385 }
+    <VertexRef> { 25 39 38 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.794656 0.577348 0.187595 }
+    <VertexRef> { 25 20 39 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.574602 0.748784 0.330388 }
+    <VertexRef> { 20 35 39 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.024747 0.943521 0.330386 }
+    <VertexRef> { 24 36 35 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.303531 0.934171 0.187597 }
+    <VertexRef> { 24 17 36 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.534576 0.777865 0.330387 }
+    <VertexRef> { 17 32 36 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.904989 0.268032 0.330385 }
+    <VertexRef> { 23 33 32 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.982246 0 0.187599 }
+    <VertexRef> { 23 14 33 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.904989 -0.268031 0.330385 }
+    <VertexRef> { 14 29 33 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.534576 -0.777865 0.330387 }
+    <VertexRef> { 22 30 29 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.303531 -0.934171 0.187597 }
+    <VertexRef> { 22 11 30 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.024747 -0.943521 0.330386 }
+    <VertexRef> { 11 26 30 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.574602 -0.748784 0.330388 }
+    <VertexRef> { 21 27 26 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.794656 -0.577348 0.187595 }
+    <VertexRef> { 21 8 27 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.889697 -0.315095 0.330385 }
+    <VertexRef> { 8 38 27 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.306569 0.943522 0.125629 }
+    <VertexRef> { 20 24 35 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.303531 0.934171 -0.187597 }
+    <VertexRef> { 20 19 24 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.024747 0.943521 -0.330386 }
+    <VertexRef> { 19 15 24 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.802609 0.583126 0.125627 }
+    <VertexRef> { 17 23 32 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.794656 0.577348 -0.187595 }
+    <VertexRef> { 17 16 23 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.889697 0.315095 -0.330385 }
+    <VertexRef> { 16 12 23 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.802609 -0.583126 0.125627 }
+    <VertexRef> { 14 22 29 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.794656 -0.577348 -0.187595 }
+    <VertexRef> { 14 13 22 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.574602 -0.748784 -0.330388 }
+    <VertexRef> { 13 9 22 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.306569 -0.943522 0.125629 }
+    <VertexRef> { 11 21 26 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.303531 -0.934171 -0.187597 }
+    <VertexRef> { 11 10 21 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.534576 -0.777865 -0.330387 }
+    <VertexRef> { 10 3 21 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.992077 0 0.125628 }
+    <VertexRef> { 8 25 38 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.982246 0 -0.187599 }
+    <VertexRef> { 8 4 25 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.904989 0.268031 -0.330385 }
+    <VertexRef> { 4 18 25 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.4713 0.583122 -0.661699 }
+    <VertexRef> { 7 19 18 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.187594 0.577345 -0.794658 }
+    <VertexRef> { 7 6 19 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.03853 0.748779 -0.661699 }
+    <VertexRef> { 6 15 19 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.408946 0.628425 -0.661698 }
+    <VertexRef> { 6 16 15 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.491119 0.356821 -0.794657 }
+    <VertexRef> { 6 5 16 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.724042 0.194736 -0.661695 }
+    <VertexRef> { 5 12 16 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.724042 -0.194736 -0.661695 }
+    <VertexRef> { 5 13 12 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.491119 -0.356821 -0.794657 }
+    <VertexRef> { 5 2 13 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.408946 -0.628425 -0.661698 }
+    <VertexRef> { 2 9 13 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.700224 0.268032 -0.661699 }
+    <VertexRef> { 4 7 18 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.60706 0 -0.794656 }
+    <VertexRef> { 4 1 7 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.331305 0 -0.943524 }
+    <VertexRef> { 1 0 7 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { -0.03853 -0.748779 -0.661699 }
+    <VertexRef> { 2 10 9 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.187594 -0.577345 -0.794658 }
+    <VertexRef> { 2 1 10 <Ref> { Icosphere } }
+  }
+  <Polygon> {
+    <Normal> { 0.4713 -0.583122 -0.661699 }
+    <VertexRef> { 1 3 10 <Ref> { Icosphere } }
+  }
+}

BIN
samples/bump-mapping/models/layingrock-c.jpg


BIN
samples/bump-mapping/models/layingrock-h.jpg


BIN
samples/bump-mapping/models/layingrock-n.jpg


+ 196 - 0
samples/carousel/main.py

@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+
+# Author: Shao Zhang, Phil Saltzman, and Eddie Canaan
+# Last Updated: 2015-03-13
+#
+# This tutorial will demonstrate some uses for intervals in Panda
+# to move objects in your panda world.
+# Intervals are tools that change a value of something, like position,
+# rotation or anything else, linearly, over a set period of time. They can be
+# also be combined to work in sequence or in Parallel
+#
+# In this lesson, we will simulate a carousel in motion using intervals.
+# The carousel will spin using an hprInterval while 4 pandas will represent
+# the horses on a traditional carousel. The 4 pandas will rotate with the
+# carousel and also move up and down on their poles using a LerpFunc interval.
+# Finally there will also be lights on the outer edge of the carousel that
+# will turn on and off by switching their texture with intervals in Sequence
+# and Parallel
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
+from panda3d.core import NodePath
+from panda3d.core import LVector3
+from direct.interval.IntervalGlobal import *  # Needed to use Intervals
+from direct.gui.DirectGui import *
+
+# Importing math constants and functions
+from math import pi, sin
+
+
+class CarouselDemo(ShowBase):
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # This creates the on screen title that is in every tutorial
+        self.title = OnscreenText(text="Panda3D: Tutorial - Carousel",
+                                  parent=base.a2dBottomCenter,
+                                  fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5),
+                                  pos=(0, .1), scale=.1)
+
+        base.disableMouse()  # Allow manual positioning of the camera
+        camera.setPosHpr(0, -8, 2.5, 0, -9, 0)  # Set the cameras' position
+                                                # and orientation
+
+        self.loadModels()  # Load and position our models
+        self.setupLights()  # Add some basic lighting
+        self.startCarousel()  # Create the needed intervals and put the
+                              # carousel into motion
+
+    def loadModels(self):
+        # Load the carousel base
+        self.carousel = loader.loadModel("models/carousel_base")
+        self.carousel.reparentTo(render)  # Attach it to render
+
+        # Load the modeled lights that are on the outer rim of the carousel
+        # (not Panda lights)
+        # There are 2 groups of lights. At any given time, one group will have
+        # the "on" texture and the other will have the "off" texture.
+        self.lights1 = loader.loadModel("models/carousel_lights")
+        self.lights1.reparentTo(self.carousel)
+
+        # Load the 2nd set of lights
+        self.lights2 = loader.loadModel("models/carousel_lights")
+        # We need to rotate the 2nd so it doesn't overlap with the 1st set.
+        self.lights2.setH(36)
+        self.lights2.reparentTo(self.carousel)
+
+        # Load the textures for the lights. One texture is for the "on" state,
+        # the other is for the "off" state.
+        self.lightOffTex = loader.loadTexture("models/carousel_lights_off.jpg")
+        self.lightOnTex = loader.loadTexture("models/carousel_lights_on.jpg")
+
+        # Create an list (self.pandas) with filled with 4 dummy nodes attached
+        # to the carousel.
+        # This uses a python concept called "Array Comprehensions."  Check the
+        # Python manual for more information on how they work
+        self.pandas = [self.carousel.attachNewNode("panda" + str(i))
+                       for i in range(4)]
+        self.models = [loader.loadModel("models/carousel_panda")
+                       for i in range(4)]
+        self.moves = [0] * 4
+
+        for i in range(4):
+            # set the position and orientation of the ith panda node we just created
+            # The Z value of the position will be the base height of the pandas.
+            # The headings are multiplied by i to put each panda in its own position
+            # around the carousel
+            self.pandas[i].setPosHpr(0, 0, 1.3, i * 90, 0, 0)
+
+            # Load the actual panda model, and parent it to its dummy node
+            self.models[i].reparentTo(self.pandas[i])
+            # Set the distance from the center. This distance is based on the way the
+            # carousel was modeled in Maya
+            self.models[i].setY(.85)
+
+        # Load the environment (Sky sphere and ground plane)
+        self.env = loader.loadModel("models/env")
+        self.env.reparentTo(render)
+        self.env.setScale(7)
+
+    # Panda Lighting
+    def setupLights(self):
+        # Create some lights and add them to the scene. By setting the lights on
+        # render they affect the entire scene
+        # Check out the lighting tutorial for more information on lights
+        ambientLight = AmbientLight("ambientLight")
+        ambientLight.setColor((.4, .4, .35, 1))
+        directionalLight = DirectionalLight("directionalLight")
+        directionalLight.setDirection(LVector3(0, 8, -2.5))
+        directionalLight.setColor((0.9, 0.8, 0.9, 1))
+        render.setLight(render.attachNewNode(directionalLight))
+        render.setLight(render.attachNewNode(ambientLight))
+
+        # Explicitly set the environment to not be lit
+        self.env.setLightOff()
+
+    def startCarousel(self):
+        # Here's where we actually create the intervals to move the carousel
+        # The first type of interval we use is one created directly from a NodePath
+        # This interval tells the NodePath to vary its orientation (hpr) from its
+        # current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from
+        # NodePaths also exist for position, scale, color, and shear
+
+        self.carouselSpin = self.carousel.hprInterval(20, LVector3(360, 0, 0))
+        # Once an interval is created, we need to tell it to actually move.
+        # start() will cause an interval to play once. loop() will tell an interval
+        # to repeat once it finished. To keep the carousel turning, we use
+        # loop()
+        self.carouselSpin.loop()
+
+        # The next type of interval we use is called a LerpFunc interval. It is
+        # called that becuase it linearly interpolates (aka Lerp) values passed to
+        # a function over a given amount of time.
+
+        # In this specific case, horses on a carousel don't move contantly up,
+        # suddenly stop, and then contantly move down again. Instead, they start
+        # slowly, get fast in the middle, and slow down at the top. This motion is
+        # close to a sine wave. This LerpFunc calls the function oscillatePanda
+        # (which we will create below), which changes the height of the panda based
+        # on the sin of the value passed in. In this way we achieve non-linear
+        # motion by linearly changing the input to a function
+        for i in range(4):
+            self.moves[i] = LerpFunc(
+                self.oscillatePanda,  # function to call
+                duration=3,  # 3 second duration
+                fromData=0,  # starting value (in radians)
+                toData=2 * pi,  # ending value (2pi radians = 360 degrees)
+                # Additional information to pass to
+                # self.oscialtePanda
+                extraArgs=[self.models[i], pi * (i % 2)]
+            )
+            # again, we want these to play continuously so we start them with
+            # loop()
+            self.moves[i].loop()
+
+        # Finally, we combine Sequence, Parallel, Func, and Wait intervals,
+        # to schedule texture swapping on the lights to simulate the lights turning
+        # on and off.
+        # Sequence intervals play other intervals in a sequence. In other words,
+        # it waits for the current interval to finish before playing the next
+        # one.
+        # Parallel intervals play a group of intervals at the same time
+        # Wait intervals simply do nothing for a given amount of time
+        # Func intervals simply make a single function call. This is helpful because
+        # it allows us to schedule functions to be called in a larger sequence. They
+        # take virtually no time so they don't cause a Sequence to wait.
+
+        self.lightBlink = Sequence(
+            # For the first step in our sequence we will set the on texture on one
+            # light and set the off texture on the other light at the same time
+            Parallel(
+                Func(self.lights1.setTexture, self.lightOnTex, 1),
+                Func(self.lights2.setTexture, self.lightOffTex, 1)),
+            Wait(1),  # Then we will wait 1 second
+            # Then we will switch the textures at the same time
+            Parallel(
+                Func(self.lights1.setTexture, self.lightOffTex, 1),
+                Func(self.lights2.setTexture, self.lightOnTex, 1)),
+            Wait(1)  # Then we will wait another second
+        )
+
+        self.lightBlink.loop()  # Loop this sequence continuously
+
+    def oscillatePanda(self, rad, panda, offset):
+        # This is the oscillation function mentioned earlier. It takes in a
+        # degree value, a NodePath to set the height on, and an offset. The
+        # offset is there so that the different pandas can move opposite to
+        # each other.  The .2 is the amplitude, so the height of the panda will
+        # vary from -.2 to .2
+        panda.setZ(sin(rad + offset) * .2)
+
+demo = CarouselDemo()
+demo.run()

BIN
samples/carousel/models/carousel_base.egg.pz


BIN
samples/carousel/models/carousel_base.jpg


BIN
samples/carousel/models/carousel_lights.egg.pz


BIN
samples/carousel/models/carousel_lights_off.jpg


BIN
samples/carousel/models/carousel_lights_on.jpg


BIN
samples/carousel/models/carousel_panda.egg.pz


BIN
samples/carousel/models/carousel_panda.jpg


BIN
samples/carousel/models/env.egg.pz


BIN
samples/carousel/models/env_ground.jpg


BIN
samples/carousel/models/env_sky.jpg


+ 160 - 0
samples/cartoon-shader/advanced.py

@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+
+# Author: Kwasi Mensah
+# Date: 7/11/2005
+#
+# This is a tutorial to show some of the more advanced things
+# you can do with Cg. Specifically, with Non Photo Realistic
+# effects like Toon Shading. It also shows how to implement
+# multiple buffers in Panda.
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import PandaNode, LightNode, TextNode
+from panda3d.core import Filename
+from panda3d.core import NodePath
+from panda3d.core import Shader
+from panda3d.core import LVecBase4
+from direct.task.Task import Task
+from direct.actor.Actor import Actor
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from direct.showbase.BufferViewer import BufferViewer
+import sys
+import os
+
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.05)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+
+class ToonMaker(ShowBase):
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        self.disableMouse()
+        camera.setPos(0, -50, 0)
+
+        # Check video card capabilities.
+        if not self.win.getGsg().getSupportsBasicShaders():
+            addTitle("Toon Shader: Video driver reports that Cg shaders are not supported.")
+            return
+
+        # Show instructions in the corner of the window.
+        self.title = addTitle(
+            "Panda3D: Tutorial - Toon Shading with Normals-Based Inking")
+        self.inst1 = addInstructions(0.06, "ESC: Quit")
+        self.inst2 = addInstructions(0.12, "Up/Down: Increase/Decrease Line Thickness")
+        self.inst3 = addInstructions(0.18, "Left/Right: Decrease/Increase Line Darkness")
+        self.inst4 = addInstructions(0.24, "V: View the render-to-texture results")
+
+        # This shader's job is to render the model with discrete lighting
+        # levels.  The lighting calculations built into the shader assume
+        # a single nonattenuating point light.
+
+        tempnode = NodePath(PandaNode("temp node"))
+        tempnode.setShader(loader.loadShader("lightingGen.sha"))
+        self.cam.node().setInitialState(tempnode.getState())
+
+        # This is the object that represents the single "light", as far
+        # the shader is concerned.  It's not a real Panda3D LightNode, but
+        # the shader doesn't care about that.
+
+        light = render.attachNewNode("light")
+        light.setPos(30, -50, 0)
+
+        # this call puts the light's nodepath into the render state.
+        # this enables the shader to access this light by name.
+
+        render.setShaderInput("light", light)
+
+        # The "normals buffer" will contain a picture of the model colorized
+        # so that the color of the model is a representation of the model's
+        # normal at that point.
+
+        normalsBuffer = self.win.makeTextureBuffer("normalsBuffer", 0, 0)
+        normalsBuffer.setClearColor(LVecBase4(0.5, 0.5, 0.5, 1))
+        self.normalsBuffer = normalsBuffer
+        normalsCamera = self.makeCamera(
+            normalsBuffer, lens=self.cam.node().getLens())
+        normalsCamera.node().setScene(render)
+        tempnode = NodePath(PandaNode("temp node"))
+        tempnode.setShader(loader.loadShader("normalGen.sha"))
+        normalsCamera.node().setInitialState(tempnode.getState())
+
+        # what we actually do to put edges on screen is apply them as a texture to
+        # a transparent screen-fitted card
+
+        drawnScene = normalsBuffer.getTextureCard()
+        drawnScene.setTransparency(1)
+        drawnScene.setColor(1, 1, 1, 0)
+        drawnScene.reparentTo(render2d)
+        self.drawnScene = drawnScene
+
+        # this shader accepts, as input, the picture from the normals buffer.
+        # it compares each adjacent pixel, looking for discontinuities.
+        # wherever a discontinuity exists, it emits black ink.
+
+        self.separation = 0.001
+        self.cutoff = 0.3
+        inkGen = loader.loadShader("inkGen.sha")
+        drawnScene.setShader(inkGen)
+        drawnScene.setShaderInput("separation", LVecBase4(self.separation, 0, self.separation, 0))
+        drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff))
+
+        # Panda contains a built-in viewer that lets you view the results of
+        # your render-to-texture operations.  This code configures the viewer.
+
+        self.accept("v", self.bufferViewer.toggleEnable)
+        self.accept("V", self.bufferViewer.toggleEnable)
+        self.bufferViewer.setPosition("llcorner")
+
+        # Load a dragon model and start its animation.
+        self.character = Actor()
+        self.character.loadModel('models/nik-dragon')
+        self.character.reparentTo(render)
+        self.character.loop('win')
+        self.character.hprInterval(15, (360, 0, 0)).loop()
+
+        # These allow you to change cartooning parameters in realtime
+        self.accept("escape", sys.exit, [0])
+        self.accept("arrow_up", self.increaseSeparation)
+        self.accept("arrow_down", self.decreaseSeparation)
+        self.accept("arrow_left", self.increaseCutoff)
+        self.accept("arrow_right", self.decreaseCutoff)
+
+    def increaseSeparation(self):
+        self.separation = self.separation * 1.11111111
+        print("separation: %f" % (self.separation))
+        self.drawnScene.setShaderInput(
+            "separation", LVecBase4(self.separation, 0, self.separation, 0))
+
+    def decreaseSeparation(self):
+        self.separation = self.separation * 0.90000000
+        print("separation: %f" % (self.separation))
+        self.drawnScene.setShaderInput(
+            "separation", LVecBase4(self.separation, 0, self.separation, 0))
+
+    def increaseCutoff(self):
+        self.cutoff = self.cutoff * 1.11111111
+        print("cutoff: %f" % (self.cutoff))
+        self.drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff))
+
+    def decreaseCutoff(self):
+        self.cutoff = self.cutoff * 0.90000000
+        print("cutoff: %f" % (self.cutoff))
+        self.drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff))
+
+t = ToonMaker()
+t.run()

+ 122 - 0
samples/cartoon-shader/basic.py

@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import PandaNode, LightNode, TextNode
+from panda3d.core import Filename, NodePath
+from panda3d.core import PointLight, AmbientLight
+from panda3d.core import LightRampAttrib, AuxBitplaneAttrib
+from panda3d.core import CardMaker
+from panda3d.core import Shader, Texture
+from direct.task.Task import Task
+from direct.actor.Actor import Actor
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from direct.showbase.BufferViewer import BufferViewer
+from direct.filter.CommonFilters import CommonFilters
+import sys
+import os
+
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.05)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+
+class ToonMaker(ShowBase):
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        self.disableMouse()
+        self.cam.node().getLens().setNear(10.0)
+        self.cam.node().getLens().setFar(200.0)
+        camera.setPos(0, -50, 0)
+
+        # Check video card capabilities.
+        if not self.win.getGsg().getSupportsBasicShaders():
+            addTitle("Toon Shader: Video driver reports that Cg shaders are not supported.")
+            return
+
+        # Enable a 'light ramp' - this discretizes the lighting,
+        # which is half of what makes a model look like a cartoon.
+        # Light ramps only work if shader generation is enabled,
+        # so we call 'setShaderAuto'.
+
+        tempnode = NodePath(PandaNode("temp node"))
+        tempnode.setAttrib(LightRampAttrib.makeSingleThreshold(0.5, 0.4))
+        tempnode.setShaderAuto()
+        self.cam.node().setInitialState(tempnode.getState())
+
+        # Use class 'CommonFilters' to enable a cartoon inking filter.
+        # This can fail if the video card is not powerful enough, if so,
+        # display an error and exit.
+
+        self.separation = 1  # Pixels
+        self.filters = CommonFilters(self.win, self.cam)
+        filterok = self.filters.setCartoonInk(separation=self.separation)
+        if (filterok == False):
+            addTitle(
+                "Toon Shader: Video card not powerful enough to do image postprocessing")
+            return
+
+        # Show instructions in the corner of the window.
+        self.title = addTitle(
+            "Panda3D: Tutorial - Toon Shading with Normals-Based Inking")
+        self.inst1 = addInstructions(0.06, "ESC: Quit")
+        self.inst2 = addInstructions(0.12, "Up/Down: Increase/Decrease Line Thickness")
+        self.inst3 = addInstructions(0.18, "V: View the render-to-texture results")
+
+        # Load a dragon model and animate it.
+        self.character = Actor()
+        self.character.loadModel('models/nik-dragon')
+        self.character.reparentTo(render)
+        self.character.loadAnims({'win': 'models/nik-dragon'})
+        self.character.loop('win')
+        self.character.hprInterval(15, (360, 0, 0)).loop()
+
+        # Create a non-attenuating point light and an ambient light.
+        plightnode = PointLight("point light")
+        plightnode.setAttenuation((1, 0, 0))
+        plight = render.attachNewNode(plightnode)
+        plight.setPos(30, -50, 0)
+        alightnode = AmbientLight("ambient light")
+        alightnode.setColor((0.8, 0.8, 0.8, 1))
+        alight = render.attachNewNode(alightnode)
+        render.setLight(alight)
+        render.setLight(plight)
+
+        # Panda contains a built-in viewer that lets you view the
+        # results of all render-to-texture operations.  This lets you
+        # see what class CommonFilters is doing behind the scenes.
+        self.accept("v", self.bufferViewer.toggleEnable)
+        self.accept("V", self.bufferViewer.toggleEnable)
+        self.bufferViewer.setPosition("llcorner")
+        self.accept("s", self.filters.manager.resizeBuffers)
+
+        # These allow you to change cartooning parameters in realtime
+        self.accept("escape", sys.exit, [0])
+        self.accept("arrow_up", self.increaseSeparation)
+        self.accept("arrow_down", self.decreaseSeparation)
+
+    def increaseSeparation(self):
+        self.separation = self.separation * 1.11111111
+        print("separation: %f" % (self.separation))
+        self.filters.setCartoonInk(separation=self.separation)
+
+    def decreaseSeparation(self):
+        self.separation = self.separation * 0.90000000
+        print("separation: %f" % (self.separation))
+        self.filters.setCartoonInk(separation=self.separation)
+
+t = ToonMaker()
+t.run()

+ 35 - 0
samples/cartoon-shader/inkGen.sha

@@ -0,0 +1,35 @@
+//Cg
+//
+//Cg profile arbvp1 arbfp1
+
+void vshader(float4 vtx_position : POSITION,
+             float4 vtx_texcoord0 : TEXCOORD0,
+             out float4 l_position : POSITION,
+             out float4 l_texcoord0 : TEXCOORD0,
+             uniform float4x4 mat_modelproj)
+{
+  l_position=mul(mat_modelproj, vtx_position);
+  l_texcoord0 = vtx_texcoord0;
+}
+
+void fshader(float4 l_texcoord0 : TEXCOORD0,
+             uniform sampler2D tex_0 : TEXUNIT0,
+             uniform float4 k_cutoff : C6,
+             uniform float4 k_separation : C7,
+             out float4 o_color : COLOR)
+{
+  float4 texcoord0 = l_texcoord0 + k_separation.xyzw;
+  float4 color0=tex2D(tex_0, float2(texcoord0.x, texcoord0.y));
+  float4 texcoord1 = l_texcoord0 - k_separation.xyzw;
+  float4 color1=tex2D(tex_0, float2(texcoord1.x, texcoord1.y));
+  float4 texcoord2 = l_texcoord0 + k_separation.wzyx;
+  float4 color2=tex2D(tex_0, float2(texcoord2.x, texcoord2.y));
+  float4 texcoord3 = l_texcoord0 - k_separation.wzyx;
+  float4 color3=tex2D(tex_0, float2(texcoord3.x, texcoord3.y));
+  float4 mx = max(color0,max(color1,max(color2,color3)));
+  float4 mn = min(color0,min(color1,min(color2,color3)));
+  float4 trigger = saturate(((mx-mn) * 3) - k_cutoff.x);
+  float thresh = dot(float3(trigger.x, trigger.y, trigger.z),float3(1,1,1));
+  float4 output_color = float4 (0, 0, 0, thresh);
+  o_color = output_color;
+}

+ 29 - 0
samples/cartoon-shader/lightingGen.sha

@@ -0,0 +1,29 @@
+//Cg
+//
+//Cg profile arbvp1 arbfp1
+
+void vshader(float4 vtx_position   : POSITION,
+             float3 vtx_normal     : NORMAL,
+             float4 vtx_color      : COLOR,
+             out float4 l_position : POSITION,
+             out float4 l_brite    : TEXCOORD0,
+             out float4 l_color    : COLOR,
+             uniform float4 mspos_light,
+             uniform float4x4 mat_modelproj)
+{
+  l_position = mul(mat_modelproj, vtx_position);
+  float3 N = normalize(vtx_normal);
+  float3 lightVector = normalize(mspos_light - vtx_position);
+  l_brite = max(dot(N,lightVector), 0);
+  l_color = vtx_color;
+}
+
+
+void fshader(float4 l_brite     : TEXCOORD0, 
+             float4 l_color     : COLOR,
+             out float4 o_color : COLOR)
+{
+  if (l_brite.x<0.5) l_brite=0.8;
+  else l_brite=1.2;
+  o_color=l_brite * l_color;
+}

BIN
samples/cartoon-shader/models/nik-dragon.egg.pz


+ 25 - 0
samples/cartoon-shader/normalGen.sha

@@ -0,0 +1,25 @@
+//Cg
+//
+//Cg profile arbvp1 arbfp1
+
+void vshader(float4 vtx_position : POSITION,
+             float4 vtx_normal : NORMAL,
+            out float4 l_position : POSITION,
+            out float3 l_color : TEXCOORD0,   
+            uniform float4x4 mat_modelproj,
+            uniform float4x4 itp_modelview)
+{
+  l_position=mul(mat_modelproj, vtx_position);
+  l_color=(float3)mul(itp_modelview, vtx_normal);
+}
+
+void fshader(float3 l_color: TEXCOORD0,
+            out float4 o_color: COLOR)
+{
+  l_color = normalize(l_color);
+  l_color = l_color/2;
+  o_color.rgb = l_color + float4(0.5, 0.5, 0.5, 0.5);
+  o_color.a = 1;
+}
+
+

+ 278 - 0
samples/chessboard/main.py

@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+
+# Author: Shao Zhang and Phil Saltzman
+# Models: Eddie Canaan
+# Last Updated: 2015-03-13
+#
+# This tutorial shows how to determine what objects the mouse is pointing to
+# We do this using a collision ray that extends from the mouse position
+# and points straight into the scene, and see what it collides with. We pick
+# the object with the closest collision
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import CollisionTraverser, CollisionNode
+from panda3d.core import CollisionHandlerQueue, CollisionRay
+from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
+from panda3d.core import TextNode
+from panda3d.core import LPoint3, LVector3, BitMask32
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from direct.task.Task import Task
+import sys
+
+# First we define some contants for the colors
+BLACK = (0, 0, 0, 1)
+WHITE = (1, 1, 1, 1)
+HIGHLIGHT = (0, 1, 1, 1)
+PIECEBLACK = (.15, .15, .15, 1)
+
+# Now we define some helper functions that we will need later
+
+# This function, given a line (vector plus origin point) and a desired z value,
+# will give us the point on the line where the desired z value is what we want.
+# This is how we know where to position an object in 3D space based on a 2D mouse
+# position. It also assumes that we are dragging in the XY plane.
+#
+# This is derived from the mathmatical of a plane, solved for a given point
+def PointAtZ(z, point, vec):
+    return point + vec * ((z - point.getZ()) / vec.getZ())
+
+# A handy little function for getting the proper position for a given square1
+def SquarePos(i):
+    return LPoint3((i % 8) - 3.5, int(i / 8) - 3.5, 0)
+
+# Helper function for determining wheter a square should be white or black
+# The modulo operations (%) generate the every-other pattern of a chess-board
+def SquareColor(i):
+    if (i + ((i / 8) % 2)) % 2:
+        return BLACK
+    else:
+        return WHITE
+
+
+class ChessboardDemo(ShowBase):
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # This code puts the standard title and instruction text on screen
+        self.title = OnscreenText(text="Panda3D: Tutorial - Mouse Picking",
+                                  style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
+                                  pos=(0.8, -0.95), scale = .07)
+        self.escapeEvent = OnscreenText(
+            text="ESC: Quit", parent=base.a2dTopLeft,
+            style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.1),
+            align=TextNode.ALeft, scale = .05)
+        self.mouse1Event = OnscreenText(
+            text="Left-click and drag: Pick up and drag piece",
+            parent=base.a2dTopLeft, align=TextNode.ALeft,
+            style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16), scale=.05)
+
+        self.accept('escape', sys.exit)  # Escape quits
+        self.disableMouse()  # Disble mouse camera control
+        camera.setPosHpr(0, -12, 8, 0, -35, 0)  # Set the camera
+        self.setupLights()  # Setup default lighting
+
+        # Since we are using collision detection to do picking, we set it up like
+        # any other collision detection system with a traverser and a handler
+        self.picker = CollisionTraverser()  # Make a traverser
+        self.pq = CollisionHandlerQueue()  # Make a handler
+        # Make a collision node for our picker ray
+        self.pickerNode = CollisionNode('mouseRay')
+        # Attach that node to the camera since the ray will need to be positioned
+        # relative to it
+        self.pickerNP = camera.attachNewNode(self.pickerNode)
+        # Everything to be picked will use bit 1. This way if we were doing other
+        # collision we could seperate it
+        self.pickerNode.setFromCollideMask(BitMask32.bit(1))
+        self.pickerRay = CollisionRay()  # Make our ray
+        # Add it to the collision node
+        self.pickerNode.addSolid(self.pickerRay)
+        # Register the ray as something that can cause collisions
+        self.picker.addCollider(self.pickerNP, self.pq)
+        # self.picker.showCollisions(render)
+
+        # Now we create the chess board and its pieces
+
+        # We will attach all of the squares to their own root. This way we can do the
+        # collision pass just on the sqaures and save the time of checking the rest
+        # of the scene
+        self.squareRoot = render.attachNewNode("squareRoot")
+
+        # For each square
+        self.squares = [None for i in range(64)]
+        self.pieces = [None for i in range(64)]
+        for i in range(64):
+            # Load, parent, color, and position the model (a single square
+            # polygon)
+            self.squares[i] = loader.loadModel("models/square")
+            self.squares[i].reparentTo(self.squareRoot)
+            self.squares[i].setPos(SquarePos(i))
+            self.squares[i].setColor(SquareColor(i))
+            # Set the model itself to be collideable with the ray. If this model was
+            # any more complex than a single polygon, you should set up a collision
+            # sphere around it instead. But for single polygons this works
+            # fine.
+            self.squares[i].find("**/polygon").node().setIntoCollideMask(
+                BitMask32.bit(1))
+            # Set a tag on the square's node so we can look up what square this is
+            # later during the collision pass
+            self.squares[i].find("**/polygon").node().setTag('square', str(i))
+
+            # We will use this variable as a pointer to whatever piece is currently
+            # in this square
+
+        # The order of pieces on a chessboard from white's perspective. This list
+        # contains the constructor functions for the piece classes defined
+        # below
+        pieceOrder = (Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook)
+
+        for i in range(8, 16):
+            # Load the white pawns
+            self.pieces[i] = Pawn(i, WHITE)
+        for i in range(48, 56):
+            # load the black pawns
+            self.pieces[i] = Pawn(i, PIECEBLACK)
+        for i in range(8):
+            # Load the special pieces for the front row and color them white
+            self.pieces[i] = pieceOrder[i](i, WHITE)
+            # Load the special pieces for the back row and color them black
+            self.pieces[i + 56] = pieceOrder[i](i + 56, PIECEBLACK)
+
+        # This will represent the index of the currently highlited square
+        self.hiSq = False
+        # This wil represent the index of the square where currently dragged piece
+        # was grabbed from
+        self.dragging = False
+
+        # Start the task that handles the picking
+        self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
+        self.accept("mouse1", self.grabPiece)  # left-click grabs a piece
+        self.accept("mouse1-up", self.releasePiece)  # releasing places it
+
+    # This function swaps the positions of two pieces
+    def swapPieces(self, fr, to):
+        temp = self.pieces[fr]
+        self.pieces[fr] = self.pieces[to]
+        self.pieces[to] = temp
+        if self.pieces[fr]:
+            self.pieces[fr].square = fr
+            self.pieces[fr].obj.setPos(SquarePos(fr))
+        if self.pieces[to]:
+            self.pieces[to].square = to
+            self.pieces[to].obj.setPos(SquarePos(to))
+
+    def mouseTask(self, task):
+        # This task deals with the highlighting and dragging based on the mouse
+
+        # First, clear the current highlight
+        if self.hiSq is not False:
+            self.squares[self.hiSq].setColor(SquareColor(self.hiSq))
+            self.hiSq = False
+
+        # Check to see if we can access the mouse. We need it to do anything
+        # else
+        if self.mouseWatcherNode.hasMouse():
+            # get the mouse position
+            mpos = self.mouseWatcherNode.getMouse()
+
+            # Set the position of the ray based on the mouse position
+            self.pickerRay.setFromLens(self.camNode, mpos.getX(), mpos.getY())
+
+            # If we are dragging something, set the position of the object
+            # to be at the appropriate point over the plane of the board
+            if self.dragging is not False:
+                # Gets the point described by pickerRay.getOrigin(), which is relative to
+                # camera, relative instead to render
+                nearPoint = render.getRelativePoint(
+                    camera, self.pickerRay.getOrigin())
+                # Same thing with the direction of the ray
+                nearVec = render.getRelativeVector(
+                    camera, self.pickerRay.getDirection())
+                self.pieces[self.dragging].obj.setPos(
+                    PointAtZ(.5, nearPoint, nearVec))
+
+            # Do the actual collision pass (Do it only on the squares for
+            # efficiency purposes)
+            self.picker.traverse(self.squareRoot)
+            if self.pq.getNumEntries() > 0:
+                # if we have hit something, sort the hits so that the closest
+                # is first, and highlight that node
+                self.pq.sortEntries()
+                i = int(self.pq.getEntry(0).getIntoNode().getTag('square'))
+                # Set the highlight on the picked square
+                self.squares[i].setColor(HIGHLIGHT)
+                self.hiSq = i
+
+        return Task.cont
+
+    def grabPiece(self):
+        # If a square is highlighted and it has a piece, set it to dragging
+        # mode
+        if self.hiSq is not False and self.pieces[self.hiSq]:
+            self.dragging = self.hiSq
+            self.hiSq = False
+
+    def releasePiece(self):
+        # Letting go of a piece. If we are not on a square, return it to its original
+        # position. Otherwise, swap it with the piece in the new square
+        # Make sure we really are dragging something
+        if self.dragging is not False:
+            # We have let go of the piece, but we are not on a square
+            if self.hiSq is False:
+                self.pieces[self.dragging].obj.setPos(
+                    SquarePos(self.dragging))
+            else:
+                # Otherwise, swap the pieces
+                self.swapPieces(self.dragging, self.hiSq)
+
+        # We are no longer dragging anything
+        self.dragging = False
+
+    def setupLights(self):  # This function sets up some default lighting
+        ambientLight = AmbientLight("ambientLight")
+        ambientLight.setColor((.8, .8, .8, 1))
+        directionalLight = DirectionalLight("directionalLight")
+        directionalLight.setDirection(LVector3(0, 45, -45))
+        directionalLight.setColor((0.2, 0.2, 0.2, 1))
+        render.setLight(render.attachNewNode(directionalLight))
+        render.setLight(render.attachNewNode(ambientLight))
+
+
+# Class for a piece. This just handels loading the model and setting initial
+# position and color
+class Piece(object):
+    def __init__(self, square, color):
+        self.obj = loader.loadModel(self.model)
+        self.obj.reparentTo(render)
+        self.obj.setColor(color)
+        self.obj.setPos(SquarePos(square))
+
+
+# Classes for each type of chess piece
+# Obviously, we could have done this by just passing a string to Piece's init.
+# But if you wanted to make rules for how the pieces move, a good place to start
+# would be to make an isValidMove(toSquare) method for each piece type
+# and then check if the destination square is acceptible during ReleasePiece
+class Pawn(Piece):
+    model = "models/pawn"
+
+class King(Piece):
+    model = "models/king"
+
+class Queen(Piece):
+    model = "models/queen"
+
+class Bishop(Piece):
+    model = "models/bishop"
+
+class Knight(Piece):
+    model = "models/knight"
+
+class Rook(Piece):
+    model = "models/rook"
+
+# Do the main initialization and start 3D rendering
+demo = ChessboardDemo()
+demo.run()

BIN
samples/chessboard/models/bishop.egg.pz


BIN
samples/chessboard/models/king.egg.pz


BIN
samples/chessboard/models/knight.egg.pz


BIN
samples/chessboard/models/pawn.egg.pz


BIN
samples/chessboard/models/queen.egg.pz


BIN
samples/chessboard/models/rook.egg.pz


BIN
samples/chessboard/models/square.egg.pz


+ 586 - 0
samples/culling/models/cells.egg

@@ -0,0 +1,586 @@
+<CoordinateSystem> { Y-Up }
+
+<Group> cell1 {
+  <VertexPool> cell1-ORG {
+    <Vertex> 0 {
+      -4 0 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      11.5789 0 -11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -11.5789 0 -11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -4 0 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      4 0 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      11.5789 0 11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 6 {
+      4 0 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 7 {
+      -11.5789 0 11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 2 3 0 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 5 1 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 6 7 5 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 1 0 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 7 3 2 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 6 5 4 <Ref> { cell1-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 7 6 <Ref> { cell1-ORG } }
+  }
+}
+<Group> cell2 {
+  <VertexPool> cell2-ORG {
+    <Vertex> 0 {
+      -4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell2-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 1 0 <Ref> { cell2-ORG } }
+  }
+}
+<Group> cell3 {
+  <VertexPool> cell3-ORG {
+    <Vertex> 0 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      0.243417 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -1.02178 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -3.36172 0 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      2.99397 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      2.99397 0 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 6 {
+      -3.36172 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 7 {
+      0.243417 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell3-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 1 3 2 <Ref> { cell3-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 5 1 <Ref> { cell3-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 6 2 3 <Ref> { cell3-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 7 1 0 <Ref> { cell3-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 3 1 <Ref> { cell3-ORG } }
+  }
+}
+<Group> cell4 {
+  <VertexPool> cell4-ORG {
+    <Vertex> 0 {
+      2.99397 0 0.527741
+      <Normal> { 0 0.854764 -0.519017 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      2.04443 1.03879 2.23852
+      <Normal> { 0.158619 0.946967 -0.279453 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      2.99397 1.03879 2.23852
+      <Normal> { 0 0.983872 -0.178875 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      2.04443 1.03879 3.18072
+      <Normal> { 0.309844 0.950787 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      2.99397 1.03879 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      0.519489 2.15079 2.23852
+      <Normal> { 0.320561 0.934229 0.156384 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 6 {
+      0.519489 2.15079 3.18072
+      <Normal> { 0.205355 0.978688 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 7 {
+      -0.423812 2.15079 2.23852
+      <Normal> { 0 0.95348 0.301457 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 8 {
+      -0.423812 2.15079 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 9 {
+      0.519489 3.3704 0.502572
+      <Normal> { 0 0.916633 0.399729 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 10 {
+      -0.423812 3.3704 0.502572
+      <Normal> { 0 0.979823 0.199865 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 11 {
+      0.519489 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 12 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 13 {
+      2.04443 0 0.527741
+      <Normal> { 0 0.854764 -0.519017 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 2 3 4 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 5 6 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 6 7 8 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 7 9 10 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 10 11 12 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 13 1 0 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 1 3 2 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 1 5 3 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 7 6 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 9 7 <Ref> { cell4-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 9 11 10 <Ref> { cell4-ORG } }
+  }
+}
+<Group> cell5 {
+  <VertexPool> cell5-ORG {
+    <Vertex> 0 {
+      2.99397 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 1 {
+      -3.36172 3.3704 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -3.36172 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 3 {
+      2.99397 3.3704 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell5-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 1 0 <Ref> { cell5-ORG } }
+  }
+}
+<Group> cell6 {
+  <VertexPool> cell6-ORG {
+    <Vertex> 0 {
+      2.99397 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      0.997627 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      2.99397 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      0.997627 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell6-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 1 0 <Ref> { cell6-ORG } }
+  }
+}
+<Group> cell7 {
+  <VertexPool> cell7-ORG {
+    <Vertex> 0 {
+      -3.36172 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      -0.900963 3.3704 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -0.900963 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -2.30748 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      -2.30748 3.3704 2.05045
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      -3.36172 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell7-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 0 3 4 <Ref> { cell7-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 1 0 <Ref> { cell7-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 3 0 <Ref> { cell7-ORG } }
+  }
+}
+<Group> cell8 {
+  <VertexPool> cell8-ORG {
+    <Vertex> 0 {
+      -2.30748 3.3704 2.23852
+      <Normal> { -0.430354 0.90266 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      1.82483 5.34053 3.18072
+      <Normal> { -0.430354 0.90266 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      1.82483 5.34053 2.23852
+      <Normal> { -0.430354 0.90266 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -2.30748 3.3704 3.18072
+      <Normal> { -0.430354 0.90266 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell8-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 3 1 0 <Ref> { cell8-ORG } }
+  }
+}
+<Group> cell9 {
+  <VertexPool> cell9-ORG {
+    <Vertex> 0 {
+      1.82483 5.34053 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 1 {
+      2.99397 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      1.82483 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 5 {
+      2.99397 5.34053 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell9-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 2 3 4 <Ref> { cell9-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 1 0 <Ref> { cell9-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 1 3 2 <Ref> { cell9-ORG } }
+  }
+}
+<Group> cell10 {
+  <VertexPool> cell10-ORG {
+    <Vertex> 0 {
+      -3.36172 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      -2.36149 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      -2.36149 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      -3.36172 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell10-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 0 3 4 <Ref> { cell10-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 1 0 <Ref> { cell10-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 3 0 <Ref> { cell10-ORG } }
+  }
+}
+<Group> cell11 {
+  <VertexPool> cell11-ORG {
+    <Vertex> 0 {
+      -2.36149 5.34053 1.0558
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.999 0.999 0.999 1 }
+    }
+    <Vertex> 1 {
+      -1.90136 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 2 {
+      -1.90136 5.34053 1.0558
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 3 {
+      1.31869 5.34053 0.350091
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 4 {
+      1.31869 5.34053 -2.1928
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 5 {
+      -1.90136 5.34053 -2.16895
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 6 {
+      1.31869 5.34053 -0.718624
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 7 {
+      1.82483 5.34053 0.350091
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 8 {
+      -2.36149 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 9 {
+      1.31869 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 10 {
+      1.82483 5.34053 -0.718624
+      <Normal> { 0 1 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 1 3 2 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 5 6 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 6 2 3 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 7 6 3 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 8 1 0 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 9 3 1 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 5 2 6 <Ref> { cell11-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 10 6 7 <Ref> { cell11-ORG } }
+  }
+}

+ 2435 - 0
samples/culling/models/level.egg

@@ -0,0 +1,2435 @@
+<CoordinateSystem> { Y-Up }
+
+<Group> levelgeo {
+  <VertexPool> levelgeo-ORG {
+    <Vertex> 0 {
+      -4 0 -4
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.551941 0.559784 0.544098 1 }
+    }
+    <Vertex> 1 {
+      -4 7.57015 -4
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 2 {
+      4 7.57015 -4
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 3 {
+      4 0 -4
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 4 {
+      -4 0 4
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 5 {
+      4 0 4
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 6 {
+      4 7.57015 4
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 7 {
+      -4 7.57015 4
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 8 {
+      -4 0 -4
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.551941 0.559784 0.544098 1 }
+    }
+    <Vertex> 9 {
+      -4 0 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 10 {
+      -4 1.48611 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 11 {
+      -4 7.57015 -4
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 12 {
+      4 7.57015 -4
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 13 {
+      4 1.48611 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 14 {
+      4 0 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 15 {
+      4 0 -4
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 16 {
+      -4 7.57015 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 17 {
+      -4 7.57015 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 18 {
+      4 7.57015 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 19 {
+      4 7.57015 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 20 {
+      -4 0 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.375471 0.497039 0.001 1 }
+    }
+    <Vertex> 21 {
+      4 0 -4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 22 {
+      11.5789 0 -11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.462745 0.713726 0.192157 1 }
+    }
+    <Vertex> 23 {
+      -11.5789 0 -11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.462745 0.713726 0.192157 1 }
+    }
+    <Vertex> 24 {
+      4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 25 {
+      -4 0 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 26 {
+      -4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 27 {
+      -11.5789 0 11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.462745 0.713726 0.192157 1 }
+    }
+    <Vertex> 28 {
+      4 0 4
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 29 {
+      11.5789 0 11.5789
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.462745 0.713726 0.192157 1 }
+    }
+    <Vertex> 30 {
+      -4 0 4
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 31 {
+      -4 7.57015 4
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 32 {
+      -4 1.48611 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 33 {
+      -4 0 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 34 {
+      4 0 4
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 35 {
+      4 0 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 36 {
+      4 1.48611 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.552941 0.560784 0.545098 1 }
+    }
+    <Vertex> 37 {
+      4 7.57015 4
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 38 {
+      -4 1.48611 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 39 {
+      -4 0 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 40 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 41 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 42 {
+      -4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 43 {
+      -4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 44 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 45 {
+      -4 0 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 46 {
+      -4 1.48611 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 47 {
+      2.04443 1.48611 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 48 {
+      2.04443 0 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 49 {
+      -4 1.48611 0.527741
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 50 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 51 {
+      0.243412 1.48611 -0.838305
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 52 {
+      2.04443 1.48611 0.527741
+      <Normal> { 0 -0.983257 0.182222 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 53 {
+      2.99397 0 -0.838305
+      <Normal> { 1.47369e-009 -2.57304e-010 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 54 {
+      2.99397 1.48611 -0.838305
+      <Normal> { 1.47369e-009 -2.57304e-010 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 55 {
+      0.243412 1.48611 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 56 {
+      0.243417 0 -0.838305
+      <Normal> { 0 -5.3759e-011 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 57 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 58 {
+      0.243417 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 59 {
+      0.243417 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 60 {
+      -1.02178 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 61 {
+      0.243417 0 -0.838305
+      <Normal> { -1 -3.33861e-006 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 62 {
+      0.243412 1.48611 -0.838305
+      <Normal> { -1 -3.33861e-006 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 63 {
+      0.243412 1.48611 -1.3609
+      <Normal> { -1 -3.33861e-006 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 64 {
+      0.243417 0 -1.36315
+      <Normal> { -1 -3.33861e-006 0 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 65 {
+      0.243412 1.48611 -0.838305
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 66 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 67 {
+      -1.03253 1.48611 -1.3609
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 68 {
+      0.243412 1.48611 -1.3609
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 69 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0.999974 0.00723416 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 70 {
+      -1.02178 0 -0.838305
+      <Normal> { 0.999974 0.00723416 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 71 {
+      -1.02178 0 -1.36315
+      <Normal> { 0.999974 0.00723416 0 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 72 {
+      -1.03253 1.48611 -1.3609
+      <Normal> { 0.999974 0.00723416 0 }
+      <RGBA> { 0.458824 0.666667 0.729412 1 }
+    }
+    <Vertex> 73 {
+      0.243412 1.48611 -1.3609
+      <Normal> { -0.00013873 0.000502693 -1 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 74 {
+      2.99397 3.00428 -1.3609
+      <Normal> { -0.00013873 0.000502693 -1 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 75 {
+      2.99397 0 -1.36315
+      <Normal> { -0.00027746 0.00100539 -0.999999 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 76 {
+      0.243417 0 -1.36315
+      <Normal> { -0.00027746 0.00100539 -0.999999 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 77 {
+      -1.02178 0 -1.36315
+      <Normal> { 0.000327154 0.00100617 -0.999999 }
+      <RGBA> { 0.713726 0.721569 0.788235 1 }
+    }
+    <Vertex> 78 {
+      -3.36172 0 -1.36315
+      <Normal> { 0.000327154 0.00100617 -0.999999 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 79 {
+      -3.36172 3.00428 -1.3609
+      <Normal> { 0.000163577 0.000503084 -1 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 80 {
+      -1.03253 1.48611 -1.3609
+      <Normal> { 0.000163577 0.000503084 -1 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 81 {
+      2.99397 0 -3.42013
+      <Normal> { 0 -1.77621e-006 1 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 82 {
+      2.99397 3.00428 -3.42012
+      <Normal> { 0 -1.77621e-006 1 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 83 {
+      -3.36172 3.00428 -3.42012
+      <Normal> { 0 -1.77621e-006 1 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 84 {
+      -3.36172 0 -3.42013
+      <Normal> { 0 -1.77621e-006 1 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 85 {
+      -3.36172 3.00428 -1.3609
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 86 {
+      -3.36172 3.00428 -3.42012
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 87 {
+      2.99397 3.00428 -3.42012
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 88 {
+      2.99397 3.00428 -1.3609
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 89 {
+      2.99397 0 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.505882 0.721569 0.788235 1 }
+    }
+    <Vertex> 90 {
+      -3.36172 0 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.505882 0.721569 0.788235 1 }
+    }
+    <Vertex> 91 {
+      0.519489 4.88145 0.507012
+      <Normal> { 0 -0.953298 -0.302032 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 92 {
+      -0.423826 4.88145 0.507012
+      <Normal> { 0 -0.953298 -0.302032 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 93 {
+      -0.423826 4.88145 0.120042
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 94 {
+      0.519489 4.88145 0.120042
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 95 {
+      0.519489 3.3704 0.502572
+      <Normal> { -1 3.06469e-012 1.07738e-012 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 96 {
+      0.519489 4.88145 0.507012
+      <Normal> { -1 3.06469e-012 1.07738e-012 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 97 {
+      0.519489 4.88145 0.120042
+      <Normal> { -1 3.06627e-012 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 98 {
+      0.519489 3.3704 0.115602
+      <Normal> { -1 3.06627e-012 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 99 {
+      2.99397 1.48611 -0.838305
+      <Normal> { 8.64537e-011 -1 7.27066e-011 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 100 {
+      2.99397 1.48611 0.527741
+      <Normal> { 5.96697e-011 -0.983257 0.182222 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 101 {
+      -4 1.48611 -0.838305
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 102 {
+      -2.30748 3.3704 3.18072
+      <Normal> { -0.220612 0.975362 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 103 {
+      -2.30748 3.3704 2.23852
+      <Normal> { -0.146659 0.989187 0 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 104 {
+      -3.36172 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 105 {
+      -0.900963 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 106 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 107 {
+      -3.36172 3.3704 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 108 {
+      0.997627 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 109 {
+      2.99397 3.3704 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 110 {
+      0.519489 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 111 {
+      2.99397 5.12958 0.115606
+      <Normal> { 3.51998e-009 -1 -1.29401e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 112 {
+      0.997627 5.12958 0.115602
+      <Normal> { 3.53529e-009 -1 -1.28696e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 113 {
+      2.99397 5.12958 -3.42013
+      <Normal> { 3.54741e-009 -1 -1.28263e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 114 {
+      2.99397 0 -3.42013
+      <Normal> { -1 -1.07371e-007 0 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 115 {
+      2.99397 0 -1.36315
+      <Normal> { -1 -1.07371e-007 0 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 116 {
+      2.99397 3.00428 -1.3609
+      <Normal> { -1 -1.07371e-007 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 117 {
+      2.99397 3.00428 -3.42012
+      <Normal> { -1 -1.07371e-007 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 118 {
+      -0.423826 4.88145 0.507012
+      <Normal> { 1 9.25347e-006 3.25304e-006 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 119 {
+      -0.423812 3.3704 0.502572
+      <Normal> { 1 9.25347e-006 3.25304e-006 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 120 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 1 9.25824e-006 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 121 {
+      -0.423826 4.88145 0.120042
+      <Normal> { 1 9.25824e-006 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 122 {
+      2.99397 2.54985 3.18072
+      <Normal> { 0 7.83887e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 123 {
+      2.99397 1.03879 3.18072
+      <Normal> { 0 7.83887e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 124 {
+      2.04443 1.03879 3.18072
+      <Normal> { 2.85806e-008 7.83886e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 125 {
+      2.04443 2.54985 3.18072
+      <Normal> { 2.85806e-008 7.83886e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 126 {
+      2.99397 1.48611 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 127 {
+      2.99397 0 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 128 {
+      2.99397 1.03879 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 129 {
+      2.99397 2.54985 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 130 {
+      2.99397 0 0.527741
+      <Normal> { 0 0.983872 -0.178875 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 131 {
+      2.04443 0 0.527741
+      <Normal> { 0 0.983872 -0.178875 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 132 {
+      2.04443 1.03879 2.23852
+      <Normal> { 0.212236 0.959168 -0.186958 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 133 {
+      2.99397 1.03879 2.23852
+      <Normal> { 0 0.963007 -0.269477 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 134 {
+      2.04443 0 0.527741
+      <Normal> { 1 -1.48049e-006 9.09752e-007 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 135 {
+      2.04443 1.48611 0.527741
+      <Normal> { 1 -1.48049e-006 9.09752e-007 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 136 {
+      2.04443 2.54985 2.23852
+      <Normal> { 1 -1.48049e-006 9.09752e-007 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 137 {
+      2.04443 1.03879 2.23852
+      <Normal> { 1 -1.48049e-006 9.09752e-007 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 138 {
+      2.04443 1.48611 0.527741
+      <Normal> { 0 -0.983257 0.182222 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 139 {
+      2.99397 1.48611 0.527741
+      <Normal> { 5.96697e-011 -0.983257 0.182222 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 140 {
+      2.99397 2.54985 2.23852
+      <Normal> { 0 -0.961568 0.274569 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 141 {
+      2.04443 2.54985 2.23852
+      <Normal> { -0.212513 -0.958418 0.190453 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 142 {
+      2.99397 1.03879 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 143 {
+      2.99397 2.54985 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 144 {
+      2.04443 1.03879 3.18072
+      <Normal> { 0.309844 0.950787 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 145 {
+      2.99397 1.03879 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 146 {
+      -0.423812 2.15079 2.23852
+      <Normal> { 1 9.25347e-006 3.25304e-006 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 147 {
+      -0.423826 3.66184 2.23852
+      <Normal> { 1 9.25347e-006 3.25304e-006 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 148 {
+      -0.423826 3.66184 3.18072
+      <Normal> { 1 9.25824e-006 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 149 {
+      -0.423812 2.15079 3.18072
+      <Normal> { 1 9.25824e-006 0 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 150 {
+      2.99397 2.54985 3.18072
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 151 {
+      2.04443 2.54985 3.18072
+      <Normal> { -0.309844 -0.950787 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 152 {
+      2.04443 1.03879 2.23852
+      <Normal> { 7.18913e-009 9.85887e-009 1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 153 {
+      2.04443 2.54985 2.23852
+      <Normal> { 7.18913e-009 9.85887e-009 1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 154 {
+      0.519489 3.66184 2.23852
+      <Normal> { 7.18913e-009 9.85887e-009 1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 155 {
+      0.519489 2.15079 2.23852
+      <Normal> { 7.18913e-009 9.85887e-009 1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 156 {
+      0.519489 3.66184 3.18072
+      <Normal> { -0.309844 -0.950787 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 157 {
+      0.519489 3.66184 2.23852
+      <Normal> { -0.214114 -0.95413 -0.209266 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 158 {
+      0.519489 2.15079 3.18072
+      <Normal> { 2.85806e-008 7.83886e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 159 {
+      0.519489 3.66184 3.18072
+      <Normal> { 2.85806e-008 7.83886e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 160 {
+      0.519489 2.15079 2.23852
+      <Normal> { 0.214078 0.954223 0.208873 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 161 {
+      0.519489 2.15079 3.18072
+      <Normal> { 0.309844 0.950787 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 162 {
+      -0.423812 3.3704 0.502572
+      <Normal> { 0 0.95348 0.301457 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 163 {
+      0.519489 3.3704 0.502572
+      <Normal> { 0 0.95348 0.301457 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 164 {
+      0.519489 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 165 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 166 {
+      -0.423826 3.66184 3.18072
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 167 {
+      -0.423826 3.66184 2.23852
+      <Normal> { 0 -0.953298 -0.302032 }
+      <RGBA> { 0.733333 0.462745 0.294118 1 }
+    }
+    <Vertex> 168 {
+      -0.423812 2.15079 3.18072
+      <Normal> { 0 7.83887e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 169 {
+      -0.423826 3.66184 3.18072
+      <Normal> { 0 7.83887e-008 -1 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 170 {
+      -0.423812 2.15079 2.23852
+      <Normal> { 0 0.95348 0.301457 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 171 {
+      -0.423812 2.15079 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.901961 0.654902 0.152941 1 }
+    }
+    <Vertex> 172 {
+      0.519489 2.15079 2.23852
+      <Normal> { -1 3.06311e-012 2.15477e-012 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 173 {
+      0.519489 3.66184 2.23852
+      <Normal> { -1 3.06311e-012 2.15477e-012 }
+      <RGBA> { 0.733333 0.654902 0.294118 1 }
+    }
+    <Vertex> 174 {
+      -3.36172 3.00428 -3.42012
+      <Normal> { 1 -6.80267e-009 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 175 {
+      -3.36172 3.00428 -1.3609
+      <Normal> { 1 -6.80267e-009 0 }
+      <RGBA> { 0.686275 0.890196 0.796078 1 }
+    }
+    <Vertex> 176 {
+      -3.36172 0 -1.36315
+      <Normal> { 1 -6.80267e-009 0 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 177 {
+      -3.36172 0 -3.42013
+      <Normal> { 1 -6.80267e-009 0 }
+      <RGBA> { 0.458824 0.776471 0.796078 1 }
+    }
+    <Vertex> 178 {
+      -2.36149 7.09573 -2.49425
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 179 {
+      -3.36172 7.09573 -3.42013
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 180 {
+      -0.485624 7.09573 -3.42013
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 181 {
+      -0.485624 7.09573 -2.49425
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 182 {
+      2.99397 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 183 {
+      0.997627 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 184 {
+      2.99397 3.3704 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 185 {
+      -1.90136 7.09573 -2.16895
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 186 {
+      1.31869 7.09573 -0.718624
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 187 {
+      1.31869 7.09573 0.350091
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 188 {
+      -1.90136 7.09573 1.0558
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 189 {
+      -3.36172 3.3704 0.115602
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 190 {
+      -2.30748 3.3704 2.05045
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 191 {
+      -0.900963 3.3704 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 192 {
+      1.82483 5.34053 3.18072
+      <Normal> { -0.220612 0.975362 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 193 {
+      1.82483 5.34053 2.23852
+      <Normal> { -0.146659 0.989187 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 194 {
+      2.99397 5.34053 3.18072
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 195 {
+      2.99397 5.34053 2.23852
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 196 {
+      1.82483 5.34053 0.350091
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 197 {
+      2.99397 7.09573 -3.42013
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 198 {
+      1.82483 7.09573 -2.49425
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 199 {
+      -2.36149 7.09573 1.0558
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 200 {
+      -3.36172 7.09573 2.04979
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 201 {
+      1.82483 5.34053 -0.718624
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 202 {
+      1.31869 5.34053 -0.718624
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 203 {
+      1.31869 5.34053 0.350091
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 204 {
+      -2.36149 5.34053 1.0558
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 205 {
+      -2.36149 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 206 {
+      -1.90136 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 207 {
+      -1.90136 5.34053 1.0558
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 208 {
+      2.99397 3.3704 -3.42013
+      <Normal> { -4.07833e-010 -6.17821e-011 1 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 209 {
+      2.99397 5.12958 -3.42013
+      <Normal> { -4.07833e-010 -6.17821e-011 1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 210 {
+      -3.36172 5.12958 -3.42013
+      <Normal> { -4.07833e-010 -6.17821e-011 1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 211 {
+      -3.36172 3.3704 -3.42013
+      <Normal> { -4.07833e-010 -6.17821e-011 1 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 212 {
+      2.99397 5.12958 1.67403
+      <Normal> { 3.51106e-009 -1 -1.29564e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 213 {
+      0.997673 5.12958 1.67402
+      <Normal> { 3.51106e-009 -1 -1.29564e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 214 {
+      2.99397 3.3704 -3.42013
+      <Normal> { -1 -8.366e-011 9.98999e-012 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 215 {
+      2.99397 3.3704 0.115602
+      <Normal> { -1 6.51735e-011 6.38546e-011 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 216 {
+      2.99397 5.12958 0.115606
+      <Normal> { -1 9.77602e-011 9.57819e-011 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 217 {
+      2.99397 5.12958 -3.42013
+      <Normal> { -1 -8.366e-011 9.98999e-012 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 218 {
+      -3.36172 3.3704 0.115602
+      <Normal> { 1 2.60413e-011 1.94624e-012 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 219 {
+      -3.36172 3.3704 -3.42013
+      <Normal> { 1 2.23911e-011 1.9372e-012 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 220 {
+      -3.36172 5.12958 -3.42013
+      <Normal> { 1 2.23911e-011 1.9372e-012 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 221 {
+      -3.36172 5.12958 0.115593
+      <Normal> { 1 2.60413e-011 1.94624e-012 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 222 {
+      2.99397 4.51665 1.67402
+      <Normal> { -1 1.3959e-010 9.07869e-011 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 223 {
+      2.99397 5.12958 1.67403
+      <Normal> { -1 2.7918e-010 1.81574e-010 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 224 {
+      -3.36172 5.12958 0.115593
+      <Normal> { 3.60372e-009 -1 -1.29603e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 225 {
+      -2.30746 5.12958 2.05044
+      <Normal> { 3.60957e-009 -1 -1.29785e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 226 {
+      -2.30746 5.12958 2.23852
+      <Normal> { 0.146407 -0.989224 -1.50609e-007 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 227 {
+      -3.36172 5.12958 3.18072
+      <Normal> { 3.62962e-009 -1 -1.30501e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 228 {
+      2.99397 3.3704 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 229 {
+      0.997627 3.3704 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 230 {
+      0.997627 4.51665 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 231 {
+      2.99397 4.51665 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 232 {
+      -3.36172 5.12958 3.18072
+      <Normal> { 1 2.96915e-011 1.95529e-012 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 233 {
+      -3.36172 3.3704 3.18072
+      <Normal> { 1 2.96915e-011 1.95529e-012 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 234 {
+      0.997627 3.3704 3.18072
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 235 {
+      0.997627 3.3704 0.115602
+      <Normal> { 1 -9.56319e-006 -3.85741e-006 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 236 {
+      0.997627 4.51665 1.67402
+      <Normal> { 1 -9.56319e-006 -3.85741e-006 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 237 {
+      0.997627 4.51665 3.18072
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 238 {
+      -2.30748 3.3704 3.18072
+      <Normal> { 4.42741e-011 -1.09614e-010 -1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 239 {
+      -3.36172 3.3704 3.18072
+      <Normal> { 2.75919e-011 -1.51613e-010 -1 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 240 {
+      -3.36172 5.12958 3.18072
+      <Normal> { 2.75919e-011 -1.51613e-010 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 241 {
+      -2.30746 5.12958 3.18072
+      <Normal> { 4.42741e-011 -1.09614e-010 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 242 {
+      1.82483 5.34053 3.18072
+      <Normal> { 3.04782e-011 -3.38078e-011 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 243 {
+      1.82483 7.09573 3.18072
+      <Normal> { 3.04782e-011 -3.38078e-011 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 244 {
+      2.99397 5.34053 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 245 {
+      2.99397 7.09573 3.18072
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 246 {
+      2.99397 5.34053 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 247 {
+      2.99397 5.34053 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 248 {
+      2.99397 7.09573 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 249 {
+      2.99397 7.09573 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 250 {
+      2.99397 5.34053 -3.42013
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 251 {
+      2.99397 7.09573 -3.42013
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 252 {
+      2.99397 5.34053 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 253 {
+      2.99397 7.09573 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 254 {
+      -0.485624 7.09573 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 255 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 256 {
+      -3.36172 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 257 {
+      -2.36149 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 258 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 259 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 260 {
+      -3.36172 5.34053 -3.42013
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 261 {
+      -3.36172 7.09573 -3.42013
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 262 {
+      -3.36172 7.09573 2.04979
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 263 {
+      -3.36172 5.34053 2.04979
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 264 {
+      -3.36172 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 265 {
+      -2.36149 5.34053 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 266 {
+      -3.36172 5.34053 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 267 {
+      -3.36172 7.09573 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 268 {
+      -2.36149 7.09573 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 269 {
+      -1.90136 5.34053 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 270 {
+      -1.90136 7.09573 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 271 {
+      -0.900963 3.3704 0.115602
+      <Normal> { -1 7.02332e-006 1.01878e-006 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 272 {
+      -0.900963 3.3704 2.04979
+      <Normal> { -1 7.02332e-006 1.01878e-006 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 273 {
+      -0.900949 5.12958 2.04979
+      <Normal> { -1 7.02332e-006 1.01878e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 274 {
+      -0.900953 5.12958 0.115599
+      <Normal> { -1 7.02332e-006 1.01878e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 275 {
+      -0.900963 3.3704 2.04979
+      <Normal> { -0.000468015 -2.28319e-006 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 276 {
+      -2.30748 3.3704 2.05045
+      <Normal> { -0.000468015 -2.28319e-006 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 277 {
+      -2.30746 5.12958 2.05044
+      <Normal> { -0.000468015 -2.28319e-006 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 278 {
+      -0.900949 5.12958 2.04979
+      <Normal> { -0.000468015 -2.28319e-006 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 279 {
+      -2.30748 3.3704 2.05045
+      <Normal> { -1 1.2768e-005 1.17008e-006 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 280 {
+      -2.30748 3.3704 2.23852
+      <Normal> { -1 1.2768e-005 1.17008e-006 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 281 {
+      -2.30746 5.12958 2.23852
+      <Normal> { -1 1.2768e-005 1.17008e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 282 {
+      -2.30746 5.12958 2.05044
+      <Normal> { -1 1.2768e-005 1.17008e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 283 {
+      -2.30748 3.3704 2.23852
+      <Normal> { 7.06683e-012 -7.83886e-012 1 }
+      <RGBA> { 1 1 1 1 }
+    }
+    <Vertex> 284 {
+      1.82483 5.34053 2.23852
+      <Normal> { 7.06683e-012 -7.83886e-012 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 285 {
+      1.82483 7.09573 2.23852
+      <Normal> { 7.06683e-012 -7.83886e-012 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 286 {
+      -2.30746 5.12958 2.23852
+      <Normal> { 7.06683e-012 -7.83886e-012 1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 287 {
+      1.82483 5.34053 2.23852
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 288 {
+      1.82483 5.34053 0.350091
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 289 {
+      1.82483 7.09573 0.350091
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 290 {
+      1.82483 7.09573 2.23852
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 291 {
+      -0.423826 4.88145 0.120042
+      <Normal> { 0.00250514 -0.00827025 -0.999963 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 292 {
+      -0.900953 5.12958 0.115599
+      <Normal> { 0.00250514 -0.00827025 -0.999963 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 293 {
+      0.997627 5.12958 0.115602
+      <Normal> { -0.00249668 -0.00826966 -0.999963 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 294 {
+      0.519489 4.88145 0.120042
+      <Normal> { -0.00249668 -0.00826966 -0.999963 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 295 {
+      0.997627 3.3704 0.115602
+      <Normal> { -0.00499447 0.00135754 -0.999987 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 296 {
+      0.519489 3.3704 0.115602
+      <Normal> { -0.00499447 0.00135754 -0.999987 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 297 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0.00500867 0.00135636 -0.999987 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 298 {
+      -0.900963 3.3704 0.115602
+      <Normal> { 0.00500867 0.00135636 -0.999987 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 299 {
+      2.99397 3.3704 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.658824 0.760784 1 }
+    }
+    <Vertex> 300 {
+      2.99397 4.51665 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 301 {
+      0.997627 4.51665 3.18072
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 302 {
+      0.997627 4.51665 1.67402
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 303 {
+      2.99397 4.51665 1.67402
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 304 {
+      2.99397 4.51665 3.18072
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 305 {
+      -0.900953 5.12958 0.115599
+      <Normal> { 3.58039e-009 -1 -1.28204e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 306 {
+      -3.36172 5.12958 -3.42013
+      <Normal> { 3.57897e-009 -1 -1.28263e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 307 {
+      -2.36149 5.34053 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 308 {
+      -2.36149 7.09573 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 309 {
+      -0.485624 7.09573 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 310 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 311 {
+      0.997627 5.12958 0.115602
+      <Normal> { 1 -1.91264e-005 -7.71482e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 312 {
+      0.997673 5.12958 1.67402
+      <Normal> { 1 -1.91264e-005 -7.71482e-006 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 313 {
+      2.99397 4.51665 1.67402
+      <Normal> { 3.17248e-006 2.8011e-006 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 314 {
+      0.997627 4.51665 1.67402
+      <Normal> { 3.17248e-006 2.8011e-006 -1 }
+      <RGBA> { 0.733333 0.462745 0.760784 1 }
+    }
+    <Vertex> 315 {
+      0.997673 5.12958 1.67402
+      <Normal> { 3.17248e-006 2.8011e-006 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 316 {
+      2.99397 5.12958 1.67403
+      <Normal> { 3.17248e-006 2.8011e-006 -1 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 317 {
+      -0.900949 5.12958 2.04979
+      <Normal> { 3.58324e-009 -1 -1.28086e-009 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 318 {
+      -3.36172 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.505882 0.721569 0.788235 1 }
+    }
+    <Vertex> 319 {
+      2.99397 0 -1.36315
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.505882 0.721569 0.788235 1 }
+    }
+    <Vertex> 320 {
+      1.82483 7.09573 2.23852
+      <Normal> { 0.146407 -0.989224 -1.4972e-007 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 321 {
+      1.82483 7.09573 3.18072
+      <Normal> { 0.220231 -0.975448 -2.25215e-007 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 322 {
+      -2.30746 5.12958 3.18072
+      <Normal> { 0.220231 -0.975448 -2.25879e-007 }
+      <RGBA> { 0.376471 0.498039 0.760784 1 }
+    }
+    <Vertex> 323 {
+      2.99397 7.09573 2.23852
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 324 {
+      2.99397 7.09573 3.18072
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 325 {
+      1.82483 7.09573 0.350091
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 326 {
+      1.82483 7.09573 -0.718624
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 327 {
+      -1.90136 5.34053 -2.16895
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 328 {
+      1.82483 5.34053 -0.718624
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 329 {
+      1.82483 5.34053 -2.49425
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 330 {
+      1.82483 7.09573 -2.49425
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 331 {
+      1.82483 7.09573 -0.718624
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 332 {
+      1.82483 5.34053 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 333 {
+      1.82483 7.09573 -2.49425
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 334 {
+      -2.36149 5.34053 -2.49425
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 335 {
+      -2.36149 5.34053 1.0558
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 336 {
+      -2.36149 7.09573 1.0558
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 337 {
+      -2.36149 7.09573 -2.49425
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 338 {
+      1.31869 5.34053 -2.1928
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 339 {
+      1.31869 5.34053 2.04979
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 340 {
+      1.31869 5.34053 0.350091
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 341 {
+      1.31869 5.34053 2.04979
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 342 {
+      1.31869 7.09573 2.04979
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 343 {
+      1.31869 7.09573 0.350091
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 344 {
+      1.31869 5.34053 0.350091
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 345 {
+      1.31869 7.09573 0.350091
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 346 {
+      1.82483 7.09573 0.350091
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 347 {
+      1.82483 5.34053 0.350091
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 348 {
+      1.31869 7.09573 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 349 {
+      1.31869 5.34053 2.04979
+      <Normal> { 0 0 -1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 350 {
+      1.31869 5.34053 -2.1928
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 351 {
+      1.31869 5.34053 -0.718624
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 352 {
+      1.31869 7.09573 -0.718624
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 353 {
+      1.31869 7.09573 -2.1928
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 354 {
+      -1.90136 5.34053 -2.16895
+      <Normal> { 0.00740377 0 0.999973 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 355 {
+      1.31869 5.34053 -2.1928
+      <Normal> { 0.00740377 0 0.999973 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 356 {
+      1.31869 7.09573 -2.1928
+      <Normal> { 0.00740377 0 0.999973 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 357 {
+      -1.90136 7.09573 -2.16895
+      <Normal> { 0.00740377 0 0.999973 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 358 {
+      -1.90136 5.34053 1.0558
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 359 {
+      -1.90136 5.34053 -2.16895
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 360 {
+      -1.90136 7.09573 -2.16895
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 361 {
+      -1.90136 7.09573 1.0558
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 362 {
+      -1.90136 5.34053 1.0558
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 363 {
+      -1.90136 7.09573 1.0558
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 364 {
+      -2.36149 7.09573 1.0558
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 365 {
+      -2.36149 5.34053 1.0558
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 366 {
+      1.31869 7.09573 -0.718624
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 367 {
+      1.31869 5.34053 -0.718624
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 368 {
+      1.82483 5.34053 -0.718624
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 369 {
+      1.82483 7.09573 -0.718624
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 370 {
+      -3.36172 7.09573 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 371 {
+      -3.36172 5.34053 -3.42013
+      <Normal> { 0 0 1 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 372 {
+      1.31869 7.09573 -2.1928
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 373 {
+      -1.90136 7.09573 2.04979
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 374 {
+      1.31869 7.09573 2.04979
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 375 {
+      -2.36149 7.09573 2.04979
+      <Normal> { 0 -1 0 }
+      <RGBA> { 0.482353 0.560784 0.611765 1 }
+    }
+    <Vertex> 376 {
+      2.99397 5.34053 -3.42013
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 377 {
+      1.82483 5.34053 -2.49425
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.752941 0.752941 0.752941 1 }
+    }
+    <Vertex> 378 {
+      4 0 -0.838305
+      <Normal> { 2.94739e-009 -4.60849e-010 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 379 {
+      4 1.48611 -0.838305
+      <Normal> { 2.94739e-009 -4.60849e-010 1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 380 {
+      2.99397 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 381 {
+      2.99397 0 0.527741
+      <Normal> { 0 0.983872 -0.178875 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 382 {
+      4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 383 {
+      4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 384 {
+      4 1.48611 0.527741
+      <Normal> { -1.20596e-007 -9.54098e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 385 {
+      4 0 0.527741
+      <Normal> { -1.20596e-007 -9.54098e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 386 {
+      2.99397 0 0.527741
+      <Normal> { -1.20596e-007 -9.54098e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 387 {
+      2.99397 1.48611 0.527741
+      <Normal> { -1.20596e-007 -9.54098e-009 -1 }
+      <RGBA> { 0.729412 0.513726 0.341176 1 }
+    }
+    <Vertex> 388 {
+      4 1.48611 -0.838305
+      <Normal> { 1.72907e-010 -1 1.45424e-010 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 389 {
+      4 1.48611 0.527741
+      <Normal> { 1.72907e-010 -1 1.45424e-010 }
+      <RGBA> { 0.729412 0.396078 0.341176 1 }
+    }
+    <Vertex> 390 {
+      -4 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 391 {
+      4 0 0.527741
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.376471 0.498039 0 1 }
+    }
+    <Vertex> 392 {
+      0.243417 0 -0.838305
+      <Normal> { 0 1 0 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+    <Vertex> 393 {
+      2.04443 0 0.527741
+      <Normal> { 0 0.983872 -0.178875 }
+      <RGBA> { 0.729412 0.192157 0.247059 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 4 5 6 7 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 8 9 10 11 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 12 13 14 15 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 16 17 18 19 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 20 21 22 23 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 21 24 22 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 25 26 27 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 28 25 27 29 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 30 31 32 33 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 11 10 32 31 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 34 35 36 37 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 38 39 40 41 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 42 43 44 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 45 46 47 48 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 49 50 51 52 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 53 54 55 56 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 57 58 59 60 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 61 62 63 64 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 65 66 67 68 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 69 70 71 72 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 73 74 75 76 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 77 78 79 80 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 81 82 83 84 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 85 86 87 88 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 80 79 74 73 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 59 89 90 60 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 91 92 93 94 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 95 96 97 98 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 99 100 52 51 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 101 50 49 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 102 103 104 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 105 106 107 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 108 109 110 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 111 112 113 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 114 115 116 117 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 118 119 120 121 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 122 123 124 125 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 126 127 128 129 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 130 131 132 133 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 134 135 136 137 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 138 139 140 141 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 129 128 142 143 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 133 132 144 145 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 146 147 148 149 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 141 140 150 151 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 152 153 154 155 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 141 151 156 157 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 125 124 158 159 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 144 132 160 161 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 162 163 164 165 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 157 156 166 167 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 159 158 168 169 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 161 160 170 171 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 172 173 96 95 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 157 167 92 91 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 147 146 119 118 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 170 160 163 162 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 174 175 176 177 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 110 109 107 106 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 178 179 180 181 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 182 108 183 184 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 185 186 187 188 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 189 190 191 105 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 103 190 189 104 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 103 102 192 193 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 193 192 194 195 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 193 195 196 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 197 198 181 180 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 179 178 199 200 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 196 201 202 203 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 204 205 206 207 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 208 209 210 211 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 182 109 108 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 112 111 212 213 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 214 215 216 217 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 218 219 220 221 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 215 222 223 216 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 224 225 226 227 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 228 229 230 231 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 218 221 232 233 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 234 235 236 237 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 238 239 240 241 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 242 238 241 243 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 244 242 243 245 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 246 247 248 249 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 250 246 249 251 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 252 253 254 255 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 256 257 258 259 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 260 261 262 263 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 264 204 257 256 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 265 266 267 268 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 269 265 268 270 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 271 272 273 274 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 275 276 277 278 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 279 280 281 282 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 283 284 285 286 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 287 288 289 290 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 291 292 293 294 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 293 295 296 294 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 291 297 298 292 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 205 204 264 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 299 300 222 215 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 301 302 303 304 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 113 112 305 306 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 307 308 309 310 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 311 312 236 235 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 313 314 315 316 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 306 305 224 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 224 305 317 225 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 318 60 90 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 319 89 59 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 226 320 321 322 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 323 324 321 320 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 323 325 326 197 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 202 327 207 203 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 328 329 330 331 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 332 310 309 333 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 334 335 336 337 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 338 327 202 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 206 339 203 207 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 340 341 342 343 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 344 345 346 347 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 348 349 269 270 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 350 351 352 353 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 354 355 356 357 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 358 359 360 361 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 362 363 364 365 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 366 367 368 369 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 325 187 186 326 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 189 105 107 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 370 371 255 254 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 197 326 198 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 372 186 185 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 373 188 187 374 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 320 325 323 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 199 188 373 375 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 200 199 375 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 376 201 196 195 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 377 201 376 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 37 36 13 12 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 378 379 54 53 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 380 381 382 383 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 384 385 386 387 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 100 99 388 389 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 390 23 27 26 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 20 23 390 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 391 29 22 24 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 28 29 391 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 381 380 392 393 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 393 392 44 43 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 377 376 259 258 <Ref> { levelgeo-ORG } }
+  }
+  <Polygon> {
+    <VertexRef> { 322 227 226 <Ref> { levelgeo-ORG } }
+  }
+}

+ 562 - 0
samples/culling/models/occluders.egg

@@ -0,0 +1,562 @@
+<CoordinateSystem> { Y-Up }
+
+<Group> occluder1 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder1-ORG {
+    <Vertex> 0 {
+      4 7.57015 4
+    }
+    <Vertex> 1 {
+      4 7.57015 -4
+    }
+    <Vertex> 2 {
+      -4 7.57015 -4
+    }
+    <Vertex> 3 {
+      -4 7.57015 4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder1-ORG } }
+  }
+}
+<Group> occluder4 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder4-ORG {
+    <Vertex> 0 {
+      4 2.96946e-016 -0.84965
+    }
+    <Vertex> 1 {
+      4 7.57015 -0.84965
+    }
+    <Vertex> 2 {
+      4 7.57015 -4
+    }
+    <Vertex> 3 {
+      4 0 -4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder4-ORG } }
+  }
+}
+<Group> occluder2 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder2-ORG {
+    <Vertex> 0 {
+      4 0 -4
+    }
+    <Vertex> 1 {
+      4 7.57015 -4
+    }
+    <Vertex> 2 {
+      -4 7.57015 -4
+    }
+    <Vertex> 3 {
+      -4 0 -4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder2-ORG } }
+  }
+}
+<Group> occluder3 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder3-ORG {
+    <Vertex> 0 {
+      4 0 4
+    }
+    <Vertex> 1 {
+      4 7.57015 4
+    }
+    <Vertex> 2 {
+      -4 7.57015 4
+    }
+    <Vertex> 3 {
+      -4 0 4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder3-ORG } }
+  }
+}
+<Group> occluder5 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder5-ORG {
+    <Vertex> 0 {
+      4 0 4
+    }
+    <Vertex> 1 {
+      4 7.57015 4
+    }
+    <Vertex> 2 {
+      4 7.57015 0.551412
+    }
+    <Vertex> 3 {
+      4 -2.78684e-016 0.551412
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder5-ORG } }
+  }
+}
+<Group> occluder6 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder6-ORG {
+    <Vertex> 0 {
+      4 1.49418 4
+    }
+    <Vertex> 1 {
+      4 7.57015 4
+    }
+    <Vertex> 2 {
+      4 7.57015 -4
+    }
+    <Vertex> 3 {
+      4 1.49418 -4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder6-ORG } }
+  }
+}
+<Group> occluder7 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder7-ORG {
+    <Vertex> 0 {
+      -3.99576 2.96946e-016 -0.84965
+    }
+    <Vertex> 1 {
+      -3.99576 7.57015 -0.84965
+    }
+    <Vertex> 2 {
+      -3.99576 7.57015 -4
+    }
+    <Vertex> 3 {
+      -3.99576 0 -4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder7-ORG } }
+  }
+}
+<Group> occluder8 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder8-ORG {
+    <Vertex> 0 {
+      -3.99576 0 4
+    }
+    <Vertex> 1 {
+      -3.99576 7.57015 4
+    }
+    <Vertex> 2 {
+      -3.99576 7.57015 0.551412
+    }
+    <Vertex> 3 {
+      -3.99576 -2.78684e-016 0.551412
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder8-ORG } }
+  }
+}
+<Group> occluder9 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder9-ORG {
+    <Vertex> 0 {
+      -3.99576 1.49418 4
+    }
+    <Vertex> 1 {
+      -3.99576 7.57015 4
+    }
+    <Vertex> 2 {
+      -3.99576 7.57015 -4
+    }
+    <Vertex> 3 {
+      -3.99576 1.49418 -4
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder9-ORG } }
+  }
+}
+<Group> occluder10 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder10-ORG {
+    <Vertex> 0 {
+      -4 1.49418 -1.13843
+    }
+    <Vertex> 1 {
+      -4 3.24289 -1.13843
+    }
+    <Vertex> 2 {
+      4 3.24289 -1.13843
+    }
+    <Vertex> 3 {
+      4 1.49418 -1.13843
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder10-ORG } }
+  }
+}
+<Group> occluder11 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder11-ORG {
+    <Vertex> 0 {
+      -4 0 -1.13843
+    }
+    <Vertex> 1 {
+      -4 3.24289 -1.13843
+    }
+    <Vertex> 2 {
+      -1.01962 3.24289 -1.13843
+    }
+    <Vertex> 3 {
+      -1.01962 -2.78684e-016 -1.13843
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder11-ORG } }
+  }
+}
+<Group> occluder12 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder12-ORG {
+    <Vertex> 0 {
+      0.242712 2.96946e-016 -1.13843
+    }
+    <Vertex> 1 {
+      0.242712 3.24289 -1.13843
+    }
+    <Vertex> 2 {
+      4 3.24289 -1.13843
+    }
+    <Vertex> 3 {
+      4 0 -1.13843
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder12-ORG } }
+  }
+}
+<Group> occluder13 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder13-ORG {
+    <Vertex> 0 {
+      -4 3.19567 0.643347
+    }
+    <Vertex> 1 {
+      -4 3.19567 -3.99828
+    }
+    <Vertex> 2 {
+      4 3.19567 -3.99828
+    }
+    <Vertex> 3 {
+      4 3.19567 0.643347
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder13-ORG } }
+  }
+}
+<Group> occluder14 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder14-ORG {
+    <Vertex> 0 {
+      -4 3.19567 4.00073
+    }
+    <Vertex> 1 {
+      -4 3.19567 -3.99828
+    }
+    <Vertex> 2 {
+      -0.508583 3.19567 -3.99828
+    }
+    <Vertex> 3 {
+      -0.508583 3.19567 4.00073
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder14-ORG } }
+  }
+}
+<Group> occluder15 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder15-ORG {
+    <Vertex> 0 {
+      1.25869 3.19567 3.98826
+    }
+    <Vertex> 1 {
+      1.25869 3.19567 -3.99828
+    }
+    <Vertex> 2 {
+      4 3.19567 -3.99828
+    }
+    <Vertex> 3 {
+      4 3.19567 3.98826
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder15-ORG } }
+  }
+}
+<Group> occluder16 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder16-ORG {
+    <Vertex> 0 {
+      -4 5.26173 2.21946
+    }
+    <Vertex> 1 {
+      -4 5.26173 -3.99828
+    }
+    <Vertex> 2 {
+      4 5.26173 -3.99828
+    }
+    <Vertex> 3 {
+      4 5.26173 2.21946
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder16-ORG } }
+  }
+}
+<Group> occluder17 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder17-ORG {
+    <Vertex> 0 {
+      1.78343 5.26173 3.98826
+    }
+    <Vertex> 1 {
+      1.78343 5.26173 -3.99828
+    }
+    <Vertex> 2 {
+      4 5.26173 -3.99828
+    }
+    <Vertex> 3 {
+      4 5.26173 3.98826
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder17-ORG } }
+  }
+}
+<Group> occluder18 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder18-ORG {
+    <Vertex> 0 {
+      -3.99383 5.23279 2.0951
+    }
+    <Vertex> 1 {
+      1.8032 5.23279 2.0951
+    }
+    <Vertex> 2 {
+      1.8032 7.56072 2.0951
+    }
+    <Vertex> 3 {
+      -3.99383 7.56072 2.0951
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder18-ORG } }
+  }
+}
+<Group> occluder19 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder19-ORG {
+    <Vertex> 0 {
+      -3.93576 0.0379318 0.6175
+    }
+    <Vertex> 1 {
+      1.94837 0.0379318 0.6175
+    }
+    <Vertex> 2 {
+      1.94837 3.21608 0.6175
+    }
+    <Vertex> 3 {
+      -3.93576 3.21608 0.6175
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder19-ORG } }
+  }
+}
+<Group> occluder20 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder20-ORG {
+    <Vertex> 0 {
+      11.5843 -0.215275 11.5843
+    }
+    <Vertex> 1 {
+      11.5843 -0.215275 -11.5843
+    }
+    <Vertex> 2 {
+      -11.5843 -0.215275 -11.5843
+    }
+    <Vertex> 3 {
+      -11.5843 -0.215275 11.5843
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder20-ORG } }
+  }
+}
+<Group> occluder21 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder21-ORG {
+    <Vertex> 0 {
+      -3.93576 1.56418 0.5914
+    }
+    <Vertex> 1 {
+      3.94341 1.56418 0.5914
+    }
+    <Vertex> 2 {
+      3.94341 3.26834 0.5914
+    }
+    <Vertex> 3 {
+      -3.93576 3.26834 0.5914
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder21-ORG } }
+  }
+}
+<Group> occluder22 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder22-ORG {
+    <Vertex> 0 {
+      -0.694875 1.49418 2.11153
+    }
+    <Vertex> 1 {
+      -0.694875 5.26968 2.11153
+    }
+    <Vertex> 2 {
+      -0.694875 5.26968 0.21275
+    }
+    <Vertex> 3 {
+      -0.694875 1.49418 0.21275
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder22-ORG } }
+  }
+}
+<Group> occluder23 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder23-ORG {
+    <Vertex> 0 {
+      0.756062 1.49418 2.11153
+    }
+    <Vertex> 1 {
+      0.756062 5.26968 2.11153
+    }
+    <Vertex> 2 {
+      0.756062 5.26968 0.21275
+    }
+    <Vertex> 3 {
+      0.756062 1.49418 0.21275
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder23-ORG } }
+  }
+}
+<Group> occluder24 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder24-ORG {
+    <Vertex> 0 {
+      1.60244 5.2309 2.14179
+    }
+    <Vertex> 1 {
+      1.60244 7.52263 2.14179
+    }
+    <Vertex> 2 {
+      1.60244 7.52263 0.394334
+    }
+    <Vertex> 3 {
+      1.60244 5.2309 0.394334
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder24-ORG } }
+  }
+}
+<Group> occluder25 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder25-ORG {
+    <Vertex> 0 {
+      1.60244 5.2309 -0.763555
+    }
+    <Vertex> 1 {
+      1.60244 7.52263 -0.763555
+    }
+    <Vertex> 2 {
+      1.60244 7.52263 -2.42694
+    }
+    <Vertex> 3 {
+      1.60244 5.2309 -2.42694
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder25-ORG } }
+  }
+}
+<Group> occluder26 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder26-ORG {
+    <Vertex> 0 {
+      1.78944 5.2309 -2.34074
+    }
+    <Vertex> 1 {
+      1.78944 7.52263 -2.34074
+    }
+    <Vertex> 2 {
+      -2.28798 7.52263 -2.34074
+    }
+    <Vertex> 3 {
+      -2.28798 5.2309 -2.34074
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder26-ORG } }
+  }
+}
+<Group> occluder27 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder27-ORG {
+    <Vertex> 0 {
+      -2.15855 5.2309 1.03841
+    }
+    <Vertex> 1 {
+      -2.15855 7.52263 1.03841
+    }
+    <Vertex> 2 {
+      -2.15855 7.52263 -2.44292
+    }
+    <Vertex> 3 {
+      -2.15855 5.2309 -2.44292
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder27-ORG } }
+  }
+}
+<Group> occluder28 {
+  <Scalar> occluder { 1 }
+  <VertexPool> occluder28-ORG {
+    <Vertex> 0 {
+      -3.98717 1.54945 3.90197
+    }
+    <Vertex> 1 {
+      -3.98717 1.54945 -1.30625
+    }
+    <Vertex> 2 {
+      1.28595 1.54945 -1.30625
+    }
+    <Vertex> 3 {
+      1.28595 1.54945 3.90197
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { occluder28-ORG } }
+  }
+}

+ 674 - 0
samples/culling/models/portals.egg

@@ -0,0 +1,674 @@
+<CoordinateSystem> { Y-Up }
+
+<Group> portal_10to11_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_10to11_1-ORG {
+    <Vertex> 0 {
+      -2.36149 5.34053 2.04979
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      -2.36149 7.09573 2.04979
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      -2.36149 7.09573 1.0558
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      -2.36149 5.34053 1.0558
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_10to11_1-ORG } }
+  }
+}
+<Group> portal_1to2_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_1to2_1-ORG {
+    <Vertex> 0 {
+      4 4.44089e-016 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      4 1.48611 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 2 {
+      4 1.48611 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 3 {
+      4 4.44089e-016 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_1to2_1-ORG } }
+  }
+}
+<Group> portal_1to2_2 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_1to2_2-ORG {
+    <Vertex> 0 {
+      -4 0 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      -4 1.48611 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      -4 1.48611 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      -4 0 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_1to2_2-ORG } }
+  }
+}
+<Group> portal_2to3_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_2to3_1-ORG {
+    <Vertex> 0 {
+      0.243417 0 -0.838305
+      <Normal> { 0 -5.37589e-011 1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      0.243412 1.48611 -0.838305
+      <Normal> { 0 -5.37589e-011 1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 2 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0 -5.37589e-011 1 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 3 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 -5.37589e-011 1 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_2to3_1-ORG } }
+  }
+}
+<Group> portal_2to4_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_2to4_1-ORG {
+    <Vertex> 0 {
+      2.04443 0 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      2.04443 1.48611 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      2.99397 1.48611 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      2.99397 0 0.527741
+      <Normal> { 0 1.40115e-009 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_2to4_1-ORG } }
+  }
+}
+<Group> portal_4to5_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_4to5_1-ORG {
+    <Vertex> 0 {
+      0.519489 3.3704 0.115602
+      <Normal> { 0 -0.00293795 0.999996 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      0.519489 4.88145 0.120042
+      <Normal> { 0 -0.00293795 0.999996 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 2 {
+      -0.423826 4.88145 0.120042
+      <Normal> { 0 -0.00293795 0.999996 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 3 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0 -0.00293795 0.999996 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_4to5_1-ORG } }
+  }
+}
+<Group> portal_5to6_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_5to6_1-ORG {
+    <Vertex> 0 {
+      0.997627 3.3704 0.115602
+      <Normal> { 1.00717e-006 1.20465e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      0.997627 5.12958 0.115602
+      <Normal> { 1.00717e-006 1.20465e-006 -1 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      2.99397 5.12958 0.115606
+      <Normal> { 1.00717e-006 1.20465e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      2.99397 3.3704 0.115602
+      <Normal> { 1.00717e-006 1.20465e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_5to6_1-ORG } }
+  }
+}
+<Group> portal_5to7_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_5to7_1-ORG {
+    <Vertex> 0 {
+      -3.36172 3.3704 0.115602
+      <Normal> { 1.02518e-006 -3.5707e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      -3.36172 5.12958 0.115593
+      <Normal> { 1.02518e-006 -3.5707e-006 -1 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      -0.900953 5.12958 0.115599
+      <Normal> { 1.02518e-006 -3.5707e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      -0.900963 3.3704 0.115602
+      <Normal> { 1.02518e-006 -3.5707e-006 -1 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_5to7_1-ORG } }
+  }
+}
+<Group> portal_7to8_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_7to8_1-ORG {
+    <Vertex> 0 {
+      -2.30748 3.3704 3.18072
+      <Normal> { -1 1.34401e-005 1.02127e-006 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      -2.30746 5.12958 3.18072
+      <Normal> { -1 1.34401e-005 1.02127e-006 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      -2.30746 5.12958 2.23852
+      <Normal> { -1 1.34401e-005 1.02127e-006 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      -2.30748 3.3704 2.23852
+      <Normal> { -1 1.34401e-005 1.02127e-006 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_7to8_1-ORG } }
+  }
+}
+<Group> portal_8to9_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_8to9_1-ORG {
+    <Vertex> 0 {
+      1.82483 5.34053 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      1.82483 7.09573 3.18072
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 2 {
+      1.82483 7.09573 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 3 {
+      1.82483 5.34053 2.23852
+      <Normal> { -1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_8to9_1-ORG } }
+  }
+}
+<Group> portal_9to10_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_9to10_1-ORG {
+    <Vertex> 0 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      -0.485624 7.09573 -3.42013
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 2 {
+      -0.485624 7.09573 -2.49425
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 3 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_9to10_1-ORG } }
+  }
+}
+<Group> portal_9to11_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_9to11_1-ORG {
+    <Vertex> 0 {
+      1.82483 5.34053 -0.718624
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 1 {
+      1.82483 7.09573 -0.718624
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+    <Vertex> 2 {
+      1.82483 7.09573 0.350091
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.001 0.001 0.999 1 }
+    }
+    <Vertex> 3 {
+      1.82483 5.34053 0.350091
+      <Normal> { 1 0 0 }
+      <RGBA> { 0 0 1 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_9to11_1-ORG } }
+  }
+}
+<Group> portal_11to9_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_11to9_1-ORG {
+    <Vertex> 0 {
+      1.82483 5.34053 0.350091
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      1.82483 7.09573 0.350091
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 2 {
+      1.82483 7.09573 -0.718624
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 3 {
+      1.82483 5.34053 -0.718624
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_11to9_1-ORG } }
+  }
+}
+<Group> portal_10to9_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_10to9_1-ORG {
+    <Vertex> 0 {
+      -0.485624 5.34053 -2.49425
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -0.485624 7.09573 -2.49425
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 2 {
+      -0.485624 7.09573 -3.42013
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 3 {
+      -0.485624 5.34053 -3.42013
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_10to9_1-ORG } }
+  }
+}
+<Group> portal_9to8_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_9to8_1-ORG {
+    <Vertex> 0 {
+      1.82483 5.34053 2.23852
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      1.82483 7.09573 2.23852
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      1.82483 7.09573 3.18072
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      1.82483 5.34053 3.18072
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_9to8_1-ORG } }
+  }
+}
+<Group> portal_8to7_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_8to7_1-ORG {
+    <Vertex> 0 {
+      -2.30748 3.3704 2.23852
+      <Normal> { 1 -1.34401e-005 -1.02127e-006 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -2.30746 5.12958 2.23852
+      <Normal> { 1 -1.34401e-005 -1.02127e-006 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      -2.30746 5.12958 3.18072
+      <Normal> { 1 -1.34401e-005 -1.02127e-006 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      -2.30748 3.3704 3.18072
+      <Normal> { 1 -1.34401e-005 -1.02127e-006 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_8to7_1-ORG } }
+  }
+}
+<Group> portal_7to5_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_7to5_1-ORG {
+    <Vertex> 0 {
+      -0.900963 3.3704 0.115602
+      <Normal> { -1.02518e-006 3.5707e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -0.900953 5.12958 0.115599
+      <Normal> { -1.02518e-006 3.5707e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      -3.36172 5.12958 0.115593
+      <Normal> { -1.02518e-006 3.5707e-006 1 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      -3.36172 3.3704 0.115602
+      <Normal> { -1.02518e-006 3.5707e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_7to5_1-ORG } }
+  }
+}
+<Group> portal_6to5_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_6to5_1-ORG {
+    <Vertex> 0 {
+      2.99397 3.3704 0.115602
+      <Normal> { -1.00717e-006 -1.20465e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      2.99397 5.12958 0.115606
+      <Normal> { -1.00717e-006 -1.20465e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      0.997627 5.12958 0.115602
+      <Normal> { -1.00717e-006 -1.20465e-006 1 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      0.997627 3.3704 0.115602
+      <Normal> { -1.00717e-006 -1.20465e-006 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_6to5_1-ORG } }
+  }
+}
+<Group> portal_5to4_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_5to4_1-ORG {
+    <Vertex> 0 {
+      -0.423812 3.3704 0.115602
+      <Normal> { 0 0.00293795 -0.999996 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -0.423826 4.88145 0.120042
+      <Normal> { 0 0.00293795 -0.999996 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 2 {
+      0.519489 4.88145 0.120042
+      <Normal> { 0 0.00293795 -0.999996 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 3 {
+      0.519489 3.3704 0.115602
+      <Normal> { 0 0.00293795 -0.999996 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_5to4_1-ORG } }
+  }
+}
+<Group> portal_4to2_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_4to2_1-ORG {
+    <Vertex> 0 {
+      2.99397 0 0.527741
+      <Normal> { 0 -1.40115e-009 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      2.99397 1.48611 0.527741
+      <Normal> { 0 -1.40115e-009 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      2.04443 1.48611 0.527741
+      <Normal> { 0 -1.40115e-009 1 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      2.04443 0 0.527741
+      <Normal> { 0 -1.40115e-009 1 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_4to2_1-ORG } }
+  }
+}
+<Group> portal_3to2_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_3to2_1-ORG {
+    <Vertex> 0 {
+      -1.02178 0 -0.838305
+      <Normal> { 0 5.37589e-011 -1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -1.03253 1.48611 -0.838305
+      <Normal> { 0 5.37589e-011 -1 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 2 {
+      0.243412 1.48611 -0.838305
+      <Normal> { 0 5.37589e-011 -1 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 3 {
+      0.243417 0 -0.838305
+      <Normal> { 0 5.37589e-011 -1 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_3to2_1-ORG } }
+  }
+}
+<Group> portal_2to1_2 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_2to1_2-ORG {
+    <Vertex> 0 {
+      -4 0 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -4 1.48611 -0.838305
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      -4 1.48611 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      -4 0 0.527741
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_2to1_2-ORG } }
+  }
+}
+<Group> portal_2to1_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_2to1_1-ORG {
+    <Vertex> 0 {
+      4 4.44089e-016 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      4 1.48611 0.527741
+      <Normal> { -1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 2 {
+      4 1.48611 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 3 {
+      4 4.44089e-016 -0.838305
+      <Normal> { -1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_2to1_1-ORG } }
+  }
+}
+<Group> portal_11to10_1 {
+  <Scalar> portal { 1 }
+  <VertexPool> portal_11to10_1-ORG {
+    <Vertex> 0 {
+      -2.36149 5.34053 1.0558
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 1 {
+      -2.36149 7.09573 1.0558
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+    <Vertex> 2 {
+      -2.36149 7.09573 2.04979
+      <Normal> { 1 0 0 }
+      <RGBA> { 0.999 0.001 0.001 1 }
+    }
+    <Vertex> 3 {
+      -2.36149 5.34053 2.04979
+      <Normal> { 1 0 0 }
+      <RGBA> { 1 0 0 1 }
+    }
+  }
+  <Polygon> {
+    <VertexRef> { 0 1 2 3 <Ref> { portal_11to10_1-ORG } }
+  }
+}

BIN
samples/culling/models/tex_0.png


BIN
samples/culling/models/tex_1.png


BIN
samples/culling/models/tex_2.png


BIN
samples/culling/models/tex_3.png


BIN
samples/culling/models/tex_4.png


BIN
samples/culling/models/tex_5.png


BIN
samples/culling/models/tex_6.png


BIN
samples/culling/models/tex_7.png


+ 159 - 0
samples/culling/occluder_culling.py

@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+
+"""
+Author: Josh Enes
+Last Updated: 2015-03-13
+
+This is a demo of Panda's occluder-culling system. It demonstrates loading
+occluder from an EGG file and adding them to a CullTraverser.
+"""
+
+# Load PRC data
+from panda3d.core import loadPrcFileData
+loadPrcFileData('', 'window-title Occluder Demo')
+loadPrcFileData('', 'sync-video false')
+loadPrcFileData('', 'show-frame-rate-meter true')
+loadPrcFileData('', 'texture-minfilter linear-mipmap-linear')
+#loadPrcFileData('', 'fake-view-frustum-cull true') # show culled nodes in red
+
+# Import needed modules
+import random
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import PerspectiveLens, TextNode, \
+TexGenAttrib, TextureStage, TransparencyAttrib, LPoint3, Texture
+
+
+def add_instructions(pos, msg):
+    """Function to put instructions on the screen."""
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.05)
+
+def add_title(text):
+    """Function to put title on the screen."""
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+
+class Game(ShowBase):
+    """Sets up the game, camera, controls, and loads models."""
+    def __init__(self):
+        ShowBase.__init__(self)
+        self.xray_mode = False
+        self.show_model_bounds = False
+
+        # Display instructions
+        add_title("Panda3D Tutorial: Occluder Culling")
+        add_instructions(0.06, "[Esc]: Quit")
+        add_instructions(0.12, "[W]: Move Forward")
+        add_instructions(0.18, "[A]: Move Left")
+        add_instructions(0.24, "[S]: Move Right")
+        add_instructions(0.30, "[D]: Move Back")
+        add_instructions(0.36, "Arrow Keys: Look Around")
+        add_instructions(0.42, "[F]: Toggle Wireframe")
+        add_instructions(0.48, "[X]: Toggle X-Ray Mode")
+        add_instructions(0.54, "[B]: Toggle Bounding Volumes")
+
+        # Setup controls
+        self.keys = {}
+        for key in ['arrow_left', 'arrow_right', 'arrow_up', 'arrow_down',
+                    'a', 'd', 'w', 's']:
+            self.keys[key] = 0
+            self.accept(key, self.push_key, [key, 1])
+            self.accept('shift-%s' % key, self.push_key, [key, 1])
+            self.accept('%s-up' % key, self.push_key, [key, 0])
+        self.accept('f', self.toggleWireframe)
+        self.accept('x', self.toggle_xray_mode)
+        self.accept('b', self.toggle_model_bounds)
+        self.accept('escape', __import__('sys').exit, [0])
+        self.disableMouse()
+
+        # Setup camera
+        self.lens = PerspectiveLens()
+        self.lens.setFov(60)
+        self.lens.setNear(0.01)
+        self.lens.setFar(1000.0)
+        self.cam.node().setLens(self.lens)
+        self.camera.setPos(-9, -0.5, 1)
+        self.heading = -95.0
+        self.pitch = 0.0
+
+        # Load level geometry
+        self.level_model = self.loader.loadModel('models/level')
+        self.level_model.reparentTo(self.render)
+        self.level_model.setTexGen(TextureStage.getDefault(),
+                                   TexGenAttrib.MWorldPosition)
+        self.level_model.setTexProjector(TextureStage.getDefault(),
+                                         self.render, self.level_model)
+        self.level_model.setTexScale(TextureStage.getDefault(), 4)
+        tex = self.loader.load3DTexture('models/tex_#.png')
+        self.level_model.setTexture(tex)
+
+        # Load occluders
+        occluder_model = self.loader.loadModel('models/occluders')
+        occluder_nodepaths = occluder_model.findAllMatches('**/+OccluderNode')
+        for occluder_nodepath in occluder_nodepaths:
+            self.render.setOccluder(occluder_nodepath)
+            occluder_nodepath.node().setDoubleSided(True)
+
+        # Randomly spawn some models to test the occluders
+        self.models = []
+        box_model = self.loader.loadModel('box')
+
+        for dummy in xrange(0, 500):
+            pos = LPoint3((random.random() - 0.5) * 9,
+                         (random.random() - 0.5) * 9,
+                         random.random() * 8)
+            box = box_model.copy_to(self.render)
+            box.setScale(random.random() * 0.2 + 0.1)
+            box.setPos(pos)
+            box.setHpr(random.random() * 360,
+                         random.random() * 360,
+                         random.random() * 360)
+            box.reparentTo(self.render)
+            self.models.append(box)
+
+        self.taskMgr.add(self.update, 'main loop')
+
+    def push_key(self, key, value):
+        """Stores a value associated with a key."""
+        self.keys[key] = value
+
+    def update(self, task):
+        """Updates the camera based on the keyboard input."""
+        delta = globalClock.getDt()
+        move_x = delta * 3 * -self.keys['a'] + delta * 3 * self.keys['d']
+        move_z = delta * 3 * self.keys['s'] + delta * 3 * -self.keys['w']
+        self.camera.setPos(self.camera, move_x, -move_z, 0)
+        self.heading += (delta * 90 * self.keys['arrow_left'] +
+                         delta * 90 * -self.keys['arrow_right'])
+        self.pitch += (delta * 90 * self.keys['arrow_up'] +
+                       delta * 90 * -self.keys['arrow_down'])
+        self.camera.setHpr(self.heading, self.pitch, 0)
+        return task.cont
+
+    def toggle_xray_mode(self):
+        """Toggle X-ray mode on and off. This is useful for seeing the
+        effectiveness of the occluder culling."""
+        self.xray_mode = not self.xray_mode
+        if self.xray_mode:
+            self.level_model.setColorScale((1, 1, 1, 0.5))
+            self.level_model.setTransparency(TransparencyAttrib.MDual)
+        else:
+            self.level_model.setColorScaleOff()
+            self.level_model.setTransparency(TransparencyAttrib.MNone)
+
+    def toggle_model_bounds(self):
+        """Toggle bounding volumes on and off on the models."""
+        self.show_model_bounds = not self.show_model_bounds
+        if self.show_model_bounds:
+            for model in self.models:
+                model.showBounds()
+        else:
+            for model in self.models:
+                model.hideBounds()
+
+game = Game()
+game.run()

+ 306 - 0
samples/culling/portal_culling.py

@@ -0,0 +1,306 @@
+#!/usr/bin/env python
+
+"""
+Author: Josh Enes
+Last Updated: 2015-03-13
+
+This is a demo of Panda's portal-culling system. It demonstrates loading
+portals from an EGG file, and shows an example method of selecting the
+current cell using geoms and a collision ray.
+"""
+
+# Some config options which can be changed.
+ENABLE_PORTALS = True # Set False to disable portal culling and see FPS drop!
+DEBUG_PORTALS = False # Set True to see visually which portals are used
+
+# Load PRC data
+from panda3d.core import loadPrcFileData
+if ENABLE_PORTALS:
+    loadPrcFileData('', 'allow-portal-cull true')
+    if DEBUG_PORTALS:
+        loadPrcFileData('', 'debug-portal-cull true')
+loadPrcFileData('', 'window-title Portal Demo')
+loadPrcFileData('', 'sync-video false')
+loadPrcFileData('', 'show-frame-rate-meter true')
+loadPrcFileData('', 'texture-minfilter linear-mipmap-linear')
+
+# Import needed modules
+import random
+from direct.showbase.ShowBase import ShowBase
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import PerspectiveLens, NodePath, LVector3, LPoint3, \
+    TexGenAttrib, TextureStage, TransparencyAttrib, CollisionTraverser, \
+    CollisionHandlerQueue, TextNode, CollisionRay, CollisionNode
+
+
+def add_instructions(pos, msg):
+    """Function to put instructions on the screen."""
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.05)
+
+def add_title(text):
+    """Function to put title on the screen."""
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+
+class Game(ShowBase):
+    """Sets up the game, camera, controls, and loads models."""
+    def __init__(self):
+        ShowBase.__init__(self)
+        self.cellmanager = CellManager(self)
+        self.xray_mode = False
+        self.show_model_bounds = False
+
+        # Display instructions
+        add_title("Panda3D Tutorial: Portal Culling")
+        add_instructions(0.06, "[Esc]: Quit")
+        add_instructions(0.12, "[W]: Move Forward")
+        add_instructions(0.18, "[A]: Move Left")
+        add_instructions(0.24, "[S]: Move Right")
+        add_instructions(0.30, "[D]: Move Back")
+        add_instructions(0.36, "Arrow Keys: Look Around")
+        add_instructions(0.42, "[F]: Toggle Wireframe")
+        add_instructions(0.48, "[X]: Toggle X-Ray Mode")
+        add_instructions(0.54, "[B]: Toggle Bounding Volumes")
+
+        # Setup controls
+        self.keys = {}
+        for key in ['arrow_left', 'arrow_right', 'arrow_up', 'arrow_down',
+                    'a', 'd', 'w', 's']:
+            self.keys[key] = 0
+            self.accept(key, self.push_key, [key, 1])
+            self.accept('shift-%s' % key, self.push_key, [key, 1])
+            self.accept('%s-up' % key, self.push_key, [key, 0])
+        self.accept('f', self.toggleWireframe)
+        self.accept('x', self.toggle_xray_mode)
+        self.accept('b', self.toggle_model_bounds)
+        self.accept('escape', __import__('sys').exit, [0])
+        self.disableMouse()
+
+        # Setup camera
+        lens = PerspectiveLens()
+        lens.setFov(60)
+        lens.setNear(0.01)
+        lens.setFar(1000.0)
+        self.cam.node().setLens(lens)
+        self.camera.setPos(-9, -0.5, 1)
+        self.heading = -95.0
+        self.pitch = 0.0
+
+        # Load level geometry
+        self.level_model = self.loader.loadModel('models/level')
+        self.level_model.reparentTo(self.render)
+        self.level_model.setTexGen(TextureStage.getDefault(),
+                                   TexGenAttrib.MWorldPosition)
+        self.level_model.setTexProjector(TextureStage.getDefault(),
+                                         self.render, self.level_model)
+        self.level_model.setTexScale(TextureStage.getDefault(), 4)
+        tex = self.loader.load3DTexture('models/tex_#.png')
+        self.level_model.setTexture(tex)
+
+        # Load cells
+        self.cellmanager.load_cells_from_model('models/cells')
+        # Load portals
+        self.cellmanager.load_portals_from_model('models/portals')
+
+        # Randomly spawn some models to test the portals
+        self.models = []
+        for dummy in xrange(0, 500):
+            pos = LPoint3((random.random() - 0.5) * 6,
+                         (random.random() - 0.5) * 6,
+                         random.random() * 7)
+            cell = self.cellmanager.get_cell(pos)
+            if cell is None: # skip if the random position is not over a cell
+                continue
+            dist = self.cellmanager.get_dist_to_cell(pos)
+            if dist > 1.5: # skip if the random position is too far from ground
+                continue
+            box = self.loader.loadModel('box')
+            box.setScale(random.random() * 0.2 + 0.1)
+            box.setPos(pos)
+            box.setHpr(random.random() * 360,
+                         random.random() * 360,
+                         random.random() * 360)
+            box.reparentTo(cell.nodepath)
+            self.models.append(box)
+        self.taskMgr.add(self.update, 'main loop')
+
+    def push_key(self, key, value):
+        """Stores a value associated with a key."""
+        self.keys[key] = value
+
+    def update(self, task):
+        """Updates the camera based on the keyboard input. Once this is
+        done, then the CellManager's update function is called."""
+        delta = globalClock.getDt()
+        move_x = delta * 3 * -self.keys['a'] + delta * 3 * self.keys['d']
+        move_z = delta * 3 * self.keys['s'] + delta * 3 * -self.keys['w']
+        self.camera.setPos(self.camera, move_x, -move_z, 0)
+        self.heading += (delta * 90 * self.keys['arrow_left'] +
+                         delta * 90 * -self.keys['arrow_right'])
+        self.pitch += (delta * 90 * self.keys['arrow_up'] +
+                       delta * 90 * -self.keys['arrow_down'])
+        self.camera.setHpr(self.heading, self.pitch, 0)
+        if ENABLE_PORTALS:
+            self.cellmanager.update()
+        return task.cont
+
+    def toggle_xray_mode(self):
+        """Toggle X-ray mode on and off. This is useful for seeing the
+        effectiveness of the portal culling."""
+        self.xray_mode = not self.xray_mode
+        if self.xray_mode:
+            self.level_model.setColorScale((1, 1, 1, 0.5))
+            self.level_model.setTransparency(TransparencyAttrib.MDual)
+        else:
+            self.level_model.setColorScaleOff()
+            self.level_model.setTransparency(TransparencyAttrib.MNone)
+
+    def toggle_model_bounds(self):
+        """Toggle bounding volumes on and off on the models."""
+        self.show_model_bounds = not self.show_model_bounds
+        if self.show_model_bounds:
+            for model in self.models:
+                model.showBounds()
+        else:
+            for model in self.models:
+                model.hideBounds()
+
+
+class CellManager(object):
+    """Creates a collision ray and collision traverser to use for
+    selecting the current cell."""
+    def __init__(self, game):
+        self.game = game
+        self.cells = {}
+        self.cells_by_collider = {}
+        self.cell_picker_world = NodePath('cell_picker_world')
+        self.ray = CollisionRay()
+        self.ray.setDirection(LVector3.down())
+        cnode = CollisionNode('cell_raycast_cnode')
+        self.ray_nodepath = self.cell_picker_world.attachNewNode(cnode)
+        self.ray_nodepath.node().addSolid(self.ray)
+        self.ray_nodepath.node().setIntoCollideMask(0) # not for colliding into
+        self.ray_nodepath.node().setFromCollideMask(1)
+        self.traverser = CollisionTraverser('traverser')
+        self.last_known_cell = None
+
+    def add_cell(self, collider, name):
+        """Add a new cell."""
+        cell = Cell(self, name, collider)
+        self.cells[name] = cell
+        self.cells_by_collider[collider.node()] = cell
+
+    def get_cell(self, pos):
+        """Given a position, return the nearest cell below that position.
+        If no cell is found, returns None."""
+        self.ray.setOrigin(pos)
+        queue = CollisionHandlerQueue()
+        self.traverser.addCollider(self.ray_nodepath, queue)
+        self.traverser.traverse(self.cell_picker_world)
+        self.traverser.removeCollider(self.ray_nodepath)
+        queue.sortEntries()
+        if not queue.getNumEntries():
+            return None
+        entry = queue.getEntry(0)
+        cnode = entry.getIntoNode()
+        try:
+            return self.cells_by_collider[cnode]
+        except KeyError:
+            raise Warning('collision ray collided with something '
+                          'other than a cell: %s' % cnode)
+
+    def get_dist_to_cell(self, pos):
+        """Given a position, return the distance to the nearest cell
+        below that position. If no cell is found, returns None."""
+        self.ray.setOrigin(pos)
+        queue = CollisionHandlerQueue()
+        self.traverser.addCollider(self.ray_nodepath, queue)
+        self.traverser.traverse(self.cell_picker_world)
+        self.traverser.removeCollider(self.ray_nodepath)
+        queue.sortEntries()
+        if not queue.getNumEntries():
+            return None
+        entry = queue.getEntry(0)
+        return (entry.getSurfacePoint(self.cell_picker_world) - pos).length()
+
+    def load_cells_from_model(self, modelpath):
+        """Loads cells from an EGG file. Cells must be named in the
+        format "cell#" to be loaded by this function."""
+        cell_model = self.game.loader.loadModel(modelpath)
+        for collider in cell_model.findAllMatches('**/+GeomNode'):
+            name = collider.getName()
+            if name.startswith('cell'):
+                self.add_cell(collider, name[4:])
+        cell_model.removeNode()
+
+    def load_portals_from_model(self, modelpath):
+        """Loads portals from an EGG file. Portals must be named in the
+        format "portal_#to#_*" to be loaded by this function, whereby the
+        first # is the from cell, the second # is the into cell, and * can
+        be anything."""
+        portal_model = loader.loadModel(modelpath)
+        portal_nodepaths = portal_model.findAllMatches('**/+PortalNode')
+        for portal_nodepath in portal_nodepaths:
+            name = portal_nodepath.getName()
+            if name.startswith('portal_'):
+                from_cell_id, into_cell_id = name.split('_')[1].split('to')
+                try:
+                    from_cell = self.cells[from_cell_id]
+                except KeyError:
+                    print ('could not load portal "%s" because cell "%s"'
+                           'does not exist' % (name, from_cell_id))
+                    continue
+                try:
+                    into_cell = self.cells[into_cell_id]
+                except KeyError:
+                    print ('could not load portal "%s" because cell "%s"'
+                           'does not exist' % (name, into_cell_id))
+                    continue
+                from_cell.add_portal(portal_nodepath, into_cell)
+        portal_model.removeNode()
+
+    def update(self):
+        """Show the cell the camera is currently in and hides the rest.
+        If the camera is not in a cell, use the last known cell that the
+        camera was in. If the camera has not yet been in a cell, then all
+        cells will be hidden."""
+        camera_pos = self.game.camera.getPos(self.game.render)
+        for cell in self.cells:
+            self.cells[cell].nodepath.hide()
+        current_cell = self.get_cell(camera_pos)
+        if current_cell is None:
+            if self.last_known_cell is None:
+                return
+            self.last_known_cell.nodepath.show()
+        else:
+            self.last_known_cell = current_cell
+            current_cell.nodepath.show()
+
+
+class Cell(object):
+    """The Cell class is a handy way to keep an association between
+    all the related nodes and information of a cell."""
+    def __init__(self, cellmanager, name, collider):
+        self.cellmanager = cellmanager
+        self.name = name
+        self.collider = collider
+        self.collider.reparentTo(self.cellmanager.cell_picker_world)
+        self.collider.setCollideMask(1)
+        self.collider.hide()
+        self.nodepath = NodePath('cell_%s_root' % name)
+        self.nodepath.reparentTo(self.cellmanager.game.render)
+        self.portals = []
+
+    def add_portal(self, portal, cell_out):
+        """Add a portal from this cell going into another one."""
+        portal.reparentTo(self.nodepath)
+        portal.node().setCellIn(self.nodepath)
+        portal.node().setCellOut(cell_out.nodepath)
+        self.portals.append(portal)
+
+game = Game()
+game.run()

+ 305 - 0
samples/disco-lights/main.py

@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+
+# Author: Jason Pratt ([email protected])
+# Last Updated: 2015-03-13
+#
+# This project demonstrates how to use various types of
+# lighting
+#
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import PerspectiveLens
+from panda3d.core import NodePath
+from panda3d.core import AmbientLight, DirectionalLight
+from panda3d.core import PointLight, Spotlight
+from panda3d.core import TextNode
+from panda3d.core import Material
+from panda3d.core import LVector3
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+import math
+import sys
+import colorsys
+
+# Simple function to keep a value in a given range (by default 0 to 1)
+def clamp(i, mn=0, mx=1):
+    return min(max(i, mn), mx)
+
+
+class DiscoLightsDemo(ShowBase):
+
+    # Macro-like function to reduce the amount of code needed to create the
+    # onscreen instructions
+    def makeStatusLabel(self, i):
+        return OnscreenText(
+            parent=base.a2dTopLeft, align=TextNode.ALeft,
+            style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, .4),
+            pos=(0.06, -0.1 -(.06 * i)), scale=.05, mayChange=True)
+
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        # The main initialization of our class
+        # This creates the on screen title that is in every tutorial
+        self.title = OnscreenText(text="Panda3D: Tutorial - Lighting",
+                                  style=1, fg=(1, 1, 0, 1), shadow=(0, 0, 0, 0.5),
+                                  pos=(0.87, -0.95), scale = .07)
+
+        # Creates labels used for onscreen instructions
+        self.ambientText = self.makeStatusLabel(0)
+        self.directionalText = self.makeStatusLabel(1)
+        self.spotlightText = self.makeStatusLabel(2)
+        self.pointLightText = self.makeStatusLabel(3)
+        self.spinningText = self.makeStatusLabel(4)
+        self.ambientBrightnessText = self.makeStatusLabel(5)
+        self.directionalBrightnessText = self.makeStatusLabel(6)
+        self.spotlightBrightnessText = self.makeStatusLabel(7)
+        self.spotlightExponentText = self.makeStatusLabel(8)
+        self.lightingPerPixelText = self.makeStatusLabel(9)
+        self.lightingShadowsText = self.makeStatusLabel(10)
+
+        self.disco = loader.loadModel("models/disco_hall")
+        self.disco.reparentTo(render)
+        self.disco.setPosHpr(0, 50, -4, 90, 0, 0)
+
+        # First we create an ambient light. All objects are affected by ambient
+        # light equally
+        # Create and name the ambient light
+        self.ambientLight = render.attachNewNode(AmbientLight("ambientLight"))
+        # Set the color of the ambient light
+        self.ambientLight.node().setColor((.1, .1, .1, 1))
+        # add the newly created light to the lightAttrib
+
+        # Now we create a directional light. Directional lights add shading from a
+        # given angle. This is good for far away sources like the sun
+        self.directionalLight = render.attachNewNode(
+            DirectionalLight("directionalLight"))
+        self.directionalLight.node().setColor((.35, .35, .35, 1))
+        # The direction of a directional light is set as a 3D vector
+        self.directionalLight.node().setDirection(LVector3(1, 1, -2))
+        # These settings are necessary for shadows to work correctly
+        self.directionalLight.setZ(6)
+        dlens = self.directionalLight.node().getLens()
+        dlens.setFilmSize(41, 21)
+        dlens.setNearFar(50, 75)
+        # self.directionalLight.node().showFrustum()
+
+        # Now we create a spotlight. Spotlights light objects in a given cone
+        # They are good for simulating things like flashlights
+        self.spotlight = camera.attachNewNode(Spotlight("spotlight"))
+        self.spotlight.node().setColor((.45, .45, .45, 1))
+        self.spotlight.node().setSpecularColor((0, 0, 0, 1))
+        # The cone of a spotlight is controlled by it's lens. This creates the
+        # lens
+        self.spotlight.node().setLens(PerspectiveLens())
+        # This sets the Field of View (fov) of the lens, in degrees for width
+        # and height.  The lower the numbers, the tighter the spotlight.
+        self.spotlight.node().getLens().setFov(16, 16)
+        # Attenuation controls how the light fades with distance.  The three
+        # values represent the three attenuation constants (constant, linear,
+        # and quadratic) in the internal lighting equation. The higher the
+        # numbers the shorter the light goes.
+        self.spotlight.node().setAttenuation(LVector3(1, 0.0, 0.0))
+        # This exponent value sets how soft the edge of the spotlight is.
+        # 0 means a hard edge. 128 means a very soft edge.
+        self.spotlight.node().setExponent(60.0)
+
+        # Now we create three colored Point lights. Point lights are lights that
+        # radiate from a single point, like a light bulb. Like spotlights, they
+        # are given position by attaching them to NodePaths in the world
+        self.redHelper = loader.loadModel('models/sphere')
+        self.redHelper.setColor((1, 0, 0, 1))
+        self.redHelper.setPos(-6.5, -3.75, 0)
+        self.redHelper.setScale(.25)
+        self.redPointLight = self.redHelper.attachNewNode(
+            PointLight("redPointLight"))
+        self.redPointLight.node().setColor((.35, 0, 0, 1))
+        self.redPointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
+
+        # The green point light and helper
+        self.greenHelper = loader.loadModel('models/sphere')
+        self.greenHelper.setColor((0, 1, 0, 1))
+        self.greenHelper.setPos(0, 7.5, 0)
+        self.greenHelper.setScale(.25)
+        self.greenPointLight = self.greenHelper.attachNewNode(
+            PointLight("greenPointLight"))
+        self.greenPointLight.node().setAttenuation(LVector3(.1, .04, .0))
+        self.greenPointLight.node().setColor((0, .35, 0, 1))
+
+        # The blue point light and helper
+        self.blueHelper = loader.loadModel('models/sphere')
+        self.blueHelper.setColor((0, 0, 1, 1))
+        self.blueHelper.setPos(6.5, -3.75, 0)
+        self.blueHelper.setScale(.25)
+        self.bluePointLight = self.blueHelper.attachNewNode(
+            PointLight("bluePointLight"))
+        self.bluePointLight.node().setAttenuation(LVector3(.1, 0.04, 0.0))
+        self.bluePointLight.node().setColor((0, 0, .35, 1))
+        self.bluePointLight.node().setSpecularColor((1, 1, 1, 1))
+
+        # Create a dummy node so the lights can be spun with one command
+        self.pointLightHelper = render.attachNewNode("pointLightHelper")
+        self.pointLightHelper.setPos(0, 50, 11)
+        self.redHelper.reparentTo(self.pointLightHelper)
+        self.greenHelper.reparentTo(self.pointLightHelper)
+        self.blueHelper.reparentTo(self.pointLightHelper)
+
+        # Finally we store the lights on the root of the scene graph.
+        # This will cause them to affect everything in the scene.
+        render.setLight(self.ambientLight)
+        render.setLight(self.directionalLight)
+        render.setLight(self.spotlight)
+        render.setLight(self.redPointLight)
+        render.setLight(self.greenPointLight)
+        render.setLight(self.bluePointLight)
+
+        # Create and start interval to spin the lights, and a variable to
+        # manage them.
+        self.pointLightsSpin = self.pointLightHelper.hprInterval(
+            6, LVector3(360, 0, 0))
+        self.pointLightsSpin.loop()
+        self.arePointLightsSpinning = True
+
+        # Per-pixel lighting and shadows are initially off
+        self.perPixelEnabled = False
+        self.shadowsEnabled = False
+
+        # listen to keys for controlling the lights
+        self.accept("escape", sys.exit)
+        self.accept("a", self.toggleLights, [[self.ambientLight]])
+        self.accept("d", self.toggleLights, [[self.directionalLight]])
+        self.accept("s", self.toggleLights, [[self.spotlight]])
+        self.accept("p", self.toggleLights, [[self.redPointLight,
+                                              self.greenPointLight,
+                                              self.bluePointLight]])
+        self.accept("r", self.toggleSpinningPointLights)
+        self.accept("l", self.togglePerPixelLighting)
+        self.accept("e", self.toggleShadows)
+        self.accept("z", self.addBrightness, [self.ambientLight, -.05])
+        self.accept("x", self.addBrightness, [self.ambientLight, .05])
+        self.accept("c", self.addBrightness, [self.directionalLight, -.05])
+        self.accept("v", self.addBrightness, [self.directionalLight, .05])
+        self.accept("b", self.addBrightness, [self.spotlight, -.05])
+        self.accept("n", self.addBrightness, [self.spotlight, .05])
+        self.accept("q", self.adjustSpotlightExponent, [self.spotlight, -1])
+        self.accept("w", self.adjustSpotlightExponent, [self.spotlight, 1])
+
+        # Finally call the function that builds the instruction texts
+        self.updateStatusLabel()
+
+    # This function takes a list of lights and toggles their state. It takes in a
+    # list so that more than one light can be toggled in a single command
+    def toggleLights(self, lights):
+        for light in lights:
+            # If the given light is in our lightAttrib, remove it.
+            # This has the effect of turning off the light
+            if render.hasLight(light):
+                render.clearLight(light)
+            # Otherwise, add it back. This has the effect of turning the light
+            # on
+            else:
+                render.setLight(light)
+        self.updateStatusLabel()
+
+    # This function toggles the spinning of the point intervals by pausing and
+    # resuming the interval
+    def toggleSpinningPointLights(self):
+        if self.arePointLightsSpinning:
+            self.pointLightsSpin.pause()
+        else:
+            self.pointLightsSpin.resume()
+        self.arePointLightsSpinning = not self.arePointLightsSpinning
+        self.updateStatusLabel()
+
+    # This function turns per-pixel lighting on or off.
+    def togglePerPixelLighting(self):
+        if self.perPixelEnabled:
+            self.perPixelEnabled = False
+            render.clearShader()
+        else:
+            self.perPixelEnabled = True
+            render.setShaderAuto()
+        self.updateStatusLabel()
+
+    # This function turns shadows on or off.
+    def toggleShadows(self):
+        if self.shadowsEnabled:
+            self.shadowsEnabled = False
+            self.directionalLight.node().setShadowCaster(False)
+        else:
+            if not self.perPixelEnabled:
+                self.togglePerPixelLighting()
+            self.shadowsEnabled = True
+            self.directionalLight.node().setShadowCaster(True, 512, 512)
+        self.updateStatusLabel()
+
+    # This function changes the spotlight's exponent. It is kept to the range
+    # 0 to 128. Going outside of this range causes an error
+    def adjustSpotlightExponent(self, spotlight, amount):
+        e = clamp(spotlight.node().getExponent() + amount, 0, 128)
+        spotlight.node().setExponent(e)
+        self.updateStatusLabel()
+
+    # This function reads the color of the light, uses a built-in python function
+    #(from the library colorsys) to convert from RGB (red, green, blue) color
+    # representation to HSB (hue, saturation, brightness), so that we can get the
+    # brighteness of a light, change it, and then convert it back to rgb to chagne
+    # the light's color
+    def addBrightness(self, light, amount):
+        color = light.node().getColor()
+        h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
+        brightness = clamp(b + amount)
+        r, g, b = colorsys.hsv_to_rgb(h, s, brightness)
+        light.node().setColor((r, g, b, 1))
+
+        self.updateStatusLabel()
+
+    # Builds the onscreen instruction labels
+    def updateStatusLabel(self):
+        self.updateLabel(self.ambientText, "(a) ambient is",
+                         render.hasLight(self.ambientLight))
+        self.updateLabel(self.directionalText, "(d) directional is",
+                         render.hasLight(self.directionalLight))
+        self.updateLabel(self.spotlightText, "(s) spotlight is",
+                         render.hasLight(self.spotlight))
+        self.updateLabel(self.pointLightText, "(p) point lights are",
+                         render.hasLight(self.redPointLight))
+        self.updateLabel(self.spinningText, "(r) point light spinning is",
+                         self.arePointLightsSpinning)
+        self.ambientBrightnessText.setText(
+            "(z,x) Ambient Brightness: " +
+            self.getBrightnessString(self.ambientLight))
+        self.directionalBrightnessText.setText(
+            "(c,v) Directional Brightness: " +
+            self.getBrightnessString(self.directionalLight))
+        self.spotlightBrightnessText.setText(
+            "(b,n) Spotlight Brightness: " +
+            self.getBrightnessString(self.spotlight))
+        self.spotlightExponentText.setText(
+            "(q,w) Spotlight Exponent: " +
+            str(int(self.spotlight.node().getExponent())))
+        self.updateLabel(self.lightingPerPixelText, "(l) Per-pixel lighting is",
+                         self.perPixelEnabled)
+        self.updateLabel(self.lightingShadowsText, "(e) Shadows are",
+                         self.shadowsEnabled)
+
+    # Appends eitehr (on) or (off) to the base string based on the bassed value
+    def updateLabel(self, obj, base, var):
+        if var:
+            s = " (on)"
+        else:
+            s = " (off)"
+        obj.setText(base + s)
+
+    # Returns the brightness of a light as a string to put it in the instruction
+    # labels
+    def getBrightnessString(self, light):
+        color = light.node().getColor()
+        h, s, b = colorsys.rgb_to_hsv(color[0], color[1], color[2])
+        return "%.2f" % b
+
+
+# Make an instance of our class and run the demo
+demo = DiscoLightsDemo()
+demo.run()

BIN
samples/disco-lights/models/disco_hall.egg.pz


BIN
samples/disco-lights/models/sphere.egg.pz


+ 53 - 0
samples/distortion/distortion.sha

@@ -0,0 +1,53 @@
+//Cg
+//
+// time
+//
+//   You need to pass the frame time here.
+//
+// desat.x
+//
+//   Desaturation level.  If zero, the bloom's color is equal to
+//   the color of the input pixel.  If one, the bloom's color is
+//   white.
+//
+// trigger.x
+//
+//   Must be equal to mintrigger.
+//
+//   mintrigger is the minimum brightness to trigger a bloom,
+//   and maxtrigger is the brightness at which the bloom
+//   reaches maximum intensity.
+//
+// trigger.y
+//
+//   Must be equal to (1.0/(maxtrigger-mintrigger)) where
+//   
+//   mintrigger is the minimum brightness to trigger a bloom,
+//   and maxtrigger is the brightness at which the bloom
+//   reaches maximum intensity.
+//
+
+void vshader(float4 vtx_position : POSITION,
+             uniform float4x4 mat_modelproj,
+             uniform float4x4 trans_model_to_clip,
+             out float4 l_position : POSITION,
+             out float4 l_texcoord0 : TEXCOORD0)
+{
+    l_position = mul(mat_modelproj, vtx_position);
+    l_texcoord0 = mul(trans_model_to_clip, vtx_position);
+    l_texcoord0.z = l_texcoord0.w;
+}
+
+void fshader(float4 l_texcoord0 : TEXCOORD0,
+       	     uniform sampler2D k_screen : TEXUNIT1,
+             uniform sampler2D k_waves : TEXUNIT0,
+             uniform float4 texpad_screen,
+             in uniform float sys_time,
+             out float4 o_color : COLOR)
+{
+    float3 screen = l_texcoord0.xyz / l_texcoord0.w;
+    float2 texcoords = float2(screen.xy) * texpad_screen.xy + texpad_screen.xy;
+    float4 disturbance = tex2D(k_waves, texcoords);
+    //o_color = tex2D(k_screen, texcoords + disturbance.xy * 0.05 * disturbance.z * sys_time.x * 1);
+    o_color = tex2D(k_screen, texcoords + disturbance.xy * 0.05 * disturbance.z * sin(sys_time.x) * 1);
+}

+ 120 - 0
samples/distortion/main.py

@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+
+# Author: Tree Form [email protected]
+
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import FrameBufferProperties, TextNode, BitMask32, LPoint3
+from panda3d.core import WindowProperties, GraphicsOutput, Texture, GraphicsPipe
+from direct.showbase.DirectObject import DirectObject
+from direct.gui.OnscreenText import OnscreenText
+from sys import exit
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1),
+                        pos=(-1.25, pos), align=TextNode.ALeft, scale=.05)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
+                        pos=(1.25, -0.95), align=TextNode.ARight, scale=.07)
+
+
+class DistortionDemo(ShowBase):
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+
+        if not base.win.getGsg().getSupportsBasicShaders():
+            t = addTitle("Distortion Demo: Video driver says Cg shaders not supported.")
+            return
+
+        self.disableMouse()
+        self.setBackgroundColor(0, 0, 0)
+
+        # Show the instructions
+        self.title = addTitle("Panda3D: Tutorial - Distortion Effect")
+        self.inst1 = addInstructions(0.92, "ESC: Quit")
+        self.inst2 = addInstructions(0.86, "Space: Toggle distortion filter On/Off")
+        self.inst4 = addInstructions(0.80, "V: View the render-to-texture results")
+
+        # Load background
+        self.seascape = loader.loadModel("models/plane")
+        self.seascape.reparentTo(render)
+        self.seascape.setPosHpr(0, 145, 0, 0, 0, 0)
+        self.seascape.setScale(100)
+        self.seascape.setTexture(loader.loadTexture("models/ocean.jpg"))
+
+        # Create the distortion buffer. This buffer renders like a normal
+        # scene,
+        self.distortionBuffer = self.makeFBO("model buffer")
+        self.distortionBuffer.setSort(-3)
+        self.distortionBuffer.setClearColor((0, 0, 0, 0))
+
+        # We have to attach a camera to the distortion buffer. The distortion camera
+        # must have the same frustum as the main camera. As long as the aspect
+        # ratios match, the rest will take care of itself.
+        distortionCamera = self.makeCamera(self.distortionBuffer, scene=render,
+                                           lens=self.cam.node().getLens(), mask=BitMask32.bit(4))
+
+        # load the object with the distortion
+        self.distortionObject = loader.loadModel("models/boat")
+        self.distortionObject.setScale(1)
+        self.distortionObject.setPos(0, 20, -3)
+        self.distortionObject.hprInterval(10, LPoint3(360, 0, 0)).loop()
+        self.distortionObject.reparentTo(render)
+
+        # Create the shader that will determime what parts of the scene will
+        # distortion
+        distortionShader = loader.loadShader("distortion.sha")
+        self.distortionObject.setShader(distortionShader)
+        self.distortionObject.hide(BitMask32.bit(4))
+
+        # Textures
+        tex1 = loader.loadTexture("models/water.png")
+        self.distortionObject.setShaderInput("waves", tex1)
+
+        self.texDistortion = Texture()
+        self.distortionBuffer.addRenderTexture(
+            self.texDistortion, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
+        self.distortionObject.setShaderInput("screen", self.texDistortion)
+
+        # Panda contains a built-in viewer that lets you view the results of
+        # your render-to-texture operations.  This code configures the viewer.
+        self.accept("v", self.bufferViewer.toggleEnable)
+        self.accept("V", self.bufferViewer.toggleEnable)
+        self.bufferViewer.setPosition("llcorner")
+        self.bufferViewer.setLayout("hline")
+        self.bufferViewer.setCardSize(0.652, 0)
+
+        # event handling
+        self.accept("space", self.toggleDistortion)
+        self.accept("escape", exit, [0])
+        self.distortionOn = True
+
+    def makeFBO(self, name):
+        # This routine creates an offscreen buffer.  All the complicated
+        # parameters are basically demanding capabilities from the offscreen
+        # buffer - we demand that it be able to render to texture on every
+        # bitplane, that it can support aux bitplanes, that it track
+        # the size of the host window, that it can render to texture
+        # cumulatively, and so forth.
+        winprops = WindowProperties()
+        props = FrameBufferProperties()
+        props.setRgbColor(1)
+        return self.graphicsEngine.makeOutput(
+            self.pipe, "model buffer", -2, props, winprops,
+            GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFRefuseWindow,
+            self.win.getGsg(), self.win)
+
+    def toggleDistortion(self):
+        # Toggles the distortion on/off.
+        if self.distortionOn:
+            self.distortionObject.hide()
+        else:
+            self.distortionObject.show()
+        self.distortionOn = not(self.distortionOn)
+
+demo = DistortionDemo()
+demo.run()

BIN
samples/distortion/models/boat.egg.pz


BIN
samples/distortion/models/ocean.jpg


+ 2 - 0
samples/distortion/models/plane.egg.pz

@@ -0,0 +1,2 @@
+xÚ�’Ákƒ0Æïƒþ�Þc»[¶zÒÒÂN#£©˜¼³Q)þï}±Ñ‰l‡…h4ß÷|¿|D¾A´§O-œÜ·�“ª€+¼&Ýbñ@ß RR;Òi°T¢¹¬*0µÐ2Uïá…¤%UøÏøÖâ—) ¢‡>•Þ~sVèæŒVõ}¦U¦/`¡??Jëä¥D¬kÿ!Œdé7M¨ë
+`£�déã}ÊF�¿TÔ>WF#a}²àŽƒ>wÛç'/³0«›£ó9:‚̢ȫ	9.3‹Ì¼ž‘#Àÿˆ|Ÿy‰u[¡.~NÿONØèNž½¹¢ýæÔ•‡õ/?T7AùûxÒ³I

BIN
samples/distortion/models/water.png


+ 45 - 0
samples/fireflies/light.sha

@@ -0,0 +1,45 @@
+//Cg
+//
+//Cg profile arbvp1 arbfp1
+
+void vshader(float4 vtx_position : POSITION,
+             out float4 l_position : POSITION,
+             out float4 l_pos : TEXCOORD0,
+             uniform float4x4 mat_modelproj,
+             uniform float4x4 trans_model_to_clip)
+{
+  l_position=mul(mat_modelproj, vtx_position);
+  l_pos=mul(trans_model_to_clip, vtx_position);
+  l_pos.z = l_pos.w;
+}
+
+void fshader(float4 l_pos: TEXCOORD0,
+             float4 l_scale: TEXCOORD1,
+             uniform sampler2D k_texnormal : TEXUNIT0,
+             uniform sampler2D k_texalbedo : TEXUNIT1,
+             uniform sampler2D k_texdepth  : TEXUNIT2,
+             uniform float4 texpad_texnormal,
+             uniform float4 k_proj,
+             uniform float4 vspos_model,
+             uniform float4 k_lightcolor,
+             uniform float4 row0_model_to_view,
+             out float4 o_color: COLOR)
+{
+  float3 screen = l_pos.xyz / l_pos.w;
+  float2 texcoords = float2(screen.xy) * texpad_texnormal.xy + texpad_texnormal.xy;
+
+  float4 albedo = tex2D(k_texalbedo, texcoords);
+  float4 normal = tex2D(k_texnormal, texcoords);
+  float  depth = tex2D(k_texdepth, texcoords);
+
+  float3 view = (screen.xzy * k_proj.xyz) / (depth + k_proj.w);
+
+  float3 lightvec = float3(vspos_model) - view;
+  float  lightdist = length(lightvec);
+  float3 lightdir = lightvec / lightdist;
+  float  scaledist = (lightdist / row0_model_to_view.x);
+  float  falloff = saturate(1.0 - scaledist);
+  float  brite = falloff * falloff * dot(lightdir, float3(normal));
+  o_color = albedo * k_lightcolor * brite;
+  o_color.a = 1;
+}

+ 416 - 0
samples/fireflies/main.py

@@ -0,0 +1,416 @@
+#!/usr/bin/env python
+
+# Author: Josh Yelon
+# Date: 7/11/2005
+#
+# See the associated manual page for an explanation.
+#
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import FrameBufferProperties, WindowProperties
+from panda3d.core import GraphicsPipe, GraphicsOutput
+from panda3d.core import Filename, Texture, Shader
+from panda3d.core import RenderState, CardMaker
+from panda3d.core import PandaNode, TextNode, NodePath
+from panda3d.core import RenderAttrib, AlphaTestAttrib, ColorBlendAttrib
+from panda3d.core import CullFaceAttrib, DepthTestAttrib, DepthWriteAttrib
+from panda3d.core import LPoint3, LVector3, BitMask32
+from direct.gui.OnscreenText import OnscreenText
+from direct.showbase.DirectObject import DirectObject
+from direct.interval.MetaInterval import Sequence
+from direct.task.Task import Task
+from direct.actor.Actor import Actor
+import sys
+import os
+import random
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.05)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+
+class FireflyDemo(ShowBase):
+    def __init__(self):
+        # Initialize the ShowBase class from which we inherit, which will
+        # create a window and set up everything we need for rendering into it.
+        ShowBase.__init__(self)
+        self.setBackgroundColor((0, 0, 0, 0))
+
+        # Preliminary capabilities check.
+
+        if not self.win.getGsg().getSupportsBasicShaders():
+            self.t = addTitle("Firefly Demo: Video driver reports that Cg "
+                              "shaders are not supported.")
+            return
+        if not self.win.getGsg().getSupportsDepthTexture():
+            self.t = addTitle("Firefly Demo: Video driver reports that depth "
+                              "textures are not supported.")
+            return
+
+        # This algorithm uses two offscreen buffers, one of which has
+        # an auxiliary bitplane, and the offscreen buffers share a single
+        # depth buffer.  This is a heck of a complicated buffer setup.
+
+        self.modelbuffer = self.makeFBO("model buffer", 1)
+        self.lightbuffer = self.makeFBO("light buffer", 0)
+
+        # Creation of a high-powered buffer can fail, if the graphics card
+        # doesn't support the necessary OpenGL extensions.
+
+        if self.modelbuffer is None or self.lightbuffer is None:
+            self.t = addTitle("Toon Shader: Video driver does not support "
+                              "multiple render targets")
+            return
+
+        # Create four render textures: depth, normal, albedo, and final.
+        # attach them to the various bitplanes of the offscreen buffers.
+
+        self.texDepth = Texture()
+        self.texDepth.setFormat(Texture.FDepthStencil)
+        self.texAlbedo = Texture()
+        self.texNormal = Texture()
+        self.texFinal = Texture()
+
+        self.modelbuffer.addRenderTexture(self.texDepth,
+            GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepthStencil)
+        self.modelbuffer.addRenderTexture(self.texAlbedo,
+            GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
+        self.modelbuffer.addRenderTexture(self.texNormal,
+            GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0)
+
+        self.lightbuffer.addRenderTexture(self.texFinal,
+            GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
+
+        # Set the near and far clipping planes.
+
+        self.cam.node().getLens().setNear(50.0)
+        self.cam.node().getLens().setFar(500.0)
+        lens = self.cam.node().getLens()
+
+        # This algorithm uses three cameras: one to render the models into the
+        # model buffer, one to render the lights into the light buffer, and
+        # one to render "plain" stuff (non-deferred shaded) stuff into the
+        # light buffer.  Each camera has a bitmask to identify it.
+
+        self.modelMask = 1
+        self.lightMask = 2
+        self.plainMask = 4
+
+        self.modelcam = self.makeCamera(self.modelbuffer,
+            lens=lens, scene=render, mask=self.modelMask)
+        self.lightcam = self.makeCamera(self.lightbuffer,
+            lens=lens, scene=render, mask=self.lightMask)
+        self.plaincam = self.makeCamera(self.lightbuffer,
+            lens=lens, scene=render, mask=self.plainMask)
+
+        # Panda's main camera is not used.
+
+        self.cam.node().setActive(0)
+
+        # Take explicit control over the order in which the three
+        # buffers are rendered.
+
+        self.modelbuffer.setSort(1)
+        self.lightbuffer.setSort(2)
+        self.win.setSort(3)
+
+        # Within the light buffer, control the order of the two cams.
+
+        self.lightcam.node().getDisplayRegion(0).setSort(1)
+        self.plaincam.node().getDisplayRegion(0).setSort(2)
+
+        # By default, panda usually clears the screen before every
+        # camera and before every window.  Tell it not to do that.
+        # Then, tell it specifically when to clear and what to clear.
+
+        self.modelcam.node().getDisplayRegion(0).disableClears()
+        self.lightcam.node().getDisplayRegion(0).disableClears()
+        self.plaincam.node().getDisplayRegion(0).disableClears()
+        self.cam.node().getDisplayRegion(0).disableClears()
+        self.cam2d.node().getDisplayRegion(0).disableClears()
+        self.modelbuffer.disableClears()
+        self.win.disableClears()
+
+        self.modelbuffer.setClearColorActive(1)
+        self.modelbuffer.setClearDepthActive(1)
+        self.lightbuffer.setClearColorActive(1)
+        self.lightbuffer.setClearColor((0, 0, 0, 1))
+
+        # Miscellaneous stuff.
+
+        self.disableMouse()
+        self.camera.setPos(-9.112, -211.077, 46.951)
+        self.camera.setHpr(0, -7.5, 2.4)
+        random.seed()
+
+        # Calculate the projection parameters for the final shader.
+        # The math here is too complex to explain in an inline comment,
+        # I've put in a full explanation into the HTML intro.
+
+        proj = self.cam.node().getLens().getProjectionMat()
+        proj_x = 0.5 * proj.getCell(3, 2) / proj.getCell(0, 0)
+        proj_y = 0.5 * proj.getCell(3, 2)
+        proj_z = 0.5 * proj.getCell(3, 2) / proj.getCell(2, 1)
+        proj_w = -0.5 - 0.5 * proj.getCell(1, 2)
+
+        # Configure the render state of the model camera.
+
+        tempnode = NodePath(PandaNode("temp node"))
+        tempnode.setAttrib(
+            AlphaTestAttrib.make(RenderAttrib.MGreaterEqual, 0.5))
+        tempnode.setShader(loader.loadShader("model.sha"))
+        tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MLessEqual))
+        self.modelcam.node().setInitialState(tempnode.getState())
+
+        # Configure the render state of the light camera.
+
+        tempnode = NodePath(PandaNode("temp node"))
+        tempnode.setShader(loader.loadShader("light.sha"))
+        tempnode.setShaderInput("texnormal", self.texNormal)
+        tempnode.setShaderInput("texalbedo", self.texAlbedo)
+        tempnode.setShaderInput("texdepth", self.texDepth)
+        tempnode.setShaderInput("proj", (proj_x, proj_y, proj_z, proj_w))
+        tempnode.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd,
+            ColorBlendAttrib.OOne, ColorBlendAttrib.OOne))
+        tempnode.setAttrib(
+            CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))
+        # The next line causes problems on Linux.
+        # tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreaterEqual))
+        tempnode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff))
+        self.lightcam.node().setInitialState(tempnode.getState())
+
+        # Configure the render state of the plain camera.
+
+        rs = RenderState.makeEmpty()
+        self.plaincam.node().setInitialState(rs)
+
+        # Clear any render attribs on the root node. This is necessary
+        # because by default, panda assigns some attribs to the root
+        # node.  These default attribs will override the
+        # carefully-configured render attribs that we just attached
+        # to the cameras.  The simplest solution is to just clear
+        # them all out.
+
+        render.setState(RenderState.makeEmpty())
+
+        # My artist created a model in which some of the polygons
+        # don't have textures.  This confuses the shader I wrote.
+        # This little hack guarantees that everything has a texture.
+
+        white = loader.loadTexture("models/white.jpg")
+        render.setTexture(white, 0)
+
+        # Create two subroots, to help speed cull traversal.
+
+        self.lightroot = NodePath(PandaNode("lightroot"))
+        self.lightroot.reparentTo(render)
+        self.modelroot = NodePath(PandaNode("modelroot"))
+        self.modelroot.reparentTo(render)
+        self.lightroot.hide(BitMask32(self.modelMask))
+        self.modelroot.hide(BitMask32(self.lightMask))
+        self.modelroot.hide(BitMask32(self.plainMask))
+
+        # Load the model of a forest.  Make it visible to the model camera.
+        # This is a big model, so we load it asynchronously while showing a
+        # load text.  We do this by passing in a callback function.
+        self.loading = addTitle("Loading models...")
+
+        self.forest = NodePath(PandaNode("Forest Root"))
+        self.forest.reparentTo(render)
+        self.forest.hide(BitMask32(self.lightMask | self.plainMask))
+        loader.loadModel([
+            "models/background",
+            "models/foliage01",
+            "models/foliage02",
+            "models/foliage03",
+            "models/foliage04",
+            "models/foliage05",
+            "models/foliage06",
+            "models/foliage07",
+            "models/foliage08",
+            "models/foliage09"],
+            callback=self.finishLoading)
+
+        # Cause the final results to be rendered into the main window on a
+        # card.
+
+        self.card = self.lightbuffer.getTextureCard()
+        self.card.setTexture(self.texFinal)
+        self.card.reparentTo(render2d)
+
+        # Panda contains a built-in viewer that lets you view the results of
+        # your render-to-texture operations.  This code configures the viewer.
+
+        self.bufferViewer.setPosition("llcorner")
+        self.bufferViewer.setCardSize(0, 0.40)
+        self.bufferViewer.setLayout("vline")
+        self.toggleCards()
+        self.toggleCards()
+
+        # Firefly parameters
+
+        self.fireflies = []
+        self.sequences = []
+        self.scaleseqs = []
+        self.glowspheres = []
+        self.fireflysize = 1.0
+        self.spheremodel = loader.loadModel("misc/sphere")
+
+        # Create the firefly model, a fuzzy dot
+        dotSize = 1.0
+        cm = CardMaker("firefly")
+        cm.setFrame(-dotSize, dotSize, -dotSize, dotSize)
+        self.firefly = NodePath(cm.generate())
+        self.firefly.setTexture(loader.loadTexture("models/firefly.png"))
+        self.firefly.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add,
+            ColorBlendAttrib.O_incoming_alpha, ColorBlendAttrib.O_one))
+
+        # these allow you to change parameters in realtime
+
+        self.accept("escape", sys.exit, [0])
+        self.accept("arrow_up",   self.incFireflyCount, [1.1111111])
+        self.accept("arrow_down", self.decFireflyCount, [0.9000000])
+        self.accept("arrow_right", self.setFireflySize, [1.1111111])
+        self.accept("arrow_left",  self.setFireflySize, [0.9000000])
+        self.accept("v", self.toggleCards)
+        self.accept("V", self.toggleCards)
+
+    def finishLoading(self, models):
+        # This function is used as callback to loader.loadModel, and called
+        # when all of the models have finished loading.
+
+        # Attach the models to the scene graph.
+        for model in models:
+            model.reparentTo(self.forest)
+
+        # Show the instructions.
+        self.loading.destroy()
+        self.title = addTitle("Panda3D: Tutorial - Fireflies using Deferred Shading")
+        self.inst1 = addInstructions(0.06, "ESC: Quit")
+        self.inst2 = addInstructions(0.12, "Up/Down: More / Fewer Fireflies (Count: unknown)")
+        self.inst3 = addInstructions(0.18, "Right/Left: Bigger / Smaller Fireflies (Radius: unknown)")
+        self.inst4 = addInstructions(0.24, "V: View the render-to-texture results")
+
+        self.setFireflySize(25.0)
+        while len(self.fireflies) < 5:
+            self.addFirefly()
+        self.updateReadout()
+
+        self.nextadd = 0
+        taskMgr.add(self.spawnTask, "spawner")
+
+    def makeFBO(self, name, auxrgba):
+        # This routine creates an offscreen buffer.  All the complicated
+        # parameters are basically demanding capabilities from the offscreen
+        # buffer - we demand that it be able to render to texture on every
+        # bitplane, that it can support aux bitplanes, that it track
+        # the size of the host window, that it can render to texture
+        # cumulatively, and so forth.
+        winprops = WindowProperties()
+        props = FrameBufferProperties()
+        props.setRgbColor(True)
+        props.setRgbaBits(8, 8, 8, 8)
+        props.setDepthBits(1)
+        props.setAuxRgba(auxrgba)
+        return self.graphicsEngine.makeOutput(
+            self.pipe, "model buffer", -2,
+            props, winprops,
+            GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFCanBindEvery |
+            GraphicsPipe.BFRttCumulative | GraphicsPipe.BFRefuseWindow,
+            self.win.getGsg(), self.win)
+
+    def addFirefly(self):
+        pos1 = LPoint3(random.uniform(-50, 50), random.uniform(-100, 150), random.uniform(-10, 80))
+        dir = LVector3(random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1))
+        dir.normalize()
+        pos2 = pos1 + (dir * 20)
+        fly = self.lightroot.attachNewNode(PandaNode("fly"))
+        glow = fly.attachNewNode(PandaNode("glow"))
+        dot = fly.attachNewNode(PandaNode("dot"))
+        color_r = 1.0
+        color_g = random.uniform(0.8, 1.0)
+        color_b = min(color_g, random.uniform(0.5, 1.0))
+        fly.setColor(color_r, color_g, color_b, 1.0)
+        fly.setShaderInput("lightcolor", color_r, color_g, color_b, 1.0)
+        int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2)
+        int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1)
+        si1 = fly.scaleInterval(random.uniform(0.8, 1.5),
+            LPoint3(0.2, 0.2, 0.2), LPoint3(0.2, 0.2, 0.2))
+        si2 = fly.scaleInterval(random.uniform(1.5, 0.8),
+            LPoint3(1.0, 1.0, 1.0), LPoint3(0.2, 0.2, 0.2))
+        si3 = fly.scaleInterval(random.uniform(1.0, 2.0),
+            LPoint3(0.2, 0.2, 0.2), LPoint3(1.0, 1.0, 1.0))
+        siseq = Sequence(si1, si2, si3)
+        siseq.loop()
+        siseq.setT(random.uniform(0, 1000))
+        seq = Sequence(int1, int2)
+        seq.loop()
+        self.spheremodel.instanceTo(glow)
+        self.firefly.instanceTo(dot)
+        glow.setScale(self.fireflysize * 1.1)
+        glow.hide(BitMask32(self.modelMask | self.plainMask))
+        dot.hide(BitMask32(self.modelMask | self.lightMask))
+        dot.setColor(color_r, color_g, color_b, 1.0)
+        self.fireflies.append(fly)
+        self.sequences.append(seq)
+        self.glowspheres.append(glow)
+        self.scaleseqs.append(siseq)
+
+    def updateReadout(self):
+        self.inst2.destroy()
+        self.inst2 = addInstructions(0.12,
+            "Up/Down: More / Fewer Fireflies (Currently: %d)" % len(self.fireflies))
+        self.inst3.destroy()
+        self.inst3 = addInstructions(0.18,
+            "Right/Left: Bigger / Smaller Fireflies (Radius: %d ft)" % self.fireflysize)
+
+    def toggleCards(self):
+        self.bufferViewer.toggleEnable()
+        # When the cards are not visible, I also disable the color clear.
+        # This color-clear is actually not necessary, the depth-clear is
+        # sufficient for the purposes of the algorithm.
+        if (self.bufferViewer.isEnabled()):
+            self.modelbuffer.setClearColorActive(True)
+        else:
+            self.modelbuffer.setClearColorActive(False)
+
+    def incFireflyCount(self, scale):
+        n = int((len(self.fireflies) * scale) + 1)
+        while (n > len(self.fireflies)):
+            self.addFirefly()
+        self.updateReadout()
+
+    def decFireflyCount(self, scale):
+        n = int(len(self.fireflies) * scale)
+        if (n < 1):
+            n = 1
+        while (len(self.fireflies) > n):
+            self.glowspheres.pop()
+            self.sequences.pop().finish()
+            self.scaleseqs.pop().finish()
+            self.fireflies.pop().removeNode()
+        self.updateReadout()
+
+    def setFireflySize(self, n):
+        n = n * self.fireflysize
+        self.fireflysize = n
+        for x in self.glowspheres:
+            x.setScale(self.fireflysize * 1.1)
+        self.updateReadout()
+
+    def spawnTask(self, task):
+        if task.time > self.nextadd:
+            self.nextadd = task.time + 1.0
+            if (len(self.fireflies) < 300):
+                self.incFireflyCount(1.03)
+        return Task.cont
+
+demo = FireflyDemo()
+demo.run()

+ 35 - 0
samples/fireflies/model.sha

@@ -0,0 +1,35 @@
+//Cg
+//
+//Cg profile arbvp1 arbfp1
+
+void vshader(float4 vtx_position : POSITION,
+             float2 vtx_texcoord0 : TEXCOORD0,
+             float4 vtx_normal : NORMAL,
+             float4 vtx_color : COLOR,
+             out float4 l_position : POSITION,
+             out float2 l_texcoord0 : TEXCOORD0,
+             out float4 l_color : COLOR,   
+             out float3 l_normal : TEXCOORD1,
+             uniform float4x4 mat_modelproj,
+             uniform float4x4 itp_modelview)
+{
+  l_position=mul(mat_modelproj, vtx_position);
+  l_texcoord0 = vtx_texcoord0;
+  l_color = vtx_color;
+  l_normal = (float3)mul(itp_modelview, vtx_normal);
+}
+
+void fshader(float2 l_texcoord0: TEXCOORD0,
+             float4 l_color: COLOR,
+             float3 l_normal: TEXCOORD1,
+             uniform sampler2D tex_0 : TEXUNIT0,
+             out float4 o_color: COLOR0,
+             out float4 o_normal: COLOR1)
+{
+  l_normal = normalize(l_normal);
+  o_color = l_color * tex2D(tex_0, l_texcoord0);
+  o_normal.rgb = (l_normal * 0.5) + float3(0.5, 0.5, 0.5);
+  o_normal.a = o_color.a;
+}
+
+

BIN
samples/fireflies/models/Bark.jpg


BIN
samples/fireflies/models/Bark03.jpg


BIN
samples/fireflies/models/background.egg.pz


BIN
samples/fireflies/models/bigleaf3.png


BIN
samples/fireflies/models/elmleaf.png


BIN
samples/fireflies/models/firefly.png


BIN
samples/fireflies/models/foliage01.egg.pz


BIN
samples/fireflies/models/foliage02.egg.pz


BIN
samples/fireflies/models/foliage03.egg.pz


BIN
samples/fireflies/models/foliage04.egg.pz


BIN
samples/fireflies/models/foliage05.egg.pz


BIN
samples/fireflies/models/foliage06.egg.pz


BIN
samples/fireflies/models/foliage07.egg.pz


BIN
samples/fireflies/models/foliage08.egg.pz


BIN
samples/fireflies/models/foliage09.egg.pz


BIN
samples/fireflies/models/indt04S.jpg


BIN
samples/fireflies/models/noise.jpg


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels