Add sample programs to the repository

This commit is contained in:
rdb 2015-03-14 15:01:31 +01:00
parent 68cf8cf44f
commit 78c3593d2c
193 changed files with 13879 additions and 1 deletions

1
.gitignore vendored
View File

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

399
samples/asteroids/main.py Executable file
View File

@ -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()

View File

@ -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 } }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

338
samples/ball-in-maze/main.py Executable file
View File

@ -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()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

199
samples/boxing-robots/main.py Executable file
View File

@ -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()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

188
samples/bump-mapping/main.py Executable file
View File

@ -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()

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@ -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 } }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

196
samples/carousel/main.py Executable file
View File

@ -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()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -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
samples/cartoon-shader/basic.py Executable file
View File

@ -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()

View File

@ -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;
}

View File

@ -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;
}

Binary file not shown.

View File

@ -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
samples/chessboard/main.py Executable file
View File

@ -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()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 } }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 } }
}
}

View File

@ -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 } }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

@ -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
samples/culling/portal_culling.py Executable file
View File

@ -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
samples/disco-lights/main.py Executable file
View File

@ -0,0 +1,305 @@
#!/usr/bin/env python
# Author: Jason Pratt (pratt@andrew.cmu.edu)
# 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()

Binary file not shown.

Binary file not shown.

View File

@ -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
samples/distortion/main.py Executable file
View File

@ -0,0 +1,120 @@
#!/usr/bin/env python
# Author: Tree Form starplant@gmail.com
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()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -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
samples/fireflies/main.py Executable file
View File

@ -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()

View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Some files were not shown because too many files have changed in this diff Show More