diff --git a/.gitignore b/.gitignore index b7923b22be..416d087716 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /built_x64 /built -/samples /thirdparty diff --git a/samples/asteroids/main.py b/samples/asteroids/main.py new file mode 100755 index 0000000000..297ccf91e6 --- /dev/null +++ b/samples/asteroids/main.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman, and Greg Lindley +# Last Updated: 2015-03-13 +# +# This tutorial demonstrates the use of tasks. A task is a function that +# gets called once every frame. They are good for things that need to be +# updated very often. In the case of asteroids, we use tasks to update +# the positions of all the objects, and to check if the bullets or the +# ship have hit the asteroids. +# +# Note: This definitely a complicated example. Tasks are the cores of +# most games so it seemed appropriate to show what a full game in Panda +# could look like. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, TransparencyAttrib +from panda3d.core import LPoint3, LVector3 +from direct.gui.OnscreenText import OnscreenText +from direct.task.Task import Task +from math import sin, cos, pi +from random import randint, choice, random +from direct.interval.MetaInterval import Sequence +from direct.interval.FunctionInterval import Wait, Func +import sys + +# Constants that will control the behavior of the game. It is good to +# group constants like this so that they can be changed once without +# having to find everywhere they are used in code +SPRITE_POS = 55 # At default field of view and a depth of 55, the screen +# dimensions is 40x30 units +SCREEN_X = 20 # Screen goes from -20 to 20 on X +SCREEN_Y = 15 # Screen goes from -15 to 15 on Y +TURN_RATE = 360 # Degrees ship can turn in 1 second +ACCELERATION = 10 # Ship acceleration in units/sec/sec +MAX_VEL = 6 # Maximum ship velocity in units/sec +MAX_VEL_SQ = MAX_VEL ** 2 # Square of the ship velocity +DEG_TO_RAD = pi / 180 # translates degrees to radians for sin and cos +BULLET_LIFE = 2 # How long bullets stay on screen before removed +BULLET_REPEAT = .2 # How often bullets can be fired +BULLET_SPEED = 10 # Speed bullets move +AST_INIT_VEL = 1 # Velocity of the largest asteroids +AST_INIT_SCALE = 3 # Initial asteroid scale +AST_VEL_SCALE = 2.2 # How much asteroid speed multiplies when broken up +AST_SIZE_SCALE = .6 # How much asteroid scale changes when broken up +AST_MIN_SCALE = 1.1 # If and asteroid is smaller than this and is hit, +# it disapears instead of splitting up + + +# This helps reduce the amount of code used by loading objects, since all of +# the objects are pretty much the same. +def loadObject(tex=None, pos=LPoint3(0, 0), depth=SPRITE_POS, scale=1, + transparency=True): + # Every object uses the plane model and is parented to the camera + # so that it faces the screen. + obj = loader.loadModel("models/plane") + obj.reparentTo(camera) + + # Set the initial position and scale. + obj.setPos(pos.getX(), depth, pos.getY()) + obj.setScale(scale) + + # This tells Panda not to worry about the order that things are drawn in + # (ie. disable Z-testing). This prevents an effect known as Z-fighting. + obj.setBin("unsorted", 0) + obj.setDepthTest(False) + + if transparency: + # Enable transparency blending. + obj.setTransparency(TransparencyAttrib.MAlpha) + + if tex: + # Load and set the requested texture. + tex = loader.loadTexture("textures/" + tex) + obj.setTexture(tex, 1) + + return obj + + +# Macro-like function used to reduce the amount to code needed to create the +# on screen instructions +def genLabelText(text, i): + return OnscreenText(text=text, parent=base.a2dTopLeft, pos=(0.07, -.06 * i - 0.1), + fg=(1, 1, 1, 1), align=TextNode.ALeft, shadow=(0, 0, 0, 0.5), scale=.05) + + +class AsteroidsDemo(ShowBase): + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # This code puts the standard title and instruction text on screen + self.title = OnscreenText(text="Panda3D: Tutorial - Tasks", + parent=base.a2dBottomRight, scale=.07, + align=TextNode.ARight, pos=(-0.1, 0.1), + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 0.5)) + self.escapeText = genLabelText("ESC: Quit", 0) + self.leftkeyText = genLabelText("[Left Arrow]: Turn Left (CCW)", 1) + self.rightkeyText = genLabelText("[Right Arrow]: Turn Right (CW)", 2) + self.upkeyText = genLabelText("[Up Arrow]: Accelerate", 3) + self.spacekeyText = genLabelText("[Space Bar]: Fire", 4) + + # Disable default mouse-based camera control. This is a method on the + # ShowBase class from which we inherit. + self.disableMouse() + + # Load the background starfield. + self.setBackgroundColor((0, 0, 0, 1)) + self.bg = loadObject("stars.jpg", scale=146, depth=200, + transparency=False) + + # Load the ship and set its initial velocity. + self.ship = loadObject("ship.png") + self.setVelocity(self.ship, LVector3.zero()) + + # A dictionary of what keys are currently being pressed + # The key events update this list, and our task will query it as input + self.keys = {"turnLeft": 0, "turnRight": 0, + "accel": 0, "fire": 0} + + self.accept("escape", sys.exit) # Escape quits + # Other keys events set the appropriate value in our key dictionary + self.accept("arrow_left", self.setKey, ["turnLeft", 1]) + self.accept("arrow_left-up", self.setKey, ["turnLeft", 0]) + self.accept("arrow_right", self.setKey, ["turnRight", 1]) + self.accept("arrow_right-up", self.setKey, ["turnRight", 0]) + self.accept("arrow_up", self.setKey, ["accel", 1]) + self.accept("arrow_up-up", self.setKey, ["accel", 0]) + self.accept("space", self.setKey, ["fire", 1]) + + # Now we create the task. taskMgr is the task manager that actually + # calls the function each frame. The add method creates a new task. + # The first argument is the function to be called, and the second + # argument is the name for the task. It returns a task object which + # is passed to the function each frame. + self.gameTask = taskMgr.add(self.gameLoop, "gameLoop") + + # Stores the time at which the next bullet may be fired. + self.nextBullet = 0.0 + + # This list will stored fired bullets. + self.bullets = [] + + # Complete initialization by spawning the asteroids. + self.spawnAsteroids() + + # As described earlier, this simply sets a key in the self.keys dictionary + # to the given value. + def setKey(self, key, val): + self.keys[key] = val + + def setVelocity(self, obj, val): + obj.setPythonTag("velocity", val) + + def getVelocity(self, obj): + return obj.getPythonTag("velocity") + + def setExpires(self, obj, val): + obj.setPythonTag("expires", val) + + def getExpires(self, obj): + return obj.getPythonTag("expires") + + def spawnAsteroids(self): + # Control variable for if the ship is alive + self.alive = True + self.asteroids = [] # List that will contain our asteroids + + for i in range(10): + # This loads an asteroid. The texture chosen is random + # from "asteroid1.png" to "asteroid3.png". + asteroid = loadObject("asteroid%d.png" % (randint(1, 3)), + scale=AST_INIT_SCALE) + self.asteroids.append(asteroid) + + # This is kind of a hack, but it keeps the asteroids from spawning + # near the player. It creates the list (-20, -19 ... -5, 5, 6, 7, + # ... 20) and chooses a value from it. Since the player starts at 0 + # and this list doesn't contain anything from -4 to 4, it won't be + # close to the player. + asteroid.setX(choice(range(-SCREEN_X, -5) + range(5, SCREEN_X))) + # Same thing for Y, but from -15 to 15 + asteroid.setZ(choice(range(-SCREEN_Y, -5) + range(5, SCREEN_Y))) + + # Heading is a random angle in radians + heading = random() * 2 * pi + + # Converts the heading to a vector and multiplies it by speed to + # get a velocity vector + v = LVector3(sin(heading), 0, cos(heading)) * AST_INIT_VEL + self.setVelocity(self.asteroids[i], v) + + # This is our main task function, which does all of the per-frame + # processing. It takes in self like all functions in a class, and task, + # the task object returned by taskMgr. + def gameLoop(self, task): + # Get the time elapsed since the next frame. We need this for our + # distance and velocity calculations. + dt = globalClock.getDt() + + # If the ship is not alive, do nothing. Tasks return Task.cont to + # signify that the task should continue running. If Task.done were + # returned instead, the task would be removed and would no longer be + # called every frame. + if not self.alive: + return Task.cont + + # update ship position + self.updateShip(dt) + + # check to see if the ship can fire + if self.keys["fire"] and task.time > self.nextBullet: + self.fire(task.time) # If so, call the fire function + # And disable firing for a bit + self.nextBullet = task.time + BULLET_REPEAT + # Remove the fire flag until the next spacebar press + self.keys["fire"] = 0 + + # update asteroids + for obj in self.asteroids: + self.updatePos(obj, dt) + + # update bullets + newBulletArray = [] + for obj in self.bullets: + self.updatePos(obj, dt) # Update the bullet + # Bullets have an experation time (see definition of fire) + # If a bullet has not expired, add it to the new bullet list so + # that it will continue to exist. + if self.getExpires(obj) > task.time: + newBulletArray.append(obj) + else: + obj.removeNode() # Otherwise, remove it from the scene. + # Set the bullet array to be the newly updated array + self.bullets = newBulletArray + + # Check bullet collision with asteroids + # In short, it checks every bullet against every asteroid. This is + # quite slow. A big optimization would be to sort the objects left to + # right and check only if they overlap. Framerate can go way down if + # there are many bullets on screen, but for the most part it's okay. + for bullet in self.bullets: + # This range statement makes it step though the asteroid list + # backwards. This is because if an asteroid is removed, the + # elements after it will change position in the list. If you go + # backwards, the length stays constant. + for i in range(len(self.asteroids) - 1, -1, -1): + asteroid = self.asteroids[i] + # Panda's collision detection is more complicated than we need + # here. This is the basic sphere collision check. If the + # distance between the object centers is less than sum of the + # radii of the two objects, then we have a collision. We use + # lengthSquared() since it is faster than length(). + if ((bullet.getPos() - asteroid.getPos()).lengthSquared() < + (((bullet.getScale().getX() + asteroid.getScale().getX()) + * .5) ** 2)): + # Schedule the bullet for removal + self.setExpires(bullet, 0) + self.asteroidHit(i) # Handle the hit + + # Now we do the same collision pass for the ship + shipSize = self.ship.getScale().getX() + for ast in self.asteroids: + # Same sphere collision check for the ship vs. the asteroid + if ((self.ship.getPos() - ast.getPos()).lengthSquared() < + (((shipSize + ast.getScale().getX()) * .5) ** 2)): + # If there is a hit, clear the screen and schedule a restart + self.alive = False # Ship is no longer alive + # Remove every object in asteroids and bullets from the scene + for i in self.asteroids + self.bullets: + i.removeNode() + self.bullets = [] # Clear the bullet list + self.ship.hide() # Hide the ship + # Reset the velocity + self.setVelocity(self.ship, LVector3(0, 0, 0)) + Sequence(Wait(2), # Wait 2 seconds + Func(self.ship.setR, 0), # Reset heading + Func(self.ship.setX, 0), # Reset position X + # Reset position Y (Z for Panda) + Func(self.ship.setZ, 0), + Func(self.ship.show), # Show the ship + Func(self.spawnAsteroids)).start() # Remake asteroids + return Task.cont + + # If the player has successfully destroyed all asteroids, respawn them + if len(self.asteroids) == 0: + self.spawnAsteroids() + + return Task.cont # Since every return is Task.cont, the task will + # continue indefinitely + + # Updates the positions of objects + def updatePos(self, obj, dt): + vel = self.getVelocity(obj) + newPos = obj.getPos() + (vel * dt) + + # Check if the object is out of bounds. If so, wrap it + radius = .5 * obj.getScale().getX() + if newPos.getX() - radius > SCREEN_X: + newPos.setX(-SCREEN_X) + elif newPos.getX() + radius < -SCREEN_X: + newPos.setX(SCREEN_X) + if newPos.getZ() - radius > SCREEN_Y: + newPos.setZ(-SCREEN_Y) + elif newPos.getZ() + radius < -SCREEN_Y: + newPos.setZ(SCREEN_Y) + + obj.setPos(newPos) + + # The handler when an asteroid is hit by a bullet + def asteroidHit(self, index): + # If the asteroid is small it is simply removed + if self.asteroids[index].getScale().getX() <= AST_MIN_SCALE: + self.asteroids[index].removeNode() + # Remove the asteroid from the list of asteroids. + del self.asteroids[index] + else: + # If it is big enough, divide it up into little asteroids. + # First we update the current asteroid. + asteroid = self.asteroids[index] + newScale = asteroid.getScale().getX() * AST_SIZE_SCALE + asteroid.setScale(newScale) # Rescale it + + # The new direction is chosen as perpendicular to the old direction + # This is determined using the cross product, which returns a + # vector perpendicular to the two input vectors. By crossing + # velocity with a vector that goes into the screen, we get a vector + # that is orthagonal to the original velocity in the screen plane. + vel = self.getVelocity(asteroid) + speed = vel.length() * AST_VEL_SCALE + vel.normalize() + vel = LVector3(0, 1, 0).cross(vel) + vel *= speed + self.setVelocity(asteroid, vel) + + # Now we create a new asteroid identical to the current one + newAst = loadObject(scale=newScale) + self.setVelocity(newAst, vel * -1) + newAst.setPos(asteroid.getPos()) + newAst.setTexture(asteroid.getTexture(), 1) + self.asteroids.append(newAst) + + # This updates the ship's position. This is similar to the general update + # but takes into account turn and thrust + def updateShip(self, dt): + heading = self.ship.getR() # Heading is the roll value for this model + # Change heading if left or right is being pressed + if self.keys["turnRight"]: + heading += dt * TURN_RATE + self.ship.setR(heading % 360) + elif self.keys["turnLeft"]: + heading -= dt * TURN_RATE + self.ship.setR(heading % 360) + + # Thrust causes acceleration in the direction the ship is currently + # facing + if self.keys["accel"]: + heading_rad = DEG_TO_RAD * heading + # This builds a new velocity vector and adds it to the current one + # relative to the camera, the screen in Panda is the XZ plane. + # Therefore all of our Y values in our velocities are 0 to signify + # no change in that direction. + newVel = \ + LVector3(sin(heading_rad), 0, cos(heading_rad)) * ACCELERATION * dt + newVel += self.getVelocity(self.ship) + # Clamps the new velocity to the maximum speed. lengthSquared() is + # used again since it is faster than length() + if newVel.lengthSquared() > MAX_VEL_SQ: + newVel.normalize() + newVel *= MAX_VEL + self.setVelocity(self.ship, newVel) + + # Finally, update the position as with any other object + self.updatePos(self.ship, dt) + + # Creates a bullet and adds it to the bullet list + def fire(self, time): + direction = DEG_TO_RAD * self.ship.getR() + pos = self.ship.getPos() + bullet = loadObject("bullet.png", scale=.2) # Create the object + bullet.setPos(pos) + # Velocity is in relation to the ship + vel = (self.getVelocity(self.ship) + + (LVector3(sin(direction), 0, cos(direction)) * + BULLET_SPEED)) + self.setVelocity(bullet, vel) + # Set the bullet expiration time to be a certain amount past the + # current time + self.setExpires(bullet, time + BULLET_LIFE) + + # Finally, add the new bullet to the list + self.bullets.append(bullet) + +# We now have everything we need. Make an instance of the class and start +# 3D rendering +demo = AsteroidsDemo() +demo.run() diff --git a/samples/asteroids/models/plane.egg b/samples/asteroids/models/plane.egg new file mode 100644 index 0000000000..b82f1c0a74 --- /dev/null +++ b/samples/asteroids/models/plane.egg @@ -0,0 +1,39 @@ + { Y-Up } + + { + "maya2egg plane.mb plane.egg" +} + groundPlane_transform { +} + pPlane1 { + pPlaneShape1.verts { + 1 { + -0.5 -0.5 0 + { 0 0 -1 } + { 0 0 } + { 1 1 1 1 } + } + 2 { + -0.5 0.5 0 + { 0 0 -1 } + { 0 1 } + { 1 1 1 1 } + } + 3 { + 0.5 -0.5 0 + { 0 0 -1 } + { 1 0 } + { 1 1 1 1 } + } + 4 { + 0.5 0.5 0 + { 0 0 -1 } + { 1 1 } + { 1 1 1 1 } + } + } + { + { 0 0 -1 } + { 3 4 2 1 { pPlaneShape1.verts } } + } +} diff --git a/samples/asteroids/textures/asteroid1.png b/samples/asteroids/textures/asteroid1.png new file mode 100644 index 0000000000..f5b127e096 Binary files /dev/null and b/samples/asteroids/textures/asteroid1.png differ diff --git a/samples/asteroids/textures/asteroid2.png b/samples/asteroids/textures/asteroid2.png new file mode 100644 index 0000000000..6f61c89657 Binary files /dev/null and b/samples/asteroids/textures/asteroid2.png differ diff --git a/samples/asteroids/textures/asteroid3.png b/samples/asteroids/textures/asteroid3.png new file mode 100644 index 0000000000..0ad15afd2e Binary files /dev/null and b/samples/asteroids/textures/asteroid3.png differ diff --git a/samples/asteroids/textures/bullet.png b/samples/asteroids/textures/bullet.png new file mode 100644 index 0000000000..5af5129c62 Binary files /dev/null and b/samples/asteroids/textures/bullet.png differ diff --git a/samples/asteroids/textures/ship.png b/samples/asteroids/textures/ship.png new file mode 100644 index 0000000000..d7ee706447 Binary files /dev/null and b/samples/asteroids/textures/ship.png differ diff --git a/samples/asteroids/textures/stars.jpg b/samples/asteroids/textures/stars.jpg new file mode 100644 index 0000000000..b56c143569 Binary files /dev/null and b/samples/asteroids/textures/stars.jpg differ diff --git a/samples/ball-in-maze/main.py b/samples/ball-in-maze/main.py new file mode 100755 index 0000000000..1ea47aeb05 --- /dev/null +++ b/samples/ball-in-maze/main.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial shows how to detect and respond to collisions. It uses solids +# create in code and the egg files, how to set up collision masks, a traverser, +# and a handler, how to detect collisions, and how to dispatch function based +# on the collisions. All of this is put together to simulate a labyrinth-style +# game + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import CollisionTraverser, CollisionNode +from panda3d.core import CollisionHandlerQueue, CollisionRay +from panda3d.core import Material, LRotationf, NodePath +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import TextNode +from panda3d.core import LVector3, BitMask32 +from direct.gui.OnscreenText import OnscreenText +from direct.interval.MetaInterval import Sequence, Parallel +from direct.interval.LerpInterval import LerpFunc +from direct.interval.FunctionInterval import Func, Wait +from direct.task.Task import Task +import sys + +# Some constants for the program +ACCEL = 70 # Acceleration in ft/sec/sec +MAX_SPEED = 5 # Max speed in ft/sec +MAX_SPEED_SQ = MAX_SPEED ** 2 # Squared to make it easier to use lengthSquared +# Instead of length + + +class BallInMazeDemo(ShowBase): + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # This code puts the standard title and instruction text on screen + self.title = \ + OnscreenText(text="Panda3D: Tutorial - Collision Detection", + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.08, + shadow=(0, 0, 0, 0.5)) + self.instructions = \ + OnscreenText(text="Mouse pointer tilts the board", + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.05, -0.08), fg=(1, 1, 1, 1), scale=.06, + shadow=(0, 0, 0, 0.5)) + + self.accept("escape", sys.exit) # Escape quits + + # Disable default mouse-based camera control. This is a method on the + # ShowBase class from which we inherit. + self.disableMouse() + camera.setPosHpr(0, 0, 25, 0, -90, 0) # Place the camera + + # Load the maze and place it in the scene + self.maze = loader.loadModel("models/maze") + self.maze.reparentTo(render) + + # Most times, you want collisions to be tested against invisible geometry + # rather than every polygon. This is because testing against every polygon + # in the scene is usually too slow. You can have simplified or approximate + # geometry for the solids and still get good results. + # + # Sometimes you'll want to create and position your own collision solids in + # code, but it's often easier to have them built automatically. This can be + # done by adding special tags into an egg file. Check maze.egg and ball.egg + # and look for lines starting with . 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() diff --git a/samples/ball-in-maze/models/ball.egg.pz b/samples/ball-in-maze/models/ball.egg.pz new file mode 100644 index 0000000000..b2433a951a Binary files /dev/null and b/samples/ball-in-maze/models/ball.egg.pz differ diff --git a/samples/ball-in-maze/models/iron05.jpg b/samples/ball-in-maze/models/iron05.jpg new file mode 100644 index 0000000000..78d53bc163 Binary files /dev/null and b/samples/ball-in-maze/models/iron05.jpg differ diff --git a/samples/ball-in-maze/models/limba.jpg b/samples/ball-in-maze/models/limba.jpg new file mode 100644 index 0000000000..0725b7c1b8 Binary files /dev/null and b/samples/ball-in-maze/models/limba.jpg differ diff --git a/samples/ball-in-maze/models/maze.egg.pz b/samples/ball-in-maze/models/maze.egg.pz new file mode 100644 index 0000000000..a2be4a1287 Binary files /dev/null and b/samples/ball-in-maze/models/maze.egg.pz differ diff --git a/samples/boxing-robots/main.py b/samples/boxing-robots/main.py new file mode 100755 index 0000000000..20d59b02a3 --- /dev/null +++ b/samples/boxing-robots/main.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman, and Eddie Caanan +# Last Updated: 2015-03-13 +# +# This tutorial shows how to play animations on models aka "actors". +# It is based on the popular game of "Rock 'em Sock 'em Robots". + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import TextNode +from panda3d.core import LVector3 +from direct.gui.OnscreenText import OnscreenText +from direct.interval.MetaInterval import Sequence +from direct.interval.FunctionInterval import Func, Wait +from direct.actor import Actor +from random import random +import sys + + +class BoxingRobotDemo(ShowBase): + # Macro-like function used to reduce the amount to code needed to create the + # on screen instructions + + def genLabelText(self, text, i): + return OnscreenText(text=text, parent=base.a2dTopLeft, scale=.05, + pos=(0.1, - 0.1 -.07 * i), fg=(1, 1, 1, 1), + align=TextNode.ALeft) + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # This code puts the standard title and instruction text on screen + self.title = OnscreenText(text="Panda3D: Tutorial - Actors", + parent=base.a2dBottomRight, style=1, + fg=(0, 0, 0, 1), pos=(-0.2, 0.1), + align=TextNode.ARight, scale=.09) + + self.escapeEventText = self.genLabelText("ESC: Quit", 0) + self.akeyEventText = self.genLabelText("[A]: Robot 1 Left Punch", 1) + self.skeyEventText = self.genLabelText("[S]: Robot 1 Right Punch", 2) + self.kkeyEventText = self.genLabelText("[K]: Robot 2 Left Punch", 3) + self.lkeyEventText = self.genLabelText("[L]: Robot 2 Right Punch", 4) + + # Set the camera in a fixed position + self.disableMouse() + camera.setPosHpr(14.5, -15.4, 14, 45, -14, 0) + self.setBackgroundColor(0, 0, 0) + + # Add lighting so that the objects are not drawn flat + self.setupLights() + + # Load the ring + self.ring = loader.loadModel('models/ring') + self.ring.reparentTo(render) + + # Models that use skeletal animation are known as Actors instead of models + # Instead of just one file, the have one file for the main model, and an + # additional file for each playable animation. + # They are loaded using Actor.Actor instead of loader.LoadModel. + # The constructor takes the location of the main object as with a normal model + # and a dictionary (A fancy python structure that is like a lookup table) + # that contains names for animations, and paths to the appropriate + # files + self.robot1 = Actor.Actor('models/robot', + {'leftPunch': 'models/robot_left_punch', + 'rightPunch': 'models/robot_right_punch', + 'headUp': 'models/robot_head_up', + 'headDown': 'models/robot_head_down'}) + + # Actors need to be positioned and parented like normal objects + self.robot1.setPosHprScale(-1, -2.5, 4, 45, 0, 0, 1.25, 1.25, 1.25) + self.robot1.reparentTo(render) + + # We'll repeat the process for the second robot. The only thing that changes + # here is the robot's color and position + self.robot2 = Actor.Actor('models/robot', + {'leftPunch': 'models/robot_left_punch', + 'rightPunch': 'models/robot_right_punch', + 'headUp': 'models/robot_head_up', + 'headDown': 'models/robot_head_down'}) + + # Set the properties of this robot + self.robot2.setPosHprScale(1, 1.5, 4, 225, 0, 0, 1.25, 1.25, 1.25) + self.robot2.setColor((.7, 0, 0, 1)) + self.robot2.reparentTo(render) + + # Now we define how the animated models will move. Animations are played + # through special intervals. In this case we use actor intervals in a + # sequence to play the part of the punch animation where the arm extends, + # call a function to check if the punch landed, and then play the part of the + # animation where the arm retracts + + # Punch sequence for robot 1's left arm + self.robot1.punchLeft = Sequence( + # Interval for the outstreched animation + self.robot1.actorInterval('leftPunch', startFrame=1, endFrame=10), + # Function to check if the punch was successful + Func(self.checkPunch, 2), + # Interval for the retract animation + self.robot1.actorInterval('leftPunch', startFrame=11, endFrame=32)) + + # Punch sequence for robot 1's right arm + self.robot1.punchRight = Sequence( + self.robot1.actorInterval('rightPunch', startFrame=1, endFrame=10), + Func(self.checkPunch, 2), + self.robot1.actorInterval('rightPunch', startFrame=11, endFrame=32)) + + # Punch sequence for robot 2's left arm + self.robot2.punchLeft = Sequence( + self.robot2.actorInterval('leftPunch', startFrame=1, endFrame=10), + Func(self.checkPunch, 1), + self.robot2.actorInterval('leftPunch', startFrame=11, endFrame=32)) + + # Punch sequence for robot 2's right arm + self.robot2.punchRight = Sequence( + self.robot2.actorInterval('rightPunch', startFrame=1, endFrame=10), + Func(self.checkPunch, 1), + self.robot2.actorInterval('rightPunch', startFrame=11, endFrame=32)) + + # We use the same techinique to create a sequence for when a robot is knocked + # out where the head pops up, waits a while, and then resets + + # Head animation for robot 1 + self.robot1.resetHead = Sequence( + # Interval for the head going up. Since no start or end frames were given, + # the entire animation is played. + self.robot1.actorInterval('headUp'), + Wait(1.5), + # The head down animation was animated a little too quickly, so this will + # play it at 75% of it's normal speed + self.robot1.actorInterval('headDown', playRate=.75)) + + # Head animation for robot 2 + self.robot2.resetHead = Sequence( + self.robot2.actorInterval('headUp'), + Wait(1.5), + self.robot2.actorInterval('headDown', playRate=.75)) + + # Now that we have defined the motion, we can define our key input. + # Each fist is bound to a key. When a key is pressed, self.tryPunch checks to + # make sure that the both robots have their heads down, and if they do it + # plays the given interval + self.accept('escape', sys.exit) + self.accept('a', self.tryPunch, [self.robot1.punchLeft]) + self.accept('s', self.tryPunch, [self.robot1.punchRight]) + self.accept('k', self.tryPunch, [self.robot2.punchLeft]) + self.accept('l', self.tryPunch, [self.robot2.punchRight]) + + # tryPunch will play the interval passed to it only if + # neither robot has 'resetHead' playing (a head is up) AND + # the punch interval passed to it is not already playing + def tryPunch(self, interval): + if (not self.robot1.resetHead.isPlaying() and + not self.robot2.resetHead.isPlaying() and + not interval.isPlaying()): + interval.start() + + # checkPunch will determine if a successful punch has been thrown + def checkPunch(self, robot): + if robot == 1: + # punch is directed to robot 1 + # if robot 1 is playing'resetHead', do nothing + if self.robot1.resetHead.isPlaying(): + return + # if robot 1 is not punching... + if (not self.robot1.punchLeft.isPlaying() and + not self.robot1.punchRight.isPlaying()): + # ...15% chance of successful hit + if random() > .85: + self.robot1.resetHead.start() + # Otherwise, only 5% chance of sucessful hit + elif random() > .95: + self.robot1.resetHead.start() + else: + # punch is directed to robot 2, same as above + if self.robot2.resetHead.isPlaying(): + return + if (not self.robot2.punchLeft.isPlaying() and + not self.robot2.punchRight.isPlaying()): + if random() > .85: + self.robot2.resetHead.start() + elif random() > .95: + self.robot2.resetHead.start() + + # This function sets up the lighting + def setupLights(self): + ambientLight = AmbientLight("ambientLight") + ambientLight.setColor((.8, .8, .75, 1)) + directionalLight = DirectionalLight("directionalLight") + directionalLight.setDirection(LVector3(0, 0, -2.5)) + directionalLight.setColor((0.9, 0.8, 0.9, 1)) + render.setLight(render.attachNewNode(ambientLight)) + render.setLight(render.attachNewNode(directionalLight)) + +demo = BoxingRobotDemo() +demo.run() diff --git a/samples/boxing-robots/models/ring.egg.pz b/samples/boxing-robots/models/ring.egg.pz new file mode 100644 index 0000000000..81ca6ca8dd Binary files /dev/null and b/samples/boxing-robots/models/ring.egg.pz differ diff --git a/samples/boxing-robots/models/robot.egg.pz b/samples/boxing-robots/models/robot.egg.pz new file mode 100644 index 0000000000..268c2cbb63 Binary files /dev/null and b/samples/boxing-robots/models/robot.egg.pz differ diff --git a/samples/boxing-robots/models/robot_head_down.egg.pz b/samples/boxing-robots/models/robot_head_down.egg.pz new file mode 100644 index 0000000000..ea9529a1d2 Binary files /dev/null and b/samples/boxing-robots/models/robot_head_down.egg.pz differ diff --git a/samples/boxing-robots/models/robot_head_up.egg.pz b/samples/boxing-robots/models/robot_head_up.egg.pz new file mode 100644 index 0000000000..c2678c835f Binary files /dev/null and b/samples/boxing-robots/models/robot_head_up.egg.pz differ diff --git a/samples/boxing-robots/models/robot_left_punch.egg.pz b/samples/boxing-robots/models/robot_left_punch.egg.pz new file mode 100644 index 0000000000..d7dc06405e Binary files /dev/null and b/samples/boxing-robots/models/robot_left_punch.egg.pz differ diff --git a/samples/boxing-robots/models/robot_right_punch.egg.pz b/samples/boxing-robots/models/robot_right_punch.egg.pz new file mode 100644 index 0000000000..372f3bbd4e Binary files /dev/null and b/samples/boxing-robots/models/robot_right_punch.egg.pz differ diff --git a/samples/bump-mapping/main.py b/samples/bump-mapping/main.py new file mode 100755 index 0000000000..f399dfd909 --- /dev/null +++ b/samples/bump-mapping/main.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# +# Bump mapping is a way of making polygonal surfaces look +# less flat. This sample uses normal mapping for all +# surfaces, and also parallax mapping for the column. +# +# This is a tutorial to show how to do normal mapping +# in panda3d using the Shader Generator. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import loadPrcFileData +from panda3d.core import WindowProperties +from panda3d.core import Filename, Shader +from panda3d.core import AmbientLight, PointLight +from panda3d.core import TextNode +from panda3d.core import LPoint3, LVector3 +from direct.task.Task import Task +from direct.actor.Actor import Actor +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.filter.CommonFilters import * +import sys +import os + + +# Function to put instructions on the screen. +def addInstructions(pos, msg): + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), scale=.05, + shadow=(0, 0, 0, 1), parent=base.a2dTopLeft, + pos=(0.08, -pos - 0.04), align=TextNode.ALeft) + +# Function to put title on the screen. +def addTitle(text): + return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), shadow=(0, 0, 0, 1)) + + +class BumpMapDemo(ShowBase): + + def __init__(self): + # Configure the parallax mapping settings (these are just the defaults) + loadPrcFileData("", "parallax-mapping-samples 3\n" + "parallax-mapping-scale 0.1") + + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # Check video card capabilities. + if not self.win.getGsg().getSupportsBasicShaders(): + addTitle("Bump Mapping: " + "Video driver reports that Cg shaders are not supported.") + return + + # Post the instructions + self.title = addTitle("Panda3D: Tutorial - Bump Mapping") + self.inst1 = addInstructions(0.06, "Press ESC to exit") + self.inst2 = addInstructions(0.12, "Move mouse to rotate camera") + self.inst3 = addInstructions(0.18, "Left mouse button: Move forwards") + self.inst4 = addInstructions(0.24, "Right mouse button: Move backwards") + self.inst5 = addInstructions(0.30, "Enter: Turn bump maps Off") + + # Load the 'abstract room' model. This is a model of an + # empty room containing a pillar, a pyramid, and a bunch + # of exaggeratedly bumpy textures. + + self.room = loader.loadModel("models/abstractroom") + self.room.reparentTo(render) + + # Make the mouse invisible, turn off normal mouse controls + self.disableMouse() + props = WindowProperties() + props.setCursorHidden(True) + self.win.requestProperties(props) + self.camLens.setFov(60) + + # Set the current viewing target + self.focus = LVector3(55, -55, 20) + self.heading = 180 + self.pitch = 0 + self.mousex = 0 + self.mousey = 0 + self.last = 0 + self.mousebtn = [0, 0, 0] + + # Start the camera control task: + taskMgr.add(self.controlCamera, "camera-task") + self.accept("escape", sys.exit, [0]) + self.accept("mouse1", self.setMouseBtn, [0, 1]) + self.accept("mouse1-up", self.setMouseBtn, [0, 0]) + self.accept("mouse2", self.setMouseBtn, [1, 1]) + self.accept("mouse2-up", self.setMouseBtn, [1, 0]) + self.accept("mouse3", self.setMouseBtn, [2, 1]) + self.accept("mouse3-up", self.setMouseBtn, [2, 0]) + self.accept("enter", self.toggleShader) + self.accept("j", self.rotateLight, [-1]) + self.accept("k", self.rotateLight, [1]) + self.accept("arrow_left", self.rotateCam, [-1]) + self.accept("arrow_right", self.rotateCam, [1]) + + # Add a light to the scene. + self.lightpivot = render.attachNewNode("lightpivot") + self.lightpivot.setPos(0, 0, 25) + self.lightpivot.hprInterval(10, LPoint3(360, 0, 0)).loop() + plight = PointLight('plight') + plight.setColor((1, 1, 1, 1)) + plight.setAttenuation(LVector3(0.7, 0.05, 0)) + plnp = self.lightpivot.attachNewNode(plight) + plnp.setPos(45, 0, 0) + self.room.setLight(plnp) + + # Add an ambient light + alight = AmbientLight('alight') + alight.setColor((0.2, 0.2, 0.2, 1)) + alnp = render.attachNewNode(alight) + self.room.setLight(alnp) + + # Create a sphere to denote the light + sphere = loader.loadModel("models/icosphere") + sphere.reparentTo(plnp) + + # Tell Panda that it should generate shaders performing per-pixel + # lighting for the room. + self.room.setShaderAuto() + + self.shaderenable = 1 + + def setMouseBtn(self, btn, value): + self.mousebtn[btn] = value + + def rotateLight(self, offset): + self.lightpivot.setH(self.lightpivot.getH() + offset * 20) + + def rotateCam(self, offset): + self.heading = self.heading - offset * 10 + + def toggleShader(self): + self.inst5.destroy() + if (self.shaderenable): + self.inst5 = addInstructions(0.30, "Enter: Turn bump maps On") + self.shaderenable = 0 + self.room.setShaderOff() + else: + self.inst5 = addInstructions(0.30, "Enter: Turn bump maps Off") + self.shaderenable = 1 + self.room.setShaderAuto() + + def controlCamera(self, task): + # figure out how much the mouse has moved (in pixels) + md = self.win.getPointer(0) + x = md.getX() + y = md.getY() + if self.win.movePointer(0, 100, 100): + self.heading = self.heading - (x - 100) * 0.2 + self.pitch = self.pitch - (y - 100) * 0.2 + if self.pitch < -45: + self.pitch = -45 + if self.pitch > 45: + self.pitch = 45 + self.camera.setHpr(self.heading, self.pitch, 0) + dir = self.camera.getMat().getRow3(1) + elapsed = task.time - self.last + if self.last == 0: + elapsed = 0 + if self.mousebtn[0]: + self.focus = self.focus + dir * elapsed * 30 + if self.mousebtn[1] or self.mousebtn[2]: + self.focus = self.focus - dir * elapsed * 30 + self.camera.setPos(self.focus - (dir * 5)) + if self.camera.getX() < -59.0: + self.camera.setX(-59) + if self.camera.getX() > 59.0: + self.camera.setX(59) + if self.camera.getY() < -59.0: + self.camera.setY(-59) + if self.camera.getY() > 59.0: + self.camera.setY(59) + if self.camera.getZ() < 5.0: + self.camera.setZ(5) + if self.camera.getZ() > 45.0: + self.camera.setZ(45) + self.focus = self.camera.getPos() + (dir * 5) + self.last = task.time + return Task.cont + +demo = BumpMapDemo() +demo.run() diff --git a/samples/bump-mapping/models/abstractroom.egg b/samples/bump-mapping/models/abstractroom.egg new file mode 100644 index 0000000000..b3f4d5179a --- /dev/null +++ b/samples/bump-mapping/models/abstractroom.egg @@ -0,0 +1,1671 @@ + { Y-Up } + + { + "maya2egg85 -o room.egg room.mb" +} + phong3SG.tref3 { + layingrock-n.jpg + format { rgba } + alpha-file { layingrock-h.jpg } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } + envtype { normal_height } +} + phong3SG { + layingrock-c.jpg + format { rgb } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } +} + phong2SG.tref1 { + brick-n.jpg + format { rgb } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } + envtype { normal } +} + phong2SG { + brick-c.jpg + format { rgb } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } +} + phong1SG.tref2 { + fieldstone-n.jpg + format { rgb } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } + envtype { normal } +} + phong1SG { + fieldstone-c.jpg + format { rgb } + wrapu { repeat } + wrapv { repeat } + minfilter { linear_mipmap_linear } + magfilter { linear } +} + groundPlane_transform { +} + polySurface2 { + polySurfaceShape2.verts { + 0 { + -60 0 -60 + { + 3 3 + { 0 -3.83044e-009 -1 } + { -1 -3.83044e-009 0 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 1 { + -60 0 -60 + { + -0.5 -0.5 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 2 { + -60 0 -60 + { + 1.5 -0.5 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 3 { + -60 0 60 + { + 1.5 -0.5 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 4 { + -60 0 60 + { + -2 3 + { -5.15653e-008 4.13254e-011 -1 } + { -1 4.13254e-011 5.15653e-008 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 5 { + -60 0 60 + { + -0.5 -0.5 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 6 { + -60 50 -60 + { + -0.5 1.5 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 7 { + -60 50 -60 + { + 1.5 1.5 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 8 { + -60 50 60 + { + 1.5 1.5 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 9 { + -60 50 60 + { + -0.5 1.5 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 10 { + -37.5 0 25 + { + -1.49994 -1 + { 0 0 1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 11 { + -37.5 0 25 + { + 2.50006 -1 + { 0 0 1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 12 { + -37.5 50 25 + { + -1.49994 2 + { 0 0 1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 13 { + -37.5 50 25 + { + 2.50006 2 + { 0 0 1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 14 { + -37.5 50 25 + { + -0.541667 2.0625 + { 0 0 -1 } + { -1 0 0 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 15 { + -36.8882 0 21.1373 + { + 2.30006 -1 + { -0.309017 0 0.951057 } + { 0 1 0 } + } + { -0.951057 0 -0.309017 } + { 1 1 1 1 } + } + 16 { + -36.8882 0 28.8627 + { + -1.29994 -1 + { 0.309017 0 0.951057 } + { 0 1 0 } + } + { -0.951057 0 0.309017 } + { 1 1 1 1 } + } + 17 { + -36.8882 50 21.1373 + { + 2.30006 2 + { -0.309017 0 0.951057 } + { 0 1 0 } + } + { -0.951057 0 -0.309017 } + { 1 1 1 1 } + } + 18 { + -36.8882 50 21.1373 + { + -0.38072 2.03701 + { 6.17012e-006 0 -1 } + { -1 0 -6.17012e-006 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 19 { + -36.8882 50 28.8627 + { + -1.29994 2 + { 0.309017 0 0.951057 } + { 0 1 0 } + } + { -0.951057 0 0.309017 } + { 1 1 1 1 } + } + 20 { + -36.8882 50 28.8627 + { + -0.702613 2.03701 + { -6.17012e-006 0 -1 } + { -1 0 6.17012e-006 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 21 { + -35.1127 0 17.6527 + { + 2.10006 -1 + { -0.587785 0 0.809017 } + { 0 1 0 } + } + { -0.809017 0 -0.587785 } + { 1 1 1 1 } + } + 22 { + -35.1127 0 32.3473 + { + -1.09994 -1 + { 0.587785 0 0.809017 } + { 0 1 0 } + } + { -0.809017 0 0.587785 } + { 1 1 1 1 } + } + 23 { + -35.1127 50 17.6527 + { + 2.10006 2 + { -0.587785 0 0.809017 } + { 0 1 0 } + } + { -0.809017 0 -0.587785 } + { 1 1 1 1 } + } + 24 { + -35.1127 50 17.6527 + { + -0.235528 1.96303 + { -1.03074e-007 0 -1 } + { -1 0 1.03074e-007 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 25 { + -35.1127 50 32.3473 + { + -1.09994 2 + { 0.587785 0 0.809017 } + { 0 1 0 } + } + { -0.809017 0 0.587785 } + { 1 1 1 1 } + } + 26 { + -35.1127 50 32.3473 + { + -0.847805 1.96303 + { 1.03075e-007 0 -1 } + { -1 0 -1.03075e-007 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 27 { + -32.3473 0 14.8873 + { + 1.90006 -1 + { -0.809017 0 0.587786 } + { 0 1 0 } + } + { -0.587786 0 -0.809017 } + { 1 1 1 1 } + } + 28 { + -32.3473 0 35.1127 + { + -0.899936 -1 + { 0.809017 0 0.587785 } + { 0 1 0 } + } + { -0.587785 0 0.809017 } + { 1 1 1 1 } + } + 29 { + -32.3473 0 35.1127 + { + -0.96303 1.84781 + { 1.52608e-007 4.13254e-011 -1 } + { -1 4.13254e-011 -1.52608e-007 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 30 { + -32.3473 50 14.8873 + { + 1.90006 2 + { -0.809017 0 0.587786 } + { 0 1 0 } + } + { -0.587786 0 -0.809017 } + { 1 1 1 1 } + } + 31 { + -32.3473 50 14.8873 + { + -0.120303 1.84781 + { -8.0634e-006 0 -1 } + { -1 0 8.0634e-006 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 32 { + -32.3473 50 35.1127 + { + -0.899936 2 + { 0.809017 0 0.587785 } + { 0 1 0 } + } + { -0.587785 0 0.809017 } + { 1 1 1 1 } + } + 33 { + -28.8627 0 13.1118 + { + 1.70006 -1 + { -0.951057 0 0.309017 } + { 0 1 0 } + } + { -0.309017 0 -0.951057 } + { 1 1 1 1 } + } + 34 { + -28.8627 0 36.8882 + { + -0.699936 -1 + { 0.951057 0 0.309016 } + { 0 1 0 } + } + { -0.309016 0 0.951057 } + { 1 1 1 1 } + } + 35 { + -28.8627 50 13.1118 + { + 1.70006 2 + { -0.951057 0 0.309017 } + { 0 1 0 } + } + { -0.309017 0 -0.951057 } + { 1 1 1 1 } + } + 36 { + -28.8627 50 13.1118 + { + -0.0463246 1.70261 + { 3.81471e-007 0 -1 } + { -1 0 -3.81471e-007 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 37 { + -28.8627 50 36.8882 + { + -0.699936 2 + { 0.951057 0 0.309016 } + { 0 1 0 } + } + { -0.309016 0 0.951057 } + { 1 1 1 1 } + } + 38 { + -28.8627 50 36.8882 + { + -1.03701 1.70261 + { -3.81475e-007 0 -1 } + { -1 -4.89652e-008 3.81475e-007 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 39 { + -25 0 12.5 + { + 1.50006 -1 + { -1 0 -6.03476e-007 } + { 0 1 0 } + } + { 6.03476e-007 0 -1 } + { 1 1 1 1 } + } + 40 { + -25 0 37.5 + { + -0.499936 -1 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 41 { + -25 50 12.5 + { + -0.0208334 1.54167 + { 1.16912e-005 0 -1 } + { -1 0 -1.16912e-005 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 42 { + -25 50 12.5 + { + 1.50006 2 + { -1 0 -6.03476e-007 } + { 0 1 0 } + } + { 6.03476e-007 0 -1 } + { 1 1 1 1 } + } + 43 { + -25 50 37.5 + { + -0.499936 2 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 44 { + -25 50 37.5 + { + -1.0625 1.54167 + { -1.16911e-005 0 -1 } + { -1 -4.89652e-008 1.16911e-005 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 45 { + -21.1373 0 13.1118 + { + 1.30006 -1 + { -0.951057 0 -0.309017 } + { 0 1 0 } + } + { 0.309017 0 -0.951057 } + { 1 1 1 1 } + } + 46 { + -21.1373 0 36.8882 + { + -0.299936 -1 + { 0.951057 0 -0.309016 } + { 0 1 0 } + } + { 0.309016 0 0.951057 } + { 1 1 1 1 } + } + 47 { + -21.1373 50 13.1118 + { + -0.0463246 1.38072 + { -1.09281e-005 0 -1 } + { -1 0 1.09281e-005 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 48 { + -21.1373 50 13.1118 + { + 1.30006 2 + { -0.951057 0 -0.309017 } + { 0 1 0 } + } + { 0.309017 0 -0.951057 } + { 1 1 1 1 } + } + 49 { + -21.1373 50 36.8882 + { + -1.03701 1.38072 + { 1.09282e-005 0 -1 } + { -1 -4.89652e-008 -1.09282e-005 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 50 { + -21.1373 50 36.8882 + { + -0.299936 2 + { 0.951057 0 -0.309016 } + { 0 1 0 } + } + { 0.309016 0 0.951057 } + { 1 1 1 1 } + } + 51 { + -17.6527 0 14.8873 + { + 1.10006 -1 + { -0.809017 0 -0.587785 } + { 0 1 0 } + } + { 0.587785 0 -0.809017 } + { 1 1 1 1 } + } + 52 { + -17.6527 0 35.1127 + { + -0.0999364 -1 + { 0.809017 0 -0.587785 } + { 0 1 0 } + } + { 0.587785 0 0.809017 } + { 1 1 1 1 } + } + 53 { + -17.6527 50 14.8873 + { + -0.120304 1.23553 + { -7.25597e-007 0 -1 } + { -1 0 7.25597e-007 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 54 { + -17.6527 50 14.8873 + { + 1.10006 2 + { -0.809017 0 -0.587785 } + { 0 1 0 } + } + { 0.587785 0 -0.809017 } + { 1 1 1 1 } + } + 55 { + -17.6527 50 35.1127 + { + -0.96303 1.23553 + { 7.25593e-007 0 -1 } + { -1 -4.89652e-008 -7.25593e-007 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 56 { + -17.6527 50 35.1127 + { + -0.0999364 2 + { 0.809017 0 -0.587785 } + { 0 1 0 } + } + { 0.587785 0 0.809017 } + { 1 1 1 1 } + } + 57 { + -14.8873 0 17.6527 + { + -0.235528 1.1203 + { -5.45099e-007 4.13254e-011 -1 } + { -1 4.13254e-011 5.45099e-007 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 58 { + -14.8873 0 17.6527 + { + 0.900064 -1 + { -0.587785 0 -0.809017 } + { 0 1 0 } + } + { 0.809017 0 -0.587785 } + { 1 1 1 1 } + } + 59 { + -14.8873 0 32.3473 + { + 0.100064 -1 + { 0.587785 0 -0.809017 } + { 0 1 0 } + } + { 0.809017 0 0.587785 } + { 1 1 1 1 } + } + 60 { + -14.8873 50 17.6527 + { + 0.900064 2 + { -0.587785 0 -0.809017 } + { 0 1 0 } + } + { 0.809017 0 -0.587785 } + { 1 1 1 1 } + } + 61 { + -14.8873 50 32.3473 + { + -0.847805 1.1203 + { -1.79128e-006 0 -1 } + { -1 -4.89652e-008 1.79128e-006 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 62 { + -14.8873 50 32.3473 + { + 0.100064 2 + { 0.587785 0 -0.809017 } + { 0 1 0 } + } + { 0.809017 0 0.587785 } + { 1 1 1 1 } + } + 63 { + -13.1118 0 21.1373 + { + 0.700064 -1 + { -0.309017 0 -0.951057 } + { 0 1 0 } + } + { 0.951057 0 -0.309017 } + { 1 1 1 1 } + } + 64 { + -13.1118 0 28.8627 + { + 0.300064 -1 + { 0.309017 0 -0.951057 } + { 0 1 0 } + } + { 0.951057 0 0.309017 } + { 1 1 1 1 } + } + 65 { + -13.1118 50 21.1373 + { + -0.38072 1.04633 + { 3.58256e-006 0 -1 } + { -1 -4.89652e-008 -3.58256e-006 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 66 { + -13.1118 50 21.1373 + { + 0.700064 2 + { -0.309017 0 -0.951057 } + { 0 1 0 } + } + { 0.951057 0 -0.309017 } + { 1 1 1 1 } + } + 67 { + -13.1118 50 28.8627 + { + -0.702613 1.04633 + { -3.58256e-006 0 -1 } + { -1 -4.89652e-008 3.58256e-006 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 68 { + -13.1118 50 28.8627 + { + 0.300064 2 + { 0.309017 0 -0.951057 } + { 0 1 0 } + } + { 0.951057 0 0.309017 } + { 1 1 1 1 } + } + 69 { + -12.5 0 25 + { + 0.500064 -1 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 70 { + -12.5 50 25 + { + -0.541667 1.02083 + { 0 0 -1 } + { -1 -4.89652e-008 0 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + 71 { + -12.5 50 25 + { + 0.500064 2 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 72 { + 5 0 -5 + { + 0.708333 0.291667 + { -1.84039e-007 4.13254e-011 -1 } + { -1 4.13254e-011 1.84039e-007 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 73 { + 5 1.19209e-006 -45 + { + 1.5 -0.5 + { -1 -1.49012e-008 0 } + { -1.49012e-008 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 74 { + 5 10 -45 + { + 1.5 0.166667 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 75 { + 5 10 -45 + { + 1.5 0.166667 + { -1 0 0 } + { 0 0.707107 0.707107 } + } + { 0 0.707107 -0.707107 } + { 1 1 1 1 } + } + 76 { + 25 30 -25 + { + 0.5 1.5 + { -1 0 0 } + { 0 0.707107 0.707107 } + } + { 0 0.707107 -0.707107 } + { 1 1 1 1 } + } + 77 { + 25 30 -25 + { + 0.5 1.5 + { 0 0 -1 } + { -0.707107 0.707107 0 } + } + { 0.707107 0.707107 0 } + { 1 1 1 1 } + } + 78 { + 45 0 -45 + { + -0.5 -0.5 + { -1 -1.49012e-008 0 } + { -1.49012e-008 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 79 { + 45 0 -45 + { + 2.375 -1.375 + { -2.98023e-008 4.13254e-011 -1 } + { -1 4.13254e-011 2.98023e-008 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 80 { + 45 0 -45 + { + 1.5 -0.5 + { 0 1.49012e-008 -1 } + { 0 1 1.49012e-008 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 81 { + 45 1.19209e-006 -5 + { + -0.5 -0.5 + { 0 1.49012e-008 -1 } + { 0 1 1.49012e-008 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 82 { + 45 10 -45 + { + -0.5 0.166667 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 83 { + 45 10 -45 + { + -0.5 0.166667 + { -1 0 0 } + { 0 0.707107 0.707107 } + } + { 0 0.707107 -0.707107 } + { 1 1 1 1 } + } + 84 { + 45 10 -45 + { + 1.5 0.166667 + { 0 0 -1 } + { -0.707107 0.707107 0 } + } + { 0.707107 0.707107 0 } + { 1 1 1 1 } + } + 85 { + 45 10 -45 + { + 1.5 0.166667 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 86 { + 45 10 -5 + { + -0.5 0.166667 + { 0 0 -1 } + { -0.707107 0.707107 0 } + } + { 0.707107 0.707107 0 } + { 1 1 1 1 } + } + 87 { + 45 10 -5 + { + -0.5 0.166667 + { 0 0 -1 } + { 0 1 0 } + } + { 1 0 0 } + { 1 1 1 1 } + } + 88 { + 60 0 -60 + { + 1.5 1.5 + { 0 0 -1 } + { 0 -1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 89 { + 60 0 -60 + { + 1.5 -0.5 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 90 { + 60 0 -60 + { + 3 -2 + { -1.19209e-008 4.13254e-011 -1 } + { -1 4.13254e-011 1.19209e-008 } + } + { 4.13254e-011 1 4.13254e-011 } + { 1 1 1 1 } + } + 91 { + 60 0 60 + { + -0.5 1.5 + { 0 0 -1 } + { 0 -1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 92 { + 60 0 60 + { + -0.5 -0.5 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 93 { + 60 0 60 + { + -2 -2 + { 0 3.91309e-009 -1 } + { -1 3.91309e-009 0 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 94 { + 60 50 -60 + { + 1.5 -0.5 + { 0 0 -1 } + { 0 -1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 95 { + 60 50 -60 + { + 1.5 1.5 + { 1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 96 { + 60 50 60 + { + -0.5 -0.5 + { 0 0 -1 } + { 0 -1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 97 { + 60 50 60 + { + -0.5 1.5 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 -1 } + { 1 1 1 1 } + } + 98 { + -60 50 -60 + { + 3 3 + { 0 0 -1 } + { -1 0 0 } + } + { 0 -1 0 } + { 1 1 1 1 } + } + 99 { + -60 50 60 + { + -2 3 + { -1.18287e-008 0 -1 } + { -1 -2.44826e-008 1.18287e-008 } + } + { 2.44826e-008 -1 0 } + { 1 1 1 1 } + } + 100 { + -37.5 0 25 + { + -0.541667 2.0625 + { 0 -3.83044e-009 -1 } + { -1 -3.83044e-009 0 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 101 { + -36.8882 0 21.1373 + { + -0.38072 2.03701 + { -2.784e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 2.784e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 102 { + -36.8882 0 28.8627 + { + -0.702613 2.03701 + { 2.784e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 -2.784e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 103 { + -35.1127 0 17.6527 + { + -0.235528 1.96303 + { -1.03074e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 1.03074e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 104 { + -35.1127 0 32.3473 + { + -0.847805 1.96303 + { 1.03075e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 -1.03075e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 105 { + -32.3473 0 14.8873 + { + -0.120303 1.84781 + { -8.0634e-006 -3.83047e-009 -1 } + { -1 -3.83041e-009 8.0634e-006 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 106 { + -32.3473 50 35.1127 + { + -0.96303 1.84781 + { 1.52608e-007 0 -1 } + { -1 -2.44826e-008 -1.52608e-007 } + } + { 2.44826e-008 -1 0 } + { 1 1 1 1 } + } + 107 { + -28.8627 0 13.1118 + { + -0.0463246 1.70261 + { 3.81471e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 -3.81471e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 108 { + -28.8627 0 36.8882 + { + -1.03701 1.70261 + { -3.81475e-007 3.91309e-009 -1 } + { -1 3.91309e-009 3.81475e-007 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 109 { + -25 0 12.5 + { + -0.0208334 1.54167 + { 1.16912e-005 -3.83039e-009 -1 } + { -1 -3.83048e-009 -1.16912e-005 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 110 { + -25 0 37.5 + { + -1.0625 1.54167 + { -1.16911e-005 3.91314e-009 -1 } + { -1 3.91304e-009 1.16911e-005 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 111 { + -21.1373 0 13.1118 + { + -0.0463246 1.38072 + { -1.09281e-005 -3.83048e-009 -1 } + { -1 -3.8304e-009 1.09281e-005 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 112 { + -21.1373 0 36.8882 + { + -1.03701 1.38072 + { 1.09282e-005 3.91305e-009 -1 } + { -1 3.91313e-009 -1.09282e-005 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 113 { + -17.6527 0 14.8873 + { + -0.120304 1.23553 + { -7.25597e-007 -3.83044e-009 -1 } + { -1 -3.83044e-009 7.25597e-007 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 114 { + -17.6527 0 35.1127 + { + -0.96303 1.23553 + { 7.25593e-007 3.91309e-009 -1 } + { -1 3.91309e-009 -7.25593e-007 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 115 { + -14.8873 0 32.3473 + { + -0.847805 1.1203 + { -1.79128e-006 3.9131e-009 -1 } + { -1 3.91308e-009 1.79128e-006 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 116 { + -14.8873 50 17.6527 + { + -0.235528 1.1203 + { -4.96743e-007 0 -1 } + { -1 -2.44826e-008 4.96743e-007 } + } + { 2.44826e-008 -1 0 } + { 1 1 1 1 } + } + 117 { + -13.1118 0 21.1373 + { + -0.38072 1.04633 + { 3.58256e-006 3.91308e-009 -1 } + { -1 3.9131e-009 -3.58256e-006 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 118 { + -13.1118 0 28.8627 + { + -0.702613 1.04633 + { -3.58256e-006 3.9131e-009 -1 } + { -1 3.91308e-009 3.58256e-006 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 119 { + -12.5 0 25 + { + -0.541667 1.02083 + { 0 3.91309e-009 -1 } + { -1 3.91309e-009 0 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 120 { + 5 0 -5 + { + -0.5 -0.5 + { 0 -1.49012e-008 -1 } + { 0 1 -1.49012e-008 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 121 { + 5 0 -5 + { + 1.5 -0.5 + { -1 1.49012e-008 0 } + { 1.49012e-008 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 122 { + 5 1.19209e-006 -45 + { + 1.5 -0.5 + { 0 -1.49012e-008 -1 } + { 0 1 -1.49012e-008 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 123 { + 5 1.19209e-006 -45 + { + 2.375 0.291667 + { 0 -3.83044e-009 -1 } + { -1 -3.83044e-009 0 } + } + { -3.83044e-009 1 -3.83044e-009 } + { 1 1 1 1 } + } + 124 { + 5 10 -45 + { + 1.5 0.166667 + { 0 0 -1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 125 { + 5 10 -45 + { + 1.5 0.166667 + { 0 0 -1 } + { 0.707107 0.707107 0 } + } + { -0.707107 0.707107 0 } + { 1 1 1 1 } + } + 126 { + 5 10 -5 + { + -0.5 0.166667 + { 0 0 -1 } + { 0 1 0 } + } + { -1 0 0 } + { 1 1 1 1 } + } + 127 { + 5 10 -5 + { + -0.5 0.166667 + { 0 0 -1 } + { 0.707107 0.707107 0 } + } + { -0.707107 0.707107 0 } + { 1 1 1 1 } + } + 128 { + 5 10 -5 + { + 1.5 0.166667 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 129 { + 5 10 -5 + { + 1.5 0.166667 + { -1 0 0 } + { 0 0.707107 -0.707107 } + } + { 0 0.707107 0.707107 } + { 1 1 1 1 } + } + 130 { + 25 30 -25 + { + 0.5 1.5 + { 0 0 -1 } + { 0.707107 0.707107 0 } + } + { -0.707107 0.707107 0 } + { 1 1 1 1 } + } + 131 { + 25 30 -25 + { + 0.5 1.5 + { -1 0 0 } + { 0 0.707107 -0.707107 } + } + { 0 0.707107 0.707107 } + { 1 1 1 1 } + } + 132 { + 45 1.19209e-006 -5 + { + -0.5 -0.5 + { -1 1.49012e-008 0 } + { 1.49012e-008 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 133 { + 45 1.19209e-006 -5 + { + 0.708333 -1.375 + { 0 3.91309e-009 -1 } + { -1 3.91309e-009 0 } + } + { 3.91309e-009 1 3.91309e-009 } + { 1 1 1 1 } + } + 134 { + 45 10 -5 + { + -0.5 0.166667 + { -1 0 0 } + { 0 1 0 } + } + { 0 0 1 } + { 1 1 1 1 } + } + 135 { + 45 10 -5 + { + -0.5 0.166667 + { -1 0 0 } + { 0 0.707107 -0.707107 } + } + { 0 0.707107 0.707107 } + { 1 1 1 1 } + } + 136 { + 60 50 -60 + { + 3 -2 + { -6.29762e-008 0 -1 } + { -1 -2.44826e-008 6.29762e-008 } + } + { 2.44826e-008 -1 0 } + { 1 1 1 1 } + } + 137 { + 60 50 60 + { + -2 -2 + { 0 0 -1 } + { -1 -4.89652e-008 0 } + } + { 4.89652e-008 -1 0 } + { 1 1 1 1 } + } + } + { + { 0 0.707107 -0.707107 } + { phong2SG } + { phong2SG.tref1 } + { 75 76 83 { polySurfaceShape2.verts } } + } + { + { -0.707107 0.707107 0 } + { phong2SG } + { phong2SG.tref1 } + { 127 130 125 { polySurfaceShape2.verts } } + } + { + { 0 0.707107 0.707107 } + { phong2SG } + { phong2SG.tref1 } + { 135 131 129 { polySurfaceShape2.verts } } + } + { + { 0.707107 0.707107 0 } + { phong2SG } + { phong2SG.tref1 } + { 84 77 86 { polySurfaceShape2.verts } } + } + { + { 0 0 -1 } + { phong2SG } + { phong2SG.tref1 } + { 78 73 74 82 { polySurfaceShape2.verts } } + } + { + { -1 0 0 } + { phong2SG } + { phong2SG.tref1 } + { 122 120 126 124 { polySurfaceShape2.verts } } + } + { + { 0 0 1 } + { phong2SG } + { phong2SG.tref1 } + { 121 132 134 128 { polySurfaceShape2.verts } } + } + { + { 1 0 0 } + { phong2SG } + { phong2SG.tref1 } + { 81 80 85 87 { polySurfaceShape2.verts } } + } + { + { -1 0 0 } + { phong1SG } + { phong1SG.tref2 } + { 91 96 94 88 { polySurfaceShape2.verts } } + } + { + { 4.89652e-008 -1 0 } + { phong1SG } + { phong1SG.tref2 } + { + 49 55 61 67 70 65 116 136 137 99 106 38 44 + { polySurfaceShape2.verts } + } + } + { + { 0 -1 0 } + { phong1SG } + { phong1SG.tref2 } + { + 99 98 136 116 53 47 41 36 31 24 18 14 20 26 106 + { polySurfaceShape2.verts } + } + } + { + { 1 0 0 } + { phong1SG } + { phong1SG.tref2 } + { 9 5 2 7 { polySurfaceShape2.verts } } + } + { + { 3.91309e-009 1 3.91309e-009 } + { phong1SG } + { phong1SG.tref2 } + { + 93 90 79 133 72 57 117 119 118 115 114 112 110 108 29 4 + { polySurfaceShape2.verts } + } + } + { + { -3.83044e-009 1 -3.83044e-009 } + { phong1SG } + { phong1SG.tref2 } + { + 123 79 90 0 4 29 104 102 100 101 103 105 107 109 111 113 57 72 + { polySurfaceShape2.verts } + } + } + { + { 0 0 1 } + { phong1SG } + { phong1SG.tref2 } + { 89 95 6 1 { polySurfaceShape2.verts } } + } + { + { 0 0 -1 } + { phong1SG } + { phong1SG.tref2 } + { 3 8 97 92 { polySurfaceShape2.verts } } + } + { + { -0.987688 0 0.156435 } + { phong3SG } + { phong3SG.tref3 } + { 10 16 19 12 { polySurfaceShape2.verts } } + } + { + { -0.987688 0 -0.156435 } + { phong3SG } + { phong3SG.tref3 } + { 15 11 13 17 { polySurfaceShape2.verts } } + } + { + { -0.891007 0 -0.45399 } + { phong3SG } + { phong3SG.tref3 } + { 21 15 17 23 { polySurfaceShape2.verts } } + } + { + { -0.707107 0 -0.707107 } + { phong3SG } + { phong3SG.tref3 } + { 27 21 23 30 { polySurfaceShape2.verts } } + } + { + { -0.453991 0 -0.891006 } + { phong3SG } + { phong3SG.tref3 } + { 33 27 30 35 { polySurfaceShape2.verts } } + } + { + { -0.156433 0 -0.987689 } + { phong3SG } + { phong3SG.tref3 } + { 39 33 35 42 { polySurfaceShape2.verts } } + } + { + { 0.156434 0 -0.987688 } + { phong3SG } + { phong3SG.tref3 } + { 45 39 42 48 { polySurfaceShape2.verts } } + } + { + { 0.453991 0 -0.891007 } + { phong3SG } + { phong3SG.tref3 } + { 51 45 48 54 { polySurfaceShape2.verts } } + } + { + { 0.707107 0 -0.707107 } + { phong3SG } + { phong3SG.tref3 } + { 58 51 54 60 { polySurfaceShape2.verts } } + } + { + { 0.891007 0 -0.453991 } + { phong3SG } + { phong3SG.tref3 } + { 63 58 60 66 { polySurfaceShape2.verts } } + } + { + { 0.987688 0 -0.156434 } + { phong3SG } + { phong3SG.tref3 } + { 69 63 66 71 { polySurfaceShape2.verts } } + } + { + { 0.987688 0 0.156434 } + { phong3SG } + { phong3SG.tref3 } + { 64 69 71 68 { polySurfaceShape2.verts } } + } + { + { 0.891007 0 0.453991 } + { phong3SG } + { phong3SG.tref3 } + { 59 64 68 62 { polySurfaceShape2.verts } } + } + { + { 0.707107 0 0.707106 } + { phong3SG } + { phong3SG.tref3 } + { 52 59 62 56 { polySurfaceShape2.verts } } + } + { + { 0.45399 0 0.891007 } + { phong3SG } + { phong3SG.tref3 } + { 46 52 56 50 { polySurfaceShape2.verts } } + } + { + { 0.156434 0 0.987688 } + { phong3SG } + { phong3SG.tref3 } + { 40 46 50 43 { polySurfaceShape2.verts } } + } + { + { -0.156434 0 0.987688 } + { phong3SG } + { phong3SG.tref3 } + { 34 40 43 37 { polySurfaceShape2.verts } } + } + { + { -0.45399 0 0.891007 } + { phong3SG } + { phong3SG.tref3 } + { 28 34 37 32 { polySurfaceShape2.verts } } + } + { + { -0.707107 0 0.707107 } + { phong3SG } + { phong3SG.tref3 } + { 22 28 32 25 { polySurfaceShape2.verts } } + } + { + { -0.891007 0 0.45399 } + { phong3SG } + { phong3SG.tref3 } + { 16 22 25 19 { polySurfaceShape2.verts } } + } +} diff --git a/samples/bump-mapping/models/abstractroom.mb b/samples/bump-mapping/models/abstractroom.mb new file mode 100644 index 0000000000..2d64cca3d4 Binary files /dev/null and b/samples/bump-mapping/models/abstractroom.mb differ diff --git a/samples/bump-mapping/models/brick-c.jpg b/samples/bump-mapping/models/brick-c.jpg new file mode 100644 index 0000000000..71c802f937 Binary files /dev/null and b/samples/bump-mapping/models/brick-c.jpg differ diff --git a/samples/bump-mapping/models/brick-n.jpg b/samples/bump-mapping/models/brick-n.jpg new file mode 100644 index 0000000000..e17ec5d941 Binary files /dev/null and b/samples/bump-mapping/models/brick-n.jpg differ diff --git a/samples/bump-mapping/models/fieldstone-c.jpg b/samples/bump-mapping/models/fieldstone-c.jpg new file mode 100644 index 0000000000..a5d8dc9874 Binary files /dev/null and b/samples/bump-mapping/models/fieldstone-c.jpg differ diff --git a/samples/bump-mapping/models/fieldstone-n.jpg b/samples/bump-mapping/models/fieldstone-n.jpg new file mode 100644 index 0000000000..493683ce57 Binary files /dev/null and b/samples/bump-mapping/models/fieldstone-n.jpg differ diff --git a/samples/bump-mapping/models/icosphere.egg b/samples/bump-mapping/models/icosphere.egg new file mode 100644 index 0000000000..474e8740b2 --- /dev/null +++ b/samples/bump-mapping/models/icosphere.egg @@ -0,0 +1,497 @@ + { Z-Up } + + { + "egg-trans -F icosphere.egg -o icosphere.egg" +} + Icosphere { + Icosphere { + 0 { + 0 0 -1 + { 0 0 -1 } + } + 1 { + 0.425323 -0.309011 -0.850654 + { 0.425306 -0.309 -0.850642 } + } + 2 { + -0.162456 -0.499995 -0.850654 + { -0.16245 -0.499985 -0.850642 } + } + 3 { + 0.723607 -0.525725 -0.44722 + { 0.723594 -0.525712 -0.447188 } + } + 4 { + 0.850648 0 -0.525736 + { 0.850642 0 -0.525712 } + } + 5 { + -0.52573 0 -0.850652 + { -0.525712 0 -0.850642 } + } + 6 { + -0.162456 0.499995 -0.850654 + { -0.16245 0.499985 -0.850642 } + } + 7 { + 0.425323 0.309011 -0.850654 + { 0.425306 0.309 -0.850642 } + } + 8 { + 0.951058 -0.309013 0 + { 0.951048 -0.309 0 } + } + 9 { + -0.276388 -0.850649 -0.44722 + { -0.276376 -0.850642 -0.447218 } + } + 10 { + 0.262869 -0.809012 -0.525738 + { 0.262856 -0.808985 -0.525712 } + } + 11 { + 0 -1 0 + { 0 -1 0 } + } + 12 { + -0.894426 0 -0.447216 + { -0.894406 0 -0.447188 } + } + 13 { + -0.688189 -0.499997 -0.525736 + { -0.688162 -0.499985 -0.525712 } + } + 14 { + -0.951058 -0.309013 0 + { -0.951048 -0.309 0 } + } + 15 { + -0.276388 0.850649 -0.44722 + { -0.276376 0.850642 -0.447218 } + } + 16 { + -0.688189 0.499997 -0.525736 + { -0.688162 0.499985 -0.525712 } + } + 17 { + -0.587786 0.809017 0 + { -0.587756 0.809015 0 } + } + 18 { + 0.723607 0.525725 -0.44722 + { 0.723594 0.525712 -0.447188 } + } + 19 { + 0.262869 0.809012 -0.525738 + { 0.262856 0.808985 -0.525712 } + } + 20 { + 0.587786 0.809017 0 + { 0.587756 0.809015 0 } + } + 21 { + 0.587786 -0.809017 0 + { 0.587756 -0.809015 0 } + } + 22 { + -0.587786 -0.809017 0 + { -0.587756 -0.809015 0 } + } + 23 { + -0.951058 0.309013 0 + { -0.951048 0.309 0 } + } + 24 { + 0 1 0 + { 0 1 0 } + } + 25 { + 0.951058 0.309013 0 + { 0.951048 0.309 0 } + } + 26 { + 0.276388 -0.850649 0.44722 + { 0.276376 -0.850642 0.447218 } + } + 27 { + 0.688189 -0.499997 0.525736 + { 0.688162 -0.499985 0.525712 } + } + 28 { + 0.162456 -0.499995 0.850654 + { 0.16245 -0.499985 0.850642 } + } + 29 { + -0.723607 -0.525725 0.44722 + { -0.723594 -0.525712 0.447188 } + } + 30 { + -0.262869 -0.809012 0.525738 + { -0.262856 -0.808985 0.525712 } + } + 31 { + -0.425323 -0.309011 0.850654 + { -0.425306 -0.309 0.850642 } + } + 32 { + -0.723607 0.525725 0.44722 + { -0.723594 0.525712 0.447188 } + } + 33 { + -0.850648 0 0.525736 + { -0.850642 0 0.525712 } + } + 34 { + -0.425323 0.309011 0.850654 + { -0.425306 0.309 0.850642 } + } + 35 { + 0.276388 0.850649 0.44722 + { 0.276376 0.850642 0.447218 } + } + 36 { + -0.262869 0.809012 0.525738 + { -0.262856 0.808985 0.525712 } + } + 37 { + 0.162456 0.499995 0.850654 + { 0.16245 0.499985 0.850642 } + } + 38 { + 0.894426 0 0.447216 + { 0.894406 0 0.447188 } + } + 39 { + 0.688189 0.499997 0.525736 + { 0.688162 0.499985 0.525712 } + } + 40 { + 0.52573 0 0.850652 + { 0.525712 0 0.850642 } + } + 41 { + 0 0 1 + { 0 0 1 } + } + } + { + { 0.102381 -0.31509 -0.943523 } + { 0 1 2 { Icosphere } } + } + { + { 0.700224 -0.268032 -0.661699 } + { 3 1 4 { Icosphere } } + } + { + { -0.268034 -0.194736 -0.943523 } + { 0 2 5 { Icosphere } } + } + { + { -0.268034 0.194737 -0.943523 } + { 0 5 6 { Icosphere } } + } + { + { 0.102381 0.31509 -0.943523 } + { 0 6 7 { Icosphere } } + } + { + { 0.904989 -0.268032 -0.330385 } + { 3 4 8 { Icosphere } } + } + { + { 0.024747 -0.943521 -0.330386 } + { 9 10 11 { Icosphere } } + } + { + { -0.889697 -0.315095 -0.330385 } + { 12 13 14 { Icosphere } } + } + { + { -0.574602 0.748784 -0.330388 } + { 15 16 17 { Icosphere } } + } + { + { 0.534576 0.777865 -0.330387 } + { 18 19 20 { Icosphere } } + } + { + { 0.802609 -0.583126 -0.125627 } + { 3 8 21 { Icosphere } } + } + { + { -0.306569 -0.943522 -0.125629 } + { 9 11 22 { Icosphere } } + } + { + { -0.992077 0 -0.125628 } + { 12 14 23 { Icosphere } } + } + { + { -0.306569 0.943522 -0.125629 } + { 15 17 24 { Icosphere } } + } + { + { 0.802609 0.583126 -0.125627 } + { 18 20 25 { Icosphere } } + } + { + { 0.408946 -0.628425 0.661698 } + { 26 27 28 { Icosphere } } + } + { + { -0.4713 -0.583122 0.661699 } + { 29 30 31 { Icosphere } } + } + { + { -0.700224 0.268032 0.661699 } + { 32 33 34 { Icosphere } } + } + { + { 0.03853 0.748779 0.661699 } + { 35 36 37 { Icosphere } } + } + { + { 0.724042 0.194736 0.661695 } + { 38 39 40 { Icosphere } } + } + { + { 0.268034 0.194737 0.943523 } + { 40 37 41 { Icosphere } } + } + { + { 0.491119 0.356821 0.794657 } + { 40 39 37 { Icosphere } } + } + { + { 0.408946 0.628425 0.661699 } + { 39 35 37 { Icosphere } } + } + { + { -0.102381 0.31509 0.943523 } + { 37 34 41 { Icosphere } } + } + { + { -0.187594 0.577345 0.794658 } + { 37 36 34 { Icosphere } } + } + { + { -0.4713 0.583122 0.661699 } + { 36 32 34 { Icosphere } } + } + { + { -0.331305 0 0.943524 } + { 34 31 41 { Icosphere } } + } + { + { -0.60706 0 0.794656 } + { 34 33 31 { Icosphere } } + } + { + { -0.700224 -0.268032 0.661699 } + { 33 29 31 { Icosphere } } + } + { + { -0.102381 -0.31509 0.943523 } + { 31 28 41 { Icosphere } } + } + { + { -0.187594 -0.577345 0.794658 } + { 31 30 28 { Icosphere } } + } + { + { 0.03853 -0.748779 0.661699 } + { 30 26 28 { Icosphere } } + } + { + { 0.268034 -0.194737 0.943523 } + { 28 40 41 { Icosphere } } + } + { + { 0.491119 -0.356821 0.794657 } + { 28 27 40 { Icosphere } } + } + { + { 0.724042 -0.194736 0.661695 } + { 27 38 40 { Icosphere } } + } + { + { 0.889697 0.315095 0.330385 } + { 25 39 38 { Icosphere } } + } + { + { 0.794656 0.577348 0.187595 } + { 25 20 39 { Icosphere } } + } + { + { 0.574602 0.748784 0.330388 } + { 20 35 39 { Icosphere } } + } + { + { -0.024747 0.943521 0.330386 } + { 24 36 35 { Icosphere } } + } + { + { -0.303531 0.934171 0.187597 } + { 24 17 36 { Icosphere } } + } + { + { -0.534576 0.777865 0.330387 } + { 17 32 36 { Icosphere } } + } + { + { -0.904989 0.268032 0.330385 } + { 23 33 32 { Icosphere } } + } + { + { -0.982246 0 0.187599 } + { 23 14 33 { Icosphere } } + } + { + { -0.904989 -0.268031 0.330385 } + { 14 29 33 { Icosphere } } + } + { + { -0.534576 -0.777865 0.330387 } + { 22 30 29 { Icosphere } } + } + { + { -0.303531 -0.934171 0.187597 } + { 22 11 30 { Icosphere } } + } + { + { -0.024747 -0.943521 0.330386 } + { 11 26 30 { Icosphere } } + } + { + { 0.574602 -0.748784 0.330388 } + { 21 27 26 { Icosphere } } + } + { + { 0.794656 -0.577348 0.187595 } + { 21 8 27 { Icosphere } } + } + { + { 0.889697 -0.315095 0.330385 } + { 8 38 27 { Icosphere } } + } + { + { 0.306569 0.943522 0.125629 } + { 20 24 35 { Icosphere } } + } + { + { 0.303531 0.934171 -0.187597 } + { 20 19 24 { Icosphere } } + } + { + { 0.024747 0.943521 -0.330386 } + { 19 15 24 { Icosphere } } + } + { + { -0.802609 0.583126 0.125627 } + { 17 23 32 { Icosphere } } + } + { + { -0.794656 0.577348 -0.187595 } + { 17 16 23 { Icosphere } } + } + { + { -0.889697 0.315095 -0.330385 } + { 16 12 23 { Icosphere } } + } + { + { -0.802609 -0.583126 0.125627 } + { 14 22 29 { Icosphere } } + } + { + { -0.794656 -0.577348 -0.187595 } + { 14 13 22 { Icosphere } } + } + { + { -0.574602 -0.748784 -0.330388 } + { 13 9 22 { Icosphere } } + } + { + { 0.306569 -0.943522 0.125629 } + { 11 21 26 { Icosphere } } + } + { + { 0.303531 -0.934171 -0.187597 } + { 11 10 21 { Icosphere } } + } + { + { 0.534576 -0.777865 -0.330387 } + { 10 3 21 { Icosphere } } + } + { + { 0.992077 0 0.125628 } + { 8 25 38 { Icosphere } } + } + { + { 0.982246 0 -0.187599 } + { 8 4 25 { Icosphere } } + } + { + { 0.904989 0.268031 -0.330385 } + { 4 18 25 { Icosphere } } + } + { + { 0.4713 0.583122 -0.661699 } + { 7 19 18 { Icosphere } } + } + { + { 0.187594 0.577345 -0.794658 } + { 7 6 19 { Icosphere } } + } + { + { -0.03853 0.748779 -0.661699 } + { 6 15 19 { Icosphere } } + } + { + { -0.408946 0.628425 -0.661698 } + { 6 16 15 { Icosphere } } + } + { + { -0.491119 0.356821 -0.794657 } + { 6 5 16 { Icosphere } } + } + { + { -0.724042 0.194736 -0.661695 } + { 5 12 16 { Icosphere } } + } + { + { -0.724042 -0.194736 -0.661695 } + { 5 13 12 { Icosphere } } + } + { + { -0.491119 -0.356821 -0.794657 } + { 5 2 13 { Icosphere } } + } + { + { -0.408946 -0.628425 -0.661698 } + { 2 9 13 { Icosphere } } + } + { + { 0.700224 0.268032 -0.661699 } + { 4 7 18 { Icosphere } } + } + { + { 0.60706 0 -0.794656 } + { 4 1 7 { Icosphere } } + } + { + { 0.331305 0 -0.943524 } + { 1 0 7 { Icosphere } } + } + { + { -0.03853 -0.748779 -0.661699 } + { 2 10 9 { Icosphere } } + } + { + { 0.187594 -0.577345 -0.794658 } + { 2 1 10 { Icosphere } } + } + { + { 0.4713 -0.583122 -0.661699 } + { 1 3 10 { Icosphere } } + } +} diff --git a/samples/bump-mapping/models/layingrock-c.jpg b/samples/bump-mapping/models/layingrock-c.jpg new file mode 100644 index 0000000000..f89da981ca Binary files /dev/null and b/samples/bump-mapping/models/layingrock-c.jpg differ diff --git a/samples/bump-mapping/models/layingrock-h.jpg b/samples/bump-mapping/models/layingrock-h.jpg new file mode 100644 index 0000000000..af6598d057 Binary files /dev/null and b/samples/bump-mapping/models/layingrock-h.jpg differ diff --git a/samples/bump-mapping/models/layingrock-n.jpg b/samples/bump-mapping/models/layingrock-n.jpg new file mode 100644 index 0000000000..81ec2b090e Binary files /dev/null and b/samples/bump-mapping/models/layingrock-n.jpg differ diff --git a/samples/carousel/main.py b/samples/carousel/main.py new file mode 100755 index 0000000000..a6ae51fea8 --- /dev/null +++ b/samples/carousel/main.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman, and Eddie Canaan +# Last Updated: 2015-03-13 +# +# This tutorial will demonstrate some uses for intervals in Panda +# to move objects in your panda world. +# Intervals are tools that change a value of something, like position, +# rotation or anything else, linearly, over a set period of time. They can be +# also be combined to work in sequence or in Parallel +# +# In this lesson, we will simulate a carousel in motion using intervals. +# The carousel will spin using an hprInterval while 4 pandas will represent +# the horses on a traditional carousel. The 4 pandas will rotate with the +# carousel and also move up and down on their poles using a LerpFunc interval. +# Finally there will also be lights on the outer edge of the carousel that +# will turn on and off by switching their texture with intervals in Sequence +# and Parallel + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import AmbientLight, DirectionalLight, LightAttrib +from panda3d.core import NodePath +from panda3d.core import LVector3 +from direct.interval.IntervalGlobal import * # Needed to use Intervals +from direct.gui.DirectGui import * + +# Importing math constants and functions +from math import pi, sin + + +class CarouselDemo(ShowBase): + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # This creates the on screen title that is in every tutorial + self.title = OnscreenText(text="Panda3D: Tutorial - Carousel", + parent=base.a2dBottomCenter, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5), + pos=(0, .1), scale=.1) + + base.disableMouse() # Allow manual positioning of the camera + camera.setPosHpr(0, -8, 2.5, 0, -9, 0) # Set the cameras' position + # and orientation + + self.loadModels() # Load and position our models + self.setupLights() # Add some basic lighting + self.startCarousel() # Create the needed intervals and put the + # carousel into motion + + def loadModels(self): + # Load the carousel base + self.carousel = loader.loadModel("models/carousel_base") + self.carousel.reparentTo(render) # Attach it to render + + # Load the modeled lights that are on the outer rim of the carousel + # (not Panda lights) + # There are 2 groups of lights. At any given time, one group will have + # the "on" texture and the other will have the "off" texture. + self.lights1 = loader.loadModel("models/carousel_lights") + self.lights1.reparentTo(self.carousel) + + # Load the 2nd set of lights + self.lights2 = loader.loadModel("models/carousel_lights") + # We need to rotate the 2nd so it doesn't overlap with the 1st set. + self.lights2.setH(36) + self.lights2.reparentTo(self.carousel) + + # Load the textures for the lights. One texture is for the "on" state, + # the other is for the "off" state. + self.lightOffTex = loader.loadTexture("models/carousel_lights_off.jpg") + self.lightOnTex = loader.loadTexture("models/carousel_lights_on.jpg") + + # Create an list (self.pandas) with filled with 4 dummy nodes attached + # to the carousel. + # This uses a python concept called "Array Comprehensions." Check the + # Python manual for more information on how they work + self.pandas = [self.carousel.attachNewNode("panda" + str(i)) + for i in range(4)] + self.models = [loader.loadModel("models/carousel_panda") + for i in range(4)] + self.moves = [0] * 4 + + for i in range(4): + # set the position and orientation of the ith panda node we just created + # The Z value of the position will be the base height of the pandas. + # The headings are multiplied by i to put each panda in its own position + # around the carousel + self.pandas[i].setPosHpr(0, 0, 1.3, i * 90, 0, 0) + + # Load the actual panda model, and parent it to its dummy node + self.models[i].reparentTo(self.pandas[i]) + # Set the distance from the center. This distance is based on the way the + # carousel was modeled in Maya + self.models[i].setY(.85) + + # Load the environment (Sky sphere and ground plane) + self.env = loader.loadModel("models/env") + self.env.reparentTo(render) + self.env.setScale(7) + + # Panda Lighting + def setupLights(self): + # Create some lights and add them to the scene. By setting the lights on + # render they affect the entire scene + # Check out the lighting tutorial for more information on lights + ambientLight = AmbientLight("ambientLight") + ambientLight.setColor((.4, .4, .35, 1)) + directionalLight = DirectionalLight("directionalLight") + directionalLight.setDirection(LVector3(0, 8, -2.5)) + directionalLight.setColor((0.9, 0.8, 0.9, 1)) + render.setLight(render.attachNewNode(directionalLight)) + render.setLight(render.attachNewNode(ambientLight)) + + # Explicitly set the environment to not be lit + self.env.setLightOff() + + def startCarousel(self): + # Here's where we actually create the intervals to move the carousel + # The first type of interval we use is one created directly from a NodePath + # This interval tells the NodePath to vary its orientation (hpr) from its + # current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from + # NodePaths also exist for position, scale, color, and shear + + self.carouselSpin = self.carousel.hprInterval(20, LVector3(360, 0, 0)) + # Once an interval is created, we need to tell it to actually move. + # start() will cause an interval to play once. loop() will tell an interval + # to repeat once it finished. To keep the carousel turning, we use + # loop() + self.carouselSpin.loop() + + # The next type of interval we use is called a LerpFunc interval. It is + # called that becuase it linearly interpolates (aka Lerp) values passed to + # a function over a given amount of time. + + # In this specific case, horses on a carousel don't move contantly up, + # suddenly stop, and then contantly move down again. Instead, they start + # slowly, get fast in the middle, and slow down at the top. This motion is + # close to a sine wave. This LerpFunc calls the function oscillatePanda + # (which we will create below), which changes the height of the panda based + # on the sin of the value passed in. In this way we achieve non-linear + # motion by linearly changing the input to a function + for i in range(4): + self.moves[i] = LerpFunc( + self.oscillatePanda, # function to call + duration=3, # 3 second duration + fromData=0, # starting value (in radians) + toData=2 * pi, # ending value (2pi radians = 360 degrees) + # Additional information to pass to + # self.oscialtePanda + extraArgs=[self.models[i], pi * (i % 2)] + ) + # again, we want these to play continuously so we start them with + # loop() + self.moves[i].loop() + + # Finally, we combine Sequence, Parallel, Func, and Wait intervals, + # to schedule texture swapping on the lights to simulate the lights turning + # on and off. + # Sequence intervals play other intervals in a sequence. In other words, + # it waits for the current interval to finish before playing the next + # one. + # Parallel intervals play a group of intervals at the same time + # Wait intervals simply do nothing for a given amount of time + # Func intervals simply make a single function call. This is helpful because + # it allows us to schedule functions to be called in a larger sequence. They + # take virtually no time so they don't cause a Sequence to wait. + + self.lightBlink = Sequence( + # For the first step in our sequence we will set the on texture on one + # light and set the off texture on the other light at the same time + Parallel( + Func(self.lights1.setTexture, self.lightOnTex, 1), + Func(self.lights2.setTexture, self.lightOffTex, 1)), + Wait(1), # Then we will wait 1 second + # Then we will switch the textures at the same time + Parallel( + Func(self.lights1.setTexture, self.lightOffTex, 1), + Func(self.lights2.setTexture, self.lightOnTex, 1)), + Wait(1) # Then we will wait another second + ) + + self.lightBlink.loop() # Loop this sequence continuously + + def oscillatePanda(self, rad, panda, offset): + # This is the oscillation function mentioned earlier. It takes in a + # degree value, a NodePath to set the height on, and an offset. The + # offset is there so that the different pandas can move opposite to + # each other. The .2 is the amplitude, so the height of the panda will + # vary from -.2 to .2 + panda.setZ(sin(rad + offset) * .2) + +demo = CarouselDemo() +demo.run() diff --git a/samples/carousel/models/carousel_base.egg.pz b/samples/carousel/models/carousel_base.egg.pz new file mode 100644 index 0000000000..4f4de1f11b Binary files /dev/null and b/samples/carousel/models/carousel_base.egg.pz differ diff --git a/samples/carousel/models/carousel_base.jpg b/samples/carousel/models/carousel_base.jpg new file mode 100644 index 0000000000..3e1298a55f Binary files /dev/null and b/samples/carousel/models/carousel_base.jpg differ diff --git a/samples/carousel/models/carousel_lights.egg.pz b/samples/carousel/models/carousel_lights.egg.pz new file mode 100644 index 0000000000..6b712ce708 Binary files /dev/null and b/samples/carousel/models/carousel_lights.egg.pz differ diff --git a/samples/carousel/models/carousel_lights_off.jpg b/samples/carousel/models/carousel_lights_off.jpg new file mode 100644 index 0000000000..4d528ce747 Binary files /dev/null and b/samples/carousel/models/carousel_lights_off.jpg differ diff --git a/samples/carousel/models/carousel_lights_on.jpg b/samples/carousel/models/carousel_lights_on.jpg new file mode 100644 index 0000000000..5688bca94d Binary files /dev/null and b/samples/carousel/models/carousel_lights_on.jpg differ diff --git a/samples/carousel/models/carousel_panda.egg.pz b/samples/carousel/models/carousel_panda.egg.pz new file mode 100644 index 0000000000..a0f74c621f Binary files /dev/null and b/samples/carousel/models/carousel_panda.egg.pz differ diff --git a/samples/carousel/models/carousel_panda.jpg b/samples/carousel/models/carousel_panda.jpg new file mode 100644 index 0000000000..efe0c85981 Binary files /dev/null and b/samples/carousel/models/carousel_panda.jpg differ diff --git a/samples/carousel/models/env.egg.pz b/samples/carousel/models/env.egg.pz new file mode 100644 index 0000000000..abce5df2e9 Binary files /dev/null and b/samples/carousel/models/env.egg.pz differ diff --git a/samples/carousel/models/env_ground.jpg b/samples/carousel/models/env_ground.jpg new file mode 100644 index 0000000000..d563a603c2 Binary files /dev/null and b/samples/carousel/models/env_ground.jpg differ diff --git a/samples/carousel/models/env_sky.jpg b/samples/carousel/models/env_sky.jpg new file mode 100644 index 0000000000..b339776222 Binary files /dev/null and b/samples/carousel/models/env_sky.jpg differ diff --git a/samples/cartoon-shader/advanced.py b/samples/cartoon-shader/advanced.py new file mode 100755 index 0000000000..49f6a2adca --- /dev/null +++ b/samples/cartoon-shader/advanced.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +# Author: Kwasi Mensah +# Date: 7/11/2005 +# +# This is a tutorial to show some of the more advanced things +# you can do with Cg. Specifically, with Non Photo Realistic +# effects like Toon Shading. It also shows how to implement +# multiple buffers in Panda. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import PandaNode, LightNode, TextNode +from panda3d.core import Filename +from panda3d.core import NodePath +from panda3d.core import Shader +from panda3d.core import LVecBase4 +from direct.task.Task import Task +from direct.actor.Actor import Actor +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.showbase.BufferViewer import BufferViewer +import sys +import os + + +# Function to put instructions on the screen. +def addInstructions(pos, msg): + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.05) + +# Function to put title on the screen. +def addTitle(text): + return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + + +class ToonMaker(ShowBase): + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + self.disableMouse() + camera.setPos(0, -50, 0) + + # Check video card capabilities. + if not self.win.getGsg().getSupportsBasicShaders(): + addTitle("Toon Shader: Video driver reports that Cg shaders are not supported.") + return + + # Show instructions in the corner of the window. + self.title = addTitle( + "Panda3D: Tutorial - Toon Shading with Normals-Based Inking") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Up/Down: Increase/Decrease Line Thickness") + self.inst3 = addInstructions(0.18, "Left/Right: Decrease/Increase Line Darkness") + self.inst4 = addInstructions(0.24, "V: View the render-to-texture results") + + # This shader's job is to render the model with discrete lighting + # levels. The lighting calculations built into the shader assume + # a single nonattenuating point light. + + tempnode = NodePath(PandaNode("temp node")) + tempnode.setShader(loader.loadShader("lightingGen.sha")) + self.cam.node().setInitialState(tempnode.getState()) + + # This is the object that represents the single "light", as far + # the shader is concerned. It's not a real Panda3D LightNode, but + # the shader doesn't care about that. + + light = render.attachNewNode("light") + light.setPos(30, -50, 0) + + # this call puts the light's nodepath into the render state. + # this enables the shader to access this light by name. + + render.setShaderInput("light", light) + + # The "normals buffer" will contain a picture of the model colorized + # so that the color of the model is a representation of the model's + # normal at that point. + + normalsBuffer = self.win.makeTextureBuffer("normalsBuffer", 0, 0) + normalsBuffer.setClearColor(LVecBase4(0.5, 0.5, 0.5, 1)) + self.normalsBuffer = normalsBuffer + normalsCamera = self.makeCamera( + normalsBuffer, lens=self.cam.node().getLens()) + normalsCamera.node().setScene(render) + tempnode = NodePath(PandaNode("temp node")) + tempnode.setShader(loader.loadShader("normalGen.sha")) + normalsCamera.node().setInitialState(tempnode.getState()) + + # what we actually do to put edges on screen is apply them as a texture to + # a transparent screen-fitted card + + drawnScene = normalsBuffer.getTextureCard() + drawnScene.setTransparency(1) + drawnScene.setColor(1, 1, 1, 0) + drawnScene.reparentTo(render2d) + self.drawnScene = drawnScene + + # this shader accepts, as input, the picture from the normals buffer. + # it compares each adjacent pixel, looking for discontinuities. + # wherever a discontinuity exists, it emits black ink. + + self.separation = 0.001 + self.cutoff = 0.3 + inkGen = loader.loadShader("inkGen.sha") + drawnScene.setShader(inkGen) + drawnScene.setShaderInput("separation", LVecBase4(self.separation, 0, self.separation, 0)) + drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff)) + + # Panda contains a built-in viewer that lets you view the results of + # your render-to-texture operations. This code configures the viewer. + + self.accept("v", self.bufferViewer.toggleEnable) + self.accept("V", self.bufferViewer.toggleEnable) + self.bufferViewer.setPosition("llcorner") + + # Load a dragon model and start its animation. + self.character = Actor() + self.character.loadModel('models/nik-dragon') + self.character.reparentTo(render) + self.character.loop('win') + self.character.hprInterval(15, (360, 0, 0)).loop() + + # These allow you to change cartooning parameters in realtime + self.accept("escape", sys.exit, [0]) + self.accept("arrow_up", self.increaseSeparation) + self.accept("arrow_down", self.decreaseSeparation) + self.accept("arrow_left", self.increaseCutoff) + self.accept("arrow_right", self.decreaseCutoff) + + def increaseSeparation(self): + self.separation = self.separation * 1.11111111 + print("separation: %f" % (self.separation)) + self.drawnScene.setShaderInput( + "separation", LVecBase4(self.separation, 0, self.separation, 0)) + + def decreaseSeparation(self): + self.separation = self.separation * 0.90000000 + print("separation: %f" % (self.separation)) + self.drawnScene.setShaderInput( + "separation", LVecBase4(self.separation, 0, self.separation, 0)) + + def increaseCutoff(self): + self.cutoff = self.cutoff * 1.11111111 + print("cutoff: %f" % (self.cutoff)) + self.drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff)) + + def decreaseCutoff(self): + self.cutoff = self.cutoff * 0.90000000 + print("cutoff: %f" % (self.cutoff)) + self.drawnScene.setShaderInput("cutoff", LVecBase4(self.cutoff)) + +t = ToonMaker() +t.run() diff --git a/samples/cartoon-shader/basic.py b/samples/cartoon-shader/basic.py new file mode 100755 index 0000000000..2861fc9300 --- /dev/null +++ b/samples/cartoon-shader/basic.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import PandaNode, LightNode, TextNode +from panda3d.core import Filename, NodePath +from panda3d.core import PointLight, AmbientLight +from panda3d.core import LightRampAttrib, AuxBitplaneAttrib +from panda3d.core import CardMaker +from panda3d.core import Shader, Texture +from direct.task.Task import Task +from direct.actor.Actor import Actor +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.showbase.BufferViewer import BufferViewer +from direct.filter.CommonFilters import CommonFilters +import sys +import os + + +# Function to put instructions on the screen. +def addInstructions(pos, msg): + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.05) + +# Function to put title on the screen. +def addTitle(text): + return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + + +class ToonMaker(ShowBase): + + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + self.disableMouse() + self.cam.node().getLens().setNear(10.0) + self.cam.node().getLens().setFar(200.0) + camera.setPos(0, -50, 0) + + # Check video card capabilities. + if not self.win.getGsg().getSupportsBasicShaders(): + addTitle("Toon Shader: Video driver reports that Cg shaders are not supported.") + return + + # Enable a 'light ramp' - this discretizes the lighting, + # which is half of what makes a model look like a cartoon. + # Light ramps only work if shader generation is enabled, + # so we call 'setShaderAuto'. + + tempnode = NodePath(PandaNode("temp node")) + tempnode.setAttrib(LightRampAttrib.makeSingleThreshold(0.5, 0.4)) + tempnode.setShaderAuto() + self.cam.node().setInitialState(tempnode.getState()) + + # Use class 'CommonFilters' to enable a cartoon inking filter. + # This can fail if the video card is not powerful enough, if so, + # display an error and exit. + + self.separation = 1 # Pixels + self.filters = CommonFilters(self.win, self.cam) + filterok = self.filters.setCartoonInk(separation=self.separation) + if (filterok == False): + addTitle( + "Toon Shader: Video card not powerful enough to do image postprocessing") + return + + # Show instructions in the corner of the window. + self.title = addTitle( + "Panda3D: Tutorial - Toon Shading with Normals-Based Inking") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Up/Down: Increase/Decrease Line Thickness") + self.inst3 = addInstructions(0.18, "V: View the render-to-texture results") + + # Load a dragon model and animate it. + self.character = Actor() + self.character.loadModel('models/nik-dragon') + self.character.reparentTo(render) + self.character.loadAnims({'win': 'models/nik-dragon'}) + self.character.loop('win') + self.character.hprInterval(15, (360, 0, 0)).loop() + + # Create a non-attenuating point light and an ambient light. + plightnode = PointLight("point light") + plightnode.setAttenuation((1, 0, 0)) + plight = render.attachNewNode(plightnode) + plight.setPos(30, -50, 0) + alightnode = AmbientLight("ambient light") + alightnode.setColor((0.8, 0.8, 0.8, 1)) + alight = render.attachNewNode(alightnode) + render.setLight(alight) + render.setLight(plight) + + # Panda contains a built-in viewer that lets you view the + # results of all render-to-texture operations. This lets you + # see what class CommonFilters is doing behind the scenes. + self.accept("v", self.bufferViewer.toggleEnable) + self.accept("V", self.bufferViewer.toggleEnable) + self.bufferViewer.setPosition("llcorner") + self.accept("s", self.filters.manager.resizeBuffers) + + # These allow you to change cartooning parameters in realtime + self.accept("escape", sys.exit, [0]) + self.accept("arrow_up", self.increaseSeparation) + self.accept("arrow_down", self.decreaseSeparation) + + def increaseSeparation(self): + self.separation = self.separation * 1.11111111 + print("separation: %f" % (self.separation)) + self.filters.setCartoonInk(separation=self.separation) + + def decreaseSeparation(self): + self.separation = self.separation * 0.90000000 + print("separation: %f" % (self.separation)) + self.filters.setCartoonInk(separation=self.separation) + +t = ToonMaker() +t.run() diff --git a/samples/cartoon-shader/inkGen.sha b/samples/cartoon-shader/inkGen.sha new file mode 100644 index 0000000000..930bc00764 --- /dev/null +++ b/samples/cartoon-shader/inkGen.sha @@ -0,0 +1,35 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float4 vtx_texcoord0 : TEXCOORD0, + out float4 l_position : POSITION, + out float4 l_texcoord0 : TEXCOORD0, + uniform float4x4 mat_modelproj) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord0 = vtx_texcoord0; +} + +void fshader(float4 l_texcoord0 : TEXCOORD0, + uniform sampler2D tex_0 : TEXUNIT0, + uniform float4 k_cutoff : C6, + uniform float4 k_separation : C7, + out float4 o_color : COLOR) +{ + float4 texcoord0 = l_texcoord0 + k_separation.xyzw; + float4 color0=tex2D(tex_0, float2(texcoord0.x, texcoord0.y)); + float4 texcoord1 = l_texcoord0 - k_separation.xyzw; + float4 color1=tex2D(tex_0, float2(texcoord1.x, texcoord1.y)); + float4 texcoord2 = l_texcoord0 + k_separation.wzyx; + float4 color2=tex2D(tex_0, float2(texcoord2.x, texcoord2.y)); + float4 texcoord3 = l_texcoord0 - k_separation.wzyx; + float4 color3=tex2D(tex_0, float2(texcoord3.x, texcoord3.y)); + float4 mx = max(color0,max(color1,max(color2,color3))); + float4 mn = min(color0,min(color1,min(color2,color3))); + float4 trigger = saturate(((mx-mn) * 3) - k_cutoff.x); + float thresh = dot(float3(trigger.x, trigger.y, trigger.z),float3(1,1,1)); + float4 output_color = float4 (0, 0, 0, thresh); + o_color = output_color; +} diff --git a/samples/cartoon-shader/lightingGen.sha b/samples/cartoon-shader/lightingGen.sha new file mode 100644 index 0000000000..c7cac59943 --- /dev/null +++ b/samples/cartoon-shader/lightingGen.sha @@ -0,0 +1,29 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float3 vtx_normal : NORMAL, + float4 vtx_color : COLOR, + out float4 l_position : POSITION, + out float4 l_brite : TEXCOORD0, + out float4 l_color : COLOR, + uniform float4 mspos_light, + uniform float4x4 mat_modelproj) +{ + l_position = mul(mat_modelproj, vtx_position); + float3 N = normalize(vtx_normal); + float3 lightVector = normalize(mspos_light - vtx_position); + l_brite = max(dot(N,lightVector), 0); + l_color = vtx_color; +} + + +void fshader(float4 l_brite : TEXCOORD0, + float4 l_color : COLOR, + out float4 o_color : COLOR) +{ + if (l_brite.x<0.5) l_brite=0.8; + else l_brite=1.2; + o_color=l_brite * l_color; +} diff --git a/samples/cartoon-shader/models/nik-dragon.egg.pz b/samples/cartoon-shader/models/nik-dragon.egg.pz new file mode 100644 index 0000000000..7c089ca705 Binary files /dev/null and b/samples/cartoon-shader/models/nik-dragon.egg.pz differ diff --git a/samples/cartoon-shader/normalGen.sha b/samples/cartoon-shader/normalGen.sha new file mode 100644 index 0000000000..a3642b0630 --- /dev/null +++ b/samples/cartoon-shader/normalGen.sha @@ -0,0 +1,25 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float4 vtx_normal : NORMAL, + out float4 l_position : POSITION, + out float3 l_color : TEXCOORD0, + uniform float4x4 mat_modelproj, + uniform float4x4 itp_modelview) +{ + l_position=mul(mat_modelproj, vtx_position); + l_color=(float3)mul(itp_modelview, vtx_normal); +} + +void fshader(float3 l_color: TEXCOORD0, + out float4 o_color: COLOR) +{ + l_color = normalize(l_color); + l_color = l_color/2; + o_color.rgb = l_color + float4(0.5, 0.5, 0.5, 0.5); + o_color.a = 1; +} + + diff --git a/samples/chessboard/main.py b/samples/chessboard/main.py new file mode 100755 index 0000000000..38b4664c41 --- /dev/null +++ b/samples/chessboard/main.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Models: Eddie Canaan +# Last Updated: 2015-03-13 +# +# This tutorial shows how to determine what objects the mouse is pointing to +# We do this using a collision ray that extends from the mouse position +# and points straight into the scene, and see what it collides with. We pick +# the object with the closest collision + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import CollisionTraverser, CollisionNode +from panda3d.core import CollisionHandlerQueue, CollisionRay +from panda3d.core import AmbientLight, DirectionalLight, LightAttrib +from panda3d.core import TextNode +from panda3d.core import LPoint3, LVector3, BitMask32 +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.task.Task import Task +import sys + +# First we define some contants for the colors +BLACK = (0, 0, 0, 1) +WHITE = (1, 1, 1, 1) +HIGHLIGHT = (0, 1, 1, 1) +PIECEBLACK = (.15, .15, .15, 1) + +# Now we define some helper functions that we will need later + +# This function, given a line (vector plus origin point) and a desired z value, +# will give us the point on the line where the desired z value is what we want. +# This is how we know where to position an object in 3D space based on a 2D mouse +# position. It also assumes that we are dragging in the XY plane. +# +# This is derived from the mathmatical of a plane, solved for a given point +def PointAtZ(z, point, vec): + return point + vec * ((z - point.getZ()) / vec.getZ()) + +# A handy little function for getting the proper position for a given square1 +def SquarePos(i): + return LPoint3((i % 8) - 3.5, int(i / 8) - 3.5, 0) + +# Helper function for determining wheter a square should be white or black +# The modulo operations (%) generate the every-other pattern of a chess-board +def SquareColor(i): + if (i + ((i / 8) % 2)) % 2: + return BLACK + else: + return WHITE + + +class ChessboardDemo(ShowBase): + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + + # This code puts the standard title and instruction text on screen + self.title = OnscreenText(text="Panda3D: Tutorial - Mouse Picking", + style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1), + pos=(0.8, -0.95), scale = .07) + self.escapeEvent = OnscreenText( + text="ESC: Quit", parent=base.a2dTopLeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.1), + align=TextNode.ALeft, scale = .05) + self.mouse1Event = OnscreenText( + text="Left-click and drag: Pick up and drag piece", + parent=base.a2dTopLeft, align=TextNode.ALeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16), scale=.05) + + self.accept('escape', sys.exit) # Escape quits + self.disableMouse() # Disble mouse camera control + camera.setPosHpr(0, -12, 8, 0, -35, 0) # Set the camera + self.setupLights() # Setup default lighting + + # Since we are using collision detection to do picking, we set it up like + # any other collision detection system with a traverser and a handler + self.picker = CollisionTraverser() # Make a traverser + self.pq = CollisionHandlerQueue() # Make a handler + # Make a collision node for our picker ray + self.pickerNode = CollisionNode('mouseRay') + # Attach that node to the camera since the ray will need to be positioned + # relative to it + self.pickerNP = camera.attachNewNode(self.pickerNode) + # Everything to be picked will use bit 1. This way if we were doing other + # collision we could seperate it + self.pickerNode.setFromCollideMask(BitMask32.bit(1)) + self.pickerRay = CollisionRay() # Make our ray + # Add it to the collision node + self.pickerNode.addSolid(self.pickerRay) + # Register the ray as something that can cause collisions + self.picker.addCollider(self.pickerNP, self.pq) + # self.picker.showCollisions(render) + + # Now we create the chess board and its pieces + + # We will attach all of the squares to their own root. This way we can do the + # collision pass just on the sqaures and save the time of checking the rest + # of the scene + self.squareRoot = render.attachNewNode("squareRoot") + + # For each square + self.squares = [None for i in range(64)] + self.pieces = [None for i in range(64)] + for i in range(64): + # Load, parent, color, and position the model (a single square + # polygon) + self.squares[i] = loader.loadModel("models/square") + self.squares[i].reparentTo(self.squareRoot) + self.squares[i].setPos(SquarePos(i)) + self.squares[i].setColor(SquareColor(i)) + # Set the model itself to be collideable with the ray. If this model was + # any more complex than a single polygon, you should set up a collision + # sphere around it instead. But for single polygons this works + # fine. + self.squares[i].find("**/polygon").node().setIntoCollideMask( + BitMask32.bit(1)) + # Set a tag on the square's node so we can look up what square this is + # later during the collision pass + self.squares[i].find("**/polygon").node().setTag('square', str(i)) + + # We will use this variable as a pointer to whatever piece is currently + # in this square + + # The order of pieces on a chessboard from white's perspective. This list + # contains the constructor functions for the piece classes defined + # below + pieceOrder = (Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook) + + for i in range(8, 16): + # Load the white pawns + self.pieces[i] = Pawn(i, WHITE) + for i in range(48, 56): + # load the black pawns + self.pieces[i] = Pawn(i, PIECEBLACK) + for i in range(8): + # Load the special pieces for the front row and color them white + self.pieces[i] = pieceOrder[i](i, WHITE) + # Load the special pieces for the back row and color them black + self.pieces[i + 56] = pieceOrder[i](i + 56, PIECEBLACK) + + # This will represent the index of the currently highlited square + self.hiSq = False + # This wil represent the index of the square where currently dragged piece + # was grabbed from + self.dragging = False + + # Start the task that handles the picking + self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask') + self.accept("mouse1", self.grabPiece) # left-click grabs a piece + self.accept("mouse1-up", self.releasePiece) # releasing places it + + # This function swaps the positions of two pieces + def swapPieces(self, fr, to): + temp = self.pieces[fr] + self.pieces[fr] = self.pieces[to] + self.pieces[to] = temp + if self.pieces[fr]: + self.pieces[fr].square = fr + self.pieces[fr].obj.setPos(SquarePos(fr)) + if self.pieces[to]: + self.pieces[to].square = to + self.pieces[to].obj.setPos(SquarePos(to)) + + def mouseTask(self, task): + # This task deals with the highlighting and dragging based on the mouse + + # First, clear the current highlight + if self.hiSq is not False: + self.squares[self.hiSq].setColor(SquareColor(self.hiSq)) + self.hiSq = False + + # Check to see if we can access the mouse. We need it to do anything + # else + if self.mouseWatcherNode.hasMouse(): + # get the mouse position + mpos = self.mouseWatcherNode.getMouse() + + # Set the position of the ray based on the mouse position + self.pickerRay.setFromLens(self.camNode, mpos.getX(), mpos.getY()) + + # If we are dragging something, set the position of the object + # to be at the appropriate point over the plane of the board + if self.dragging is not False: + # Gets the point described by pickerRay.getOrigin(), which is relative to + # camera, relative instead to render + nearPoint = render.getRelativePoint( + camera, self.pickerRay.getOrigin()) + # Same thing with the direction of the ray + nearVec = render.getRelativeVector( + camera, self.pickerRay.getDirection()) + self.pieces[self.dragging].obj.setPos( + PointAtZ(.5, nearPoint, nearVec)) + + # Do the actual collision pass (Do it only on the squares for + # efficiency purposes) + self.picker.traverse(self.squareRoot) + if self.pq.getNumEntries() > 0: + # if we have hit something, sort the hits so that the closest + # is first, and highlight that node + self.pq.sortEntries() + i = int(self.pq.getEntry(0).getIntoNode().getTag('square')) + # Set the highlight on the picked square + self.squares[i].setColor(HIGHLIGHT) + self.hiSq = i + + return Task.cont + + def grabPiece(self): + # If a square is highlighted and it has a piece, set it to dragging + # mode + if self.hiSq is not False and self.pieces[self.hiSq]: + self.dragging = self.hiSq + self.hiSq = False + + def releasePiece(self): + # Letting go of a piece. If we are not on a square, return it to its original + # position. Otherwise, swap it with the piece in the new square + # Make sure we really are dragging something + if self.dragging is not False: + # We have let go of the piece, but we are not on a square + if self.hiSq is False: + self.pieces[self.dragging].obj.setPos( + SquarePos(self.dragging)) + else: + # Otherwise, swap the pieces + self.swapPieces(self.dragging, self.hiSq) + + # We are no longer dragging anything + self.dragging = False + + def setupLights(self): # This function sets up some default lighting + ambientLight = AmbientLight("ambientLight") + ambientLight.setColor((.8, .8, .8, 1)) + directionalLight = DirectionalLight("directionalLight") + directionalLight.setDirection(LVector3(0, 45, -45)) + directionalLight.setColor((0.2, 0.2, 0.2, 1)) + render.setLight(render.attachNewNode(directionalLight)) + render.setLight(render.attachNewNode(ambientLight)) + + +# Class for a piece. This just handels loading the model and setting initial +# position and color +class Piece(object): + def __init__(self, square, color): + self.obj = loader.loadModel(self.model) + self.obj.reparentTo(render) + self.obj.setColor(color) + self.obj.setPos(SquarePos(square)) + + +# Classes for each type of chess piece +# Obviously, we could have done this by just passing a string to Piece's init. +# But if you wanted to make rules for how the pieces move, a good place to start +# would be to make an isValidMove(toSquare) method for each piece type +# and then check if the destination square is acceptible during ReleasePiece +class Pawn(Piece): + model = "models/pawn" + +class King(Piece): + model = "models/king" + +class Queen(Piece): + model = "models/queen" + +class Bishop(Piece): + model = "models/bishop" + +class Knight(Piece): + model = "models/knight" + +class Rook(Piece): + model = "models/rook" + +# Do the main initialization and start 3D rendering +demo = ChessboardDemo() +demo.run() diff --git a/samples/chessboard/models/bishop.egg.pz b/samples/chessboard/models/bishop.egg.pz new file mode 100644 index 0000000000..40260c2bd1 Binary files /dev/null and b/samples/chessboard/models/bishop.egg.pz differ diff --git a/samples/chessboard/models/king.egg.pz b/samples/chessboard/models/king.egg.pz new file mode 100644 index 0000000000..8ba7a57f39 Binary files /dev/null and b/samples/chessboard/models/king.egg.pz differ diff --git a/samples/chessboard/models/knight.egg.pz b/samples/chessboard/models/knight.egg.pz new file mode 100644 index 0000000000..b7cabaa5eb Binary files /dev/null and b/samples/chessboard/models/knight.egg.pz differ diff --git a/samples/chessboard/models/pawn.egg.pz b/samples/chessboard/models/pawn.egg.pz new file mode 100644 index 0000000000..42d7e21113 Binary files /dev/null and b/samples/chessboard/models/pawn.egg.pz differ diff --git a/samples/chessboard/models/queen.egg.pz b/samples/chessboard/models/queen.egg.pz new file mode 100644 index 0000000000..0db73ebbf1 Binary files /dev/null and b/samples/chessboard/models/queen.egg.pz differ diff --git a/samples/chessboard/models/rook.egg.pz b/samples/chessboard/models/rook.egg.pz new file mode 100644 index 0000000000..e7886caf42 Binary files /dev/null and b/samples/chessboard/models/rook.egg.pz differ diff --git a/samples/chessboard/models/square.egg.pz b/samples/chessboard/models/square.egg.pz new file mode 100644 index 0000000000..f2e0a15812 Binary files /dev/null and b/samples/chessboard/models/square.egg.pz differ diff --git a/samples/culling/models/cells.egg b/samples/culling/models/cells.egg new file mode 100644 index 0000000000..50560b6041 --- /dev/null +++ b/samples/culling/models/cells.egg @@ -0,0 +1,586 @@ + { Y-Up } + + cell1 { + cell1-ORG { + 0 { + -4 0 -4 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + 11.5789 0 -11.5789 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -11.5789 0 -11.5789 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -4 0 4 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + 4 0 -4 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + 11.5789 0 11.5789 + { 0 1 0 } + { 1 1 1 1 } + } + 6 { + 4 0 4 + { 0 1 0 } + { 1 1 1 1 } + } + 7 { + -11.5789 0 11.5789 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell1-ORG } } + } + { + { 2 3 0 { cell1-ORG } } + } + { + { 4 5 1 { cell1-ORG } } + } + { + { 6 7 5 { cell1-ORG } } + } + { + { 4 1 0 { cell1-ORG } } + } + { + { 7 3 2 { cell1-ORG } } + } + { + { 6 5 4 { cell1-ORG } } + } + { + { 3 7 6 { cell1-ORG } } + } +} + cell2 { + cell2-ORG { + 0 { + -4 0 -0.838305 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + 4 0 0.527741 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + 4 0 -0.838305 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -4 0 0.527741 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell2-ORG } } + } + { + { 3 1 0 { cell2-ORG } } + } +} + cell3 { + cell3-ORG { + 0 { + -1.02178 0 -0.838305 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + 0.243417 0 -1.36315 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -1.02178 0 -1.36315 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -3.36172 0 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + 2.99397 0 -1.36315 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + 2.99397 0 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 6 { + -3.36172 0 -1.36315 + { 0 1 0 } + { 1 1 1 1 } + } + 7 { + 0.243417 0 -0.838305 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell3-ORG } } + } + { + { 1 3 2 { cell3-ORG } } + } + { + { 4 5 1 { cell3-ORG } } + } + { + { 6 2 3 { cell3-ORG } } + } + { + { 7 1 0 { cell3-ORG } } + } + { + { 5 3 1 { cell3-ORG } } + } +} + cell4 { + cell4-ORG { + 0 { + 2.99397 0 0.527741 + { 0 0.854764 -0.519017 } + { 0.999 0.999 0.999 1 } + } + 1 { + 2.04443 1.03879 2.23852 + { 0.158619 0.946967 -0.279453 } + { 1 1 1 1 } + } + 2 { + 2.99397 1.03879 2.23852 + { 0 0.983872 -0.178875 } + { 1 1 1 1 } + } + 3 { + 2.04443 1.03879 3.18072 + { 0.309844 0.950787 0 } + { 1 1 1 1 } + } + 4 { + 2.99397 1.03879 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + 0.519489 2.15079 2.23852 + { 0.320561 0.934229 0.156384 } + { 1 1 1 1 } + } + 6 { + 0.519489 2.15079 3.18072 + { 0.205355 0.978688 0 } + { 1 1 1 1 } + } + 7 { + -0.423812 2.15079 2.23852 + { 0 0.95348 0.301457 } + { 1 1 1 1 } + } + 8 { + -0.423812 2.15079 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 9 { + 0.519489 3.3704 0.502572 + { 0 0.916633 0.399729 } + { 1 1 1 1 } + } + 10 { + -0.423812 3.3704 0.502572 + { 0 0.979823 0.199865 } + { 1 1 1 1 } + } + 11 { + 0.519489 3.3704 0.115602 + { 0 1 0 } + { 1 1 1 1 } + } + 12 { + -0.423812 3.3704 0.115602 + { 0 1 0 } + { 1 1 1 1 } + } + 13 { + 2.04443 0 0.527741 + { 0 0.854764 -0.519017 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell4-ORG } } + } + { + { 2 3 4 { cell4-ORG } } + } + { + { 3 5 6 { cell4-ORG } } + } + { + { 6 7 8 { cell4-ORG } } + } + { + { 7 9 10 { cell4-ORG } } + } + { + { 10 11 12 { cell4-ORG } } + } + { + { 13 1 0 { cell4-ORG } } + } + { + { 1 3 2 { cell4-ORG } } + } + { + { 1 5 3 { cell4-ORG } } + } + { + { 5 7 6 { cell4-ORG } } + } + { + { 5 9 7 { cell4-ORG } } + } + { + { 9 11 10 { cell4-ORG } } + } +} + cell5 { + cell5-ORG { + 0 { + 2.99397 3.3704 0.115602 + { 0 1 0 } + { 1 1 1 1 } + } + 1 { + -3.36172 3.3704 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -3.36172 3.3704 0.115602 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 3 { + 2.99397 3.3704 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell5-ORG } } + } + { + { 3 1 0 { cell5-ORG } } + } +} + cell6 { + cell6-ORG { + 0 { + 2.99397 3.3704 0.115602 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + 0.997627 3.3704 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + 2.99397 3.3704 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + 0.997627 3.3704 0.115602 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell6-ORG } } + } + { + { 3 1 0 { cell6-ORG } } + } +} + cell7 { + cell7-ORG { + 0 { + -3.36172 3.3704 0.115602 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + -0.900963 3.3704 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -0.900963 3.3704 0.115602 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -2.30748 3.3704 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + -2.30748 3.3704 2.05045 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + -3.36172 3.3704 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell7-ORG } } + } + { + { 0 3 4 { cell7-ORG } } + } + { + { 4 1 0 { cell7-ORG } } + } + { + { 5 3 0 { cell7-ORG } } + } +} + cell8 { + cell8-ORG { + 0 { + -2.30748 3.3704 2.23852 + { -0.430354 0.90266 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + 1.82483 5.34053 3.18072 + { -0.430354 0.90266 0 } + { 1 1 1 1 } + } + 2 { + 1.82483 5.34053 2.23852 + { -0.430354 0.90266 0 } + { 1 1 1 1 } + } + 3 { + -2.30748 3.3704 3.18072 + { -0.430354 0.90266 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell8-ORG } } + } + { + { 3 1 0 { cell8-ORG } } + } +} + cell9 { + cell9-ORG { + 0 { + 1.82483 5.34053 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + 1 { + 2.99397 5.34053 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + 1.82483 5.34053 -2.49425 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -0.485624 5.34053 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + -0.485624 5.34053 -2.49425 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 5 { + 2.99397 5.34053 3.18072 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell9-ORG } } + } + { + { 2 3 4 { cell9-ORG } } + } + { + { 5 1 0 { cell9-ORG } } + } + { + { 1 3 2 { cell9-ORG } } + } +} + cell10 { + cell10-ORG { + 0 { + -3.36172 5.34053 -3.42013 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + -0.485624 5.34053 -2.49425 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -0.485624 5.34053 -3.42013 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + -2.36149 5.34053 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + -2.36149 5.34053 -2.49425 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + -3.36172 5.34053 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell10-ORG } } + } + { + { 0 3 4 { cell10-ORG } } + } + { + { 4 1 0 { cell10-ORG } } + } + { + { 5 3 0 { cell10-ORG } } + } +} + cell11 { + cell11-ORG { + 0 { + -2.36149 5.34053 1.0558 + { 0 1 0 } + { 0.999 0.999 0.999 1 } + } + 1 { + -1.90136 5.34053 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + 2 { + -1.90136 5.34053 1.0558 + { 0 1 0 } + { 1 1 1 1 } + } + 3 { + 1.31869 5.34053 0.350091 + { 0 1 0 } + { 1 1 1 1 } + } + 4 { + 1.31869 5.34053 -2.1928 + { 0 1 0 } + { 1 1 1 1 } + } + 5 { + -1.90136 5.34053 -2.16895 + { 0 1 0 } + { 1 1 1 1 } + } + 6 { + 1.31869 5.34053 -0.718624 + { 0 1 0 } + { 1 1 1 1 } + } + 7 { + 1.82483 5.34053 0.350091 + { 0 1 0 } + { 1 1 1 1 } + } + 8 { + -2.36149 5.34053 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + 9 { + 1.31869 5.34053 2.04979 + { 0 1 0 } + { 1 1 1 1 } + } + 10 { + 1.82483 5.34053 -0.718624 + { 0 1 0 } + { 1 1 1 1 } + } + } + { + { 0 1 2 { cell11-ORG } } + } + { + { 1 3 2 { cell11-ORG } } + } + { + { 4 5 6 { cell11-ORG } } + } + { + { 6 2 3 { cell11-ORG } } + } + { + { 7 6 3 { cell11-ORG } } + } + { + { 8 1 0 { cell11-ORG } } + } + { + { 9 3 1 { cell11-ORG } } + } + { + { 5 2 6 { cell11-ORG } } + } + { + { 10 6 7 { cell11-ORG } } + } +} diff --git a/samples/culling/models/level.egg b/samples/culling/models/level.egg new file mode 100644 index 0000000000..873fae9ff1 --- /dev/null +++ b/samples/culling/models/level.egg @@ -0,0 +1,2435 @@ + { Y-Up } + + levelgeo { + levelgeo-ORG { + 0 { + -4 0 -4 + { 0 0 -1 } + { 0.551941 0.559784 0.544098 1 } + } + 1 { + -4 7.57015 -4 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 2 { + 4 7.57015 -4 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 3 { + 4 0 -4 + { 0 0 -1 } + { 0.552941 0.560784 0.545098 1 } + } + 4 { + -4 0 4 + { 0 0 1 } + { 0.552941 0.560784 0.545098 1 } + } + 5 { + 4 0 4 + { 0 0 1 } + { 0.552941 0.560784 0.545098 1 } + } + 6 { + 4 7.57015 4 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 7 { + -4 7.57015 4 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 8 { + -4 0 -4 + { -1 0 0 } + { 0.551941 0.559784 0.544098 1 } + } + 9 { + -4 0 -0.838305 + { -1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 10 { + -4 1.48611 -0.838305 + { -1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 11 { + -4 7.57015 -4 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 12 { + 4 7.57015 -4 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 13 { + 4 1.48611 -0.838305 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 14 { + 4 0 -0.838305 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 15 { + 4 0 -4 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 16 { + -4 7.57015 -4 + { 0 1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 17 { + -4 7.57015 4 + { 0 1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 18 { + 4 7.57015 4 + { 0 1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 19 { + 4 7.57015 -4 + { 0 1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 20 { + -4 0 -4 + { 0 1 0 } + { 0.375471 0.497039 0.001 1 } + } + 21 { + 4 0 -4 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 22 { + 11.5789 0 -11.5789 + { 0 1 0 } + { 0.462745 0.713726 0.192157 1 } + } + 23 { + -11.5789 0 -11.5789 + { 0 1 0 } + { 0.462745 0.713726 0.192157 1 } + } + 24 { + 4 0 -0.838305 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 25 { + -4 0 4 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 26 { + -4 0 0.527741 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 27 { + -11.5789 0 11.5789 + { 0 1 0 } + { 0.462745 0.713726 0.192157 1 } + } + 28 { + 4 0 4 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 29 { + 11.5789 0 11.5789 + { 0 1 0 } + { 0.462745 0.713726 0.192157 1 } + } + 30 { + -4 0 4 + { -1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 31 { + -4 7.57015 4 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 32 { + -4 1.48611 0.527741 + { -1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 33 { + -4 0 0.527741 + { -1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 34 { + 4 0 4 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 35 { + 4 0 0.527741 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 36 { + 4 1.48611 0.527741 + { 1 0 0 } + { 0.552941 0.560784 0.545098 1 } + } + 37 { + 4 7.57015 4 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 38 { + -4 1.48611 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 39 { + -4 0 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 40 { + -1.02178 0 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 41 { + -1.03253 1.48611 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 42 { + -4 0 -0.838305 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 43 { + -4 0 0.527741 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 44 { + -1.02178 0 -0.838305 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 45 { + -4 0 0.527741 + { 0 1.40115e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 46 { + -4 1.48611 0.527741 + { 0 1.40115e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 47 { + 2.04443 1.48611 0.527741 + { 0 1.40115e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 48 { + 2.04443 0 0.527741 + { 0 1.40115e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 49 { + -4 1.48611 0.527741 + { 0 -1 0 } + { 0.729412 0.396078 0.341176 1 } + } + 50 { + -1.03253 1.48611 -0.838305 + { 0 -1 0 } + { 0.729412 0.396078 0.341176 1 } + } + 51 { + 0.243412 1.48611 -0.838305 + { 0 -1 0 } + { 0.729412 0.396078 0.341176 1 } + } + 52 { + 2.04443 1.48611 0.527741 + { 0 -0.983257 0.182222 } + { 0.729412 0.396078 0.341176 1 } + } + 53 { + 2.99397 0 -0.838305 + { 1.47369e-009 -2.57304e-010 1 } + { 0.729412 0.513726 0.341176 1 } + } + 54 { + 2.99397 1.48611 -0.838305 + { 1.47369e-009 -2.57304e-010 1 } + { 0.729412 0.513726 0.341176 1 } + } + 55 { + 0.243412 1.48611 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 56 { + 0.243417 0 -0.838305 + { 0 -5.3759e-011 1 } + { 0.729412 0.513726 0.341176 1 } + } + 57 { + -1.02178 0 -0.838305 + { 0 1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 58 { + 0.243417 0 -0.838305 + { 0 1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 59 { + 0.243417 0 -1.36315 + { 0 1 0 } + { 0.713726 0.721569 0.788235 1 } + } + 60 { + -1.02178 0 -1.36315 + { 0 1 0 } + { 0.713726 0.721569 0.788235 1 } + } + 61 { + 0.243417 0 -0.838305 + { -1 -3.33861e-006 0 } + { 0.458824 0.666667 0.729412 1 } + } + 62 { + 0.243412 1.48611 -0.838305 + { -1 -3.33861e-006 0 } + { 0.458824 0.666667 0.729412 1 } + } + 63 { + 0.243412 1.48611 -1.3609 + { -1 -3.33861e-006 0 } + { 0.458824 0.666667 0.729412 1 } + } + 64 { + 0.243417 0 -1.36315 + { -1 -3.33861e-006 0 } + { 0.713726 0.721569 0.788235 1 } + } + 65 { + 0.243412 1.48611 -0.838305 + { 0 -1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 66 { + -1.03253 1.48611 -0.838305 + { 0 -1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 67 { + -1.03253 1.48611 -1.3609 + { 0 -1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 68 { + 0.243412 1.48611 -1.3609 + { 0 -1 0 } + { 0.458824 0.666667 0.729412 1 } + } + 69 { + -1.03253 1.48611 -0.838305 + { 0.999974 0.00723416 0 } + { 0.458824 0.666667 0.729412 1 } + } + 70 { + -1.02178 0 -0.838305 + { 0.999974 0.00723416 0 } + { 0.458824 0.666667 0.729412 1 } + } + 71 { + -1.02178 0 -1.36315 + { 0.999974 0.00723416 0 } + { 0.713726 0.721569 0.788235 1 } + } + 72 { + -1.03253 1.48611 -1.3609 + { 0.999974 0.00723416 0 } + { 0.458824 0.666667 0.729412 1 } + } + 73 { + 0.243412 1.48611 -1.3609 + { -0.00013873 0.000502693 -1 } + { 0.458824 0.776471 0.796078 1 } + } + 74 { + 2.99397 3.00428 -1.3609 + { -0.00013873 0.000502693 -1 } + { 0.686275 0.890196 0.796078 1 } + } + 75 { + 2.99397 0 -1.36315 + { -0.00027746 0.00100539 -0.999999 } + { 0.458824 0.776471 0.796078 1 } + } + 76 { + 0.243417 0 -1.36315 + { -0.00027746 0.00100539 -0.999999 } + { 0.713726 0.721569 0.788235 1 } + } + 77 { + -1.02178 0 -1.36315 + { 0.000327154 0.00100617 -0.999999 } + { 0.713726 0.721569 0.788235 1 } + } + 78 { + -3.36172 0 -1.36315 + { 0.000327154 0.00100617 -0.999999 } + { 0.458824 0.776471 0.796078 1 } + } + 79 { + -3.36172 3.00428 -1.3609 + { 0.000163577 0.000503084 -1 } + { 0.686275 0.890196 0.796078 1 } + } + 80 { + -1.03253 1.48611 -1.3609 + { 0.000163577 0.000503084 -1 } + { 0.458824 0.776471 0.796078 1 } + } + 81 { + 2.99397 0 -3.42013 + { 0 -1.77621e-006 1 } + { 0.458824 0.776471 0.796078 1 } + } + 82 { + 2.99397 3.00428 -3.42012 + { 0 -1.77621e-006 1 } + { 0.686275 0.890196 0.796078 1 } + } + 83 { + -3.36172 3.00428 -3.42012 + { 0 -1.77621e-006 1 } + { 0.686275 0.890196 0.796078 1 } + } + 84 { + -3.36172 0 -3.42013 + { 0 -1.77621e-006 1 } + { 0.458824 0.776471 0.796078 1 } + } + 85 { + -3.36172 3.00428 -1.3609 + { 0 -1 0 } + { 0.686275 0.890196 0.796078 1 } + } + 86 { + -3.36172 3.00428 -3.42012 + { 0 -1 0 } + { 0.686275 0.890196 0.796078 1 } + } + 87 { + 2.99397 3.00428 -3.42012 + { 0 -1 0 } + { 0.686275 0.890196 0.796078 1 } + } + 88 { + 2.99397 3.00428 -1.3609 + { 0 -1 0 } + { 0.686275 0.890196 0.796078 1 } + } + 89 { + 2.99397 0 -3.42013 + { 0 1 0 } + { 0.505882 0.721569 0.788235 1 } + } + 90 { + -3.36172 0 -3.42013 + { 0 1 0 } + { 0.505882 0.721569 0.788235 1 } + } + 91 { + 0.519489 4.88145 0.507012 + { 0 -0.953298 -0.302032 } + { 0.733333 0.462745 0.294118 1 } + } + 92 { + -0.423826 4.88145 0.507012 + { 0 -0.953298 -0.302032 } + { 0.733333 0.462745 0.294118 1 } + } + 93 { + -0.423826 4.88145 0.120042 + { 0 -1 0 } + { 0.733333 0.462745 0.294118 1 } + } + 94 { + 0.519489 4.88145 0.120042 + { 0 -1 0 } + { 0.733333 0.462745 0.294118 1 } + } + 95 { + 0.519489 3.3704 0.502572 + { -1 3.06469e-012 1.07738e-012 } + { 0.733333 0.654902 0.294118 1 } + } + 96 { + 0.519489 4.88145 0.507012 + { -1 3.06469e-012 1.07738e-012 } + { 0.733333 0.654902 0.294118 1 } + } + 97 { + 0.519489 4.88145 0.120042 + { -1 3.06627e-012 0 } + { 0.733333 0.654902 0.294118 1 } + } + 98 { + 0.519489 3.3704 0.115602 + { -1 3.06627e-012 0 } + { 0.733333 0.654902 0.294118 1 } + } + 99 { + 2.99397 1.48611 -0.838305 + { 8.64537e-011 -1 7.27066e-011 } + { 0.729412 0.396078 0.341176 1 } + } + 100 { + 2.99397 1.48611 0.527741 + { 5.96697e-011 -0.983257 0.182222 } + { 0.729412 0.396078 0.341176 1 } + } + 101 { + -4 1.48611 -0.838305 + { 0 -1 0 } + { 0.729412 0.396078 0.341176 1 } + } + 102 { + -2.30748 3.3704 3.18072 + { -0.220612 0.975362 0 } + { 1 1 1 1 } + } + 103 { + -2.30748 3.3704 2.23852 + { -0.146659 0.989187 0 } + { 1 1 1 1 } + } + 104 { + -3.36172 3.3704 3.18072 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 105 { + -0.900963 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 106 { + -0.423812 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 107 { + -3.36172 3.3704 -3.42013 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 108 { + 0.997627 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 109 { + 2.99397 3.3704 -3.42013 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 110 { + 0.519489 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 111 { + 2.99397 5.12958 0.115606 + { 3.51998e-009 -1 -1.29401e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 112 { + 0.997627 5.12958 0.115602 + { 3.53529e-009 -1 -1.28696e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 113 { + 2.99397 5.12958 -3.42013 + { 3.54741e-009 -1 -1.28263e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 114 { + 2.99397 0 -3.42013 + { -1 -1.07371e-007 0 } + { 0.458824 0.776471 0.796078 1 } + } + 115 { + 2.99397 0 -1.36315 + { -1 -1.07371e-007 0 } + { 0.458824 0.776471 0.796078 1 } + } + 116 { + 2.99397 3.00428 -1.3609 + { -1 -1.07371e-007 0 } + { 0.686275 0.890196 0.796078 1 } + } + 117 { + 2.99397 3.00428 -3.42012 + { -1 -1.07371e-007 0 } + { 0.686275 0.890196 0.796078 1 } + } + 118 { + -0.423826 4.88145 0.507012 + { 1 9.25347e-006 3.25304e-006 } + { 0.733333 0.654902 0.294118 1 } + } + 119 { + -0.423812 3.3704 0.502572 + { 1 9.25347e-006 3.25304e-006 } + { 0.733333 0.654902 0.294118 1 } + } + 120 { + -0.423812 3.3704 0.115602 + { 1 9.25824e-006 0 } + { 0.733333 0.654902 0.294118 1 } + } + 121 { + -0.423826 4.88145 0.120042 + { 1 9.25824e-006 0 } + { 0.733333 0.654902 0.294118 1 } + } + 122 { + 2.99397 2.54985 3.18072 + { 0 7.83887e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 123 { + 2.99397 1.03879 3.18072 + { 0 7.83887e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 124 { + 2.04443 1.03879 3.18072 + { 2.85806e-008 7.83886e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 125 { + 2.04443 2.54985 3.18072 + { 2.85806e-008 7.83886e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 126 { + 2.99397 1.48611 0.527741 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 127 { + 2.99397 0 0.527741 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 128 { + 2.99397 1.03879 2.23852 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 129 { + 2.99397 2.54985 2.23852 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 130 { + 2.99397 0 0.527741 + { 0 0.983872 -0.178875 } + { 0.901961 0.654902 0.152941 1 } + } + 131 { + 2.04443 0 0.527741 + { 0 0.983872 -0.178875 } + { 0.901961 0.654902 0.152941 1 } + } + 132 { + 2.04443 1.03879 2.23852 + { 0.212236 0.959168 -0.186958 } + { 0.901961 0.654902 0.152941 1 } + } + 133 { + 2.99397 1.03879 2.23852 + { 0 0.963007 -0.269477 } + { 0.901961 0.654902 0.152941 1 } + } + 134 { + 2.04443 0 0.527741 + { 1 -1.48049e-006 9.09752e-007 } + { 0.733333 0.654902 0.294118 1 } + } + 135 { + 2.04443 1.48611 0.527741 + { 1 -1.48049e-006 9.09752e-007 } + { 0.733333 0.654902 0.294118 1 } + } + 136 { + 2.04443 2.54985 2.23852 + { 1 -1.48049e-006 9.09752e-007 } + { 0.733333 0.654902 0.294118 1 } + } + 137 { + 2.04443 1.03879 2.23852 + { 1 -1.48049e-006 9.09752e-007 } + { 0.733333 0.654902 0.294118 1 } + } + 138 { + 2.04443 1.48611 0.527741 + { 0 -0.983257 0.182222 } + { 0.733333 0.462745 0.294118 1 } + } + 139 { + 2.99397 1.48611 0.527741 + { 5.96697e-011 -0.983257 0.182222 } + { 0.733333 0.462745 0.294118 1 } + } + 140 { + 2.99397 2.54985 2.23852 + { 0 -0.961568 0.274569 } + { 0.733333 0.462745 0.294118 1 } + } + 141 { + 2.04443 2.54985 2.23852 + { -0.212513 -0.958418 0.190453 } + { 0.733333 0.462745 0.294118 1 } + } + 142 { + 2.99397 1.03879 3.18072 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 143 { + 2.99397 2.54985 3.18072 + { -1 0 0 } + { 0.733333 0.654902 0.294118 1 } + } + 144 { + 2.04443 1.03879 3.18072 + { 0.309844 0.950787 0 } + { 0.901961 0.654902 0.152941 1 } + } + 145 { + 2.99397 1.03879 3.18072 + { 0 1 0 } + { 0.901961 0.654902 0.152941 1 } + } + 146 { + -0.423812 2.15079 2.23852 + { 1 9.25347e-006 3.25304e-006 } + { 0.733333 0.654902 0.294118 1 } + } + 147 { + -0.423826 3.66184 2.23852 + { 1 9.25347e-006 3.25304e-006 } + { 0.733333 0.654902 0.294118 1 } + } + 148 { + -0.423826 3.66184 3.18072 + { 1 9.25824e-006 0 } + { 0.733333 0.654902 0.294118 1 } + } + 149 { + -0.423812 2.15079 3.18072 + { 1 9.25824e-006 0 } + { 0.733333 0.654902 0.294118 1 } + } + 150 { + 2.99397 2.54985 3.18072 + { 0 -1 0 } + { 0.733333 0.462745 0.294118 1 } + } + 151 { + 2.04443 2.54985 3.18072 + { -0.309844 -0.950787 0 } + { 0.733333 0.462745 0.294118 1 } + } + 152 { + 2.04443 1.03879 2.23852 + { 7.18913e-009 9.85887e-009 1 } + { 0.733333 0.654902 0.294118 1 } + } + 153 { + 2.04443 2.54985 2.23852 + { 7.18913e-009 9.85887e-009 1 } + { 0.733333 0.654902 0.294118 1 } + } + 154 { + 0.519489 3.66184 2.23852 + { 7.18913e-009 9.85887e-009 1 } + { 0.733333 0.654902 0.294118 1 } + } + 155 { + 0.519489 2.15079 2.23852 + { 7.18913e-009 9.85887e-009 1 } + { 0.733333 0.654902 0.294118 1 } + } + 156 { + 0.519489 3.66184 3.18072 + { -0.309844 -0.950787 0 } + { 0.733333 0.462745 0.294118 1 } + } + 157 { + 0.519489 3.66184 2.23852 + { -0.214114 -0.95413 -0.209266 } + { 0.733333 0.462745 0.294118 1 } + } + 158 { + 0.519489 2.15079 3.18072 + { 2.85806e-008 7.83886e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 159 { + 0.519489 3.66184 3.18072 + { 2.85806e-008 7.83886e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 160 { + 0.519489 2.15079 2.23852 + { 0.214078 0.954223 0.208873 } + { 0.901961 0.654902 0.152941 1 } + } + 161 { + 0.519489 2.15079 3.18072 + { 0.309844 0.950787 0 } + { 0.901961 0.654902 0.152941 1 } + } + 162 { + -0.423812 3.3704 0.502572 + { 0 0.95348 0.301457 } + { 0.901961 0.654902 0.152941 1 } + } + 163 { + 0.519489 3.3704 0.502572 + { 0 0.95348 0.301457 } + { 0.901961 0.654902 0.152941 1 } + } + 164 { + 0.519489 3.3704 0.115602 + { 0 1 0 } + { 0.901961 0.654902 0.152941 1 } + } + 165 { + -0.423812 3.3704 0.115602 + { 0 1 0 } + { 0.901961 0.654902 0.152941 1 } + } + 166 { + -0.423826 3.66184 3.18072 + { 0 -1 0 } + { 0.733333 0.462745 0.294118 1 } + } + 167 { + -0.423826 3.66184 2.23852 + { 0 -0.953298 -0.302032 } + { 0.733333 0.462745 0.294118 1 } + } + 168 { + -0.423812 2.15079 3.18072 + { 0 7.83887e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 169 { + -0.423826 3.66184 3.18072 + { 0 7.83887e-008 -1 } + { 0.733333 0.654902 0.294118 1 } + } + 170 { + -0.423812 2.15079 2.23852 + { 0 0.95348 0.301457 } + { 0.901961 0.654902 0.152941 1 } + } + 171 { + -0.423812 2.15079 3.18072 + { 0 1 0 } + { 0.901961 0.654902 0.152941 1 } + } + 172 { + 0.519489 2.15079 2.23852 + { -1 3.06311e-012 2.15477e-012 } + { 0.733333 0.654902 0.294118 1 } + } + 173 { + 0.519489 3.66184 2.23852 + { -1 3.06311e-012 2.15477e-012 } + { 0.733333 0.654902 0.294118 1 } + } + 174 { + -3.36172 3.00428 -3.42012 + { 1 -6.80267e-009 0 } + { 0.686275 0.890196 0.796078 1 } + } + 175 { + -3.36172 3.00428 -1.3609 + { 1 -6.80267e-009 0 } + { 0.686275 0.890196 0.796078 1 } + } + 176 { + -3.36172 0 -1.36315 + { 1 -6.80267e-009 0 } + { 0.458824 0.776471 0.796078 1 } + } + 177 { + -3.36172 0 -3.42013 + { 1 -6.80267e-009 0 } + { 0.458824 0.776471 0.796078 1 } + } + 178 { + -2.36149 7.09573 -2.49425 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 179 { + -3.36172 7.09573 -3.42013 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 180 { + -0.485624 7.09573 -3.42013 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 181 { + -0.485624 7.09573 -2.49425 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 182 { + 2.99397 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 183 { + 0.997627 3.3704 3.18072 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 184 { + 2.99397 3.3704 3.18072 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 185 { + -1.90136 7.09573 -2.16895 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 186 { + 1.31869 7.09573 -0.718624 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 187 { + 1.31869 7.09573 0.350091 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 188 { + -1.90136 7.09573 1.0558 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 189 { + -3.36172 3.3704 0.115602 + { 0 1 0 } + { 0.733333 0.658824 0.760784 1 } + } + 190 { + -2.30748 3.3704 2.05045 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 191 { + -0.900963 3.3704 2.04979 + { 0 1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 192 { + 1.82483 5.34053 3.18072 + { -0.220612 0.975362 0 } + { 0.752941 0.752941 0.752941 1 } + } + 193 { + 1.82483 5.34053 2.23852 + { -0.146659 0.989187 0 } + { 0.752941 0.752941 0.752941 1 } + } + 194 { + 2.99397 5.34053 3.18072 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 195 { + 2.99397 5.34053 2.23852 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 196 { + 1.82483 5.34053 0.350091 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 197 { + 2.99397 7.09573 -3.42013 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 198 { + 1.82483 7.09573 -2.49425 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 199 { + -2.36149 7.09573 1.0558 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 200 { + -3.36172 7.09573 2.04979 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 201 { + 1.82483 5.34053 -0.718624 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 202 { + 1.31869 5.34053 -0.718624 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 203 { + 1.31869 5.34053 0.350091 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 204 { + -2.36149 5.34053 1.0558 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 205 { + -2.36149 5.34053 2.04979 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 206 { + -1.90136 5.34053 2.04979 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 207 { + -1.90136 5.34053 1.0558 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 208 { + 2.99397 3.3704 -3.42013 + { -4.07833e-010 -6.17821e-011 1 } + { 0.733333 0.658824 0.760784 1 } + } + 209 { + 2.99397 5.12958 -3.42013 + { -4.07833e-010 -6.17821e-011 1 } + { 0.376471 0.498039 0.760784 1 } + } + 210 { + -3.36172 5.12958 -3.42013 + { -4.07833e-010 -6.17821e-011 1 } + { 0.376471 0.498039 0.760784 1 } + } + 211 { + -3.36172 3.3704 -3.42013 + { -4.07833e-010 -6.17821e-011 1 } + { 0.733333 0.658824 0.760784 1 } + } + 212 { + 2.99397 5.12958 1.67403 + { 3.51106e-009 -1 -1.29564e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 213 { + 0.997673 5.12958 1.67402 + { 3.51106e-009 -1 -1.29564e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 214 { + 2.99397 3.3704 -3.42013 + { -1 -8.366e-011 9.98999e-012 } + { 0.733333 0.658824 0.760784 1 } + } + 215 { + 2.99397 3.3704 0.115602 + { -1 6.51735e-011 6.38546e-011 } + { 0.733333 0.658824 0.760784 1 } + } + 216 { + 2.99397 5.12958 0.115606 + { -1 9.77602e-011 9.57819e-011 } + { 0.376471 0.498039 0.760784 1 } + } + 217 { + 2.99397 5.12958 -3.42013 + { -1 -8.366e-011 9.98999e-012 } + { 0.376471 0.498039 0.760784 1 } + } + 218 { + -3.36172 3.3704 0.115602 + { 1 2.60413e-011 1.94624e-012 } + { 0.733333 0.658824 0.760784 1 } + } + 219 { + -3.36172 3.3704 -3.42013 + { 1 2.23911e-011 1.9372e-012 } + { 0.733333 0.658824 0.760784 1 } + } + 220 { + -3.36172 5.12958 -3.42013 + { 1 2.23911e-011 1.9372e-012 } + { 0.376471 0.498039 0.760784 1 } + } + 221 { + -3.36172 5.12958 0.115593 + { 1 2.60413e-011 1.94624e-012 } + { 0.376471 0.498039 0.760784 1 } + } + 222 { + 2.99397 4.51665 1.67402 + { -1 1.3959e-010 9.07869e-011 } + { 0.733333 0.462745 0.760784 1 } + } + 223 { + 2.99397 5.12958 1.67403 + { -1 2.7918e-010 1.81574e-010 } + { 0.376471 0.498039 0.760784 1 } + } + 224 { + -3.36172 5.12958 0.115593 + { 3.60372e-009 -1 -1.29603e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 225 { + -2.30746 5.12958 2.05044 + { 3.60957e-009 -1 -1.29785e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 226 { + -2.30746 5.12958 2.23852 + { 0.146407 -0.989224 -1.50609e-007 } + { 0.376471 0.498039 0.760784 1 } + } + 227 { + -3.36172 5.12958 3.18072 + { 3.62962e-009 -1 -1.30501e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 228 { + 2.99397 3.3704 3.18072 + { 0 0 -1 } + { 0.733333 0.658824 0.760784 1 } + } + 229 { + 0.997627 3.3704 3.18072 + { 0 0 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 230 { + 0.997627 4.51665 3.18072 + { 0 0 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 231 { + 2.99397 4.51665 3.18072 + { 0 0 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 232 { + -3.36172 5.12958 3.18072 + { 1 2.96915e-011 1.95529e-012 } + { 0.376471 0.498039 0.760784 1 } + } + 233 { + -3.36172 3.3704 3.18072 + { 1 2.96915e-011 1.95529e-012 } + { 0.733333 0.658824 0.760784 1 } + } + 234 { + 0.997627 3.3704 3.18072 + { 1 0 0 } + { 0.733333 0.462745 0.760784 1 } + } + 235 { + 0.997627 3.3704 0.115602 + { 1 -9.56319e-006 -3.85741e-006 } + { 0.733333 0.462745 0.760784 1 } + } + 236 { + 0.997627 4.51665 1.67402 + { 1 -9.56319e-006 -3.85741e-006 } + { 0.733333 0.462745 0.760784 1 } + } + 237 { + 0.997627 4.51665 3.18072 + { 1 0 0 } + { 0.733333 0.462745 0.760784 1 } + } + 238 { + -2.30748 3.3704 3.18072 + { 4.42741e-011 -1.09614e-010 -1 } + { 1 1 1 1 } + } + 239 { + -3.36172 3.3704 3.18072 + { 2.75919e-011 -1.51613e-010 -1 } + { 0.733333 0.658824 0.760784 1 } + } + 240 { + -3.36172 5.12958 3.18072 + { 2.75919e-011 -1.51613e-010 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 241 { + -2.30746 5.12958 3.18072 + { 4.42741e-011 -1.09614e-010 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 242 { + 1.82483 5.34053 3.18072 + { 3.04782e-011 -3.38078e-011 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 243 { + 1.82483 7.09573 3.18072 + { 3.04782e-011 -3.38078e-011 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 244 { + 2.99397 5.34053 3.18072 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 245 { + 2.99397 7.09573 3.18072 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 246 { + 2.99397 5.34053 2.23852 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 247 { + 2.99397 5.34053 3.18072 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 248 { + 2.99397 7.09573 3.18072 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 249 { + 2.99397 7.09573 2.23852 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 250 { + 2.99397 5.34053 -3.42013 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 251 { + 2.99397 7.09573 -3.42013 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 252 { + 2.99397 5.34053 -3.42013 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 253 { + 2.99397 7.09573 -3.42013 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 254 { + -0.485624 7.09573 -3.42013 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 255 { + -0.485624 5.34053 -3.42013 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 256 { + -3.36172 5.34053 -3.42013 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 257 { + -2.36149 5.34053 -2.49425 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 258 { + -0.485624 5.34053 -2.49425 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 259 { + -0.485624 5.34053 -3.42013 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 260 { + -3.36172 5.34053 -3.42013 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 261 { + -3.36172 7.09573 -3.42013 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 262 { + -3.36172 7.09573 2.04979 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 263 { + -3.36172 5.34053 2.04979 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 264 { + -3.36172 5.34053 2.04979 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 265 { + -2.36149 5.34053 2.04979 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 266 { + -3.36172 5.34053 2.04979 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 267 { + -3.36172 7.09573 2.04979 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 268 { + -2.36149 7.09573 2.04979 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 269 { + -1.90136 5.34053 2.04979 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 270 { + -1.90136 7.09573 2.04979 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 271 { + -0.900963 3.3704 0.115602 + { -1 7.02332e-006 1.01878e-006 } + { 0.733333 0.462745 0.760784 1 } + } + 272 { + -0.900963 3.3704 2.04979 + { -1 7.02332e-006 1.01878e-006 } + { 0.733333 0.462745 0.760784 1 } + } + 273 { + -0.900949 5.12958 2.04979 + { -1 7.02332e-006 1.01878e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 274 { + -0.900953 5.12958 0.115599 + { -1 7.02332e-006 1.01878e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 275 { + -0.900963 3.3704 2.04979 + { -0.000468015 -2.28319e-006 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 276 { + -2.30748 3.3704 2.05045 + { -0.000468015 -2.28319e-006 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 277 { + -2.30746 5.12958 2.05044 + { -0.000468015 -2.28319e-006 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 278 { + -0.900949 5.12958 2.04979 + { -0.000468015 -2.28319e-006 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 279 { + -2.30748 3.3704 2.05045 + { -1 1.2768e-005 1.17008e-006 } + { 0.733333 0.462745 0.760784 1 } + } + 280 { + -2.30748 3.3704 2.23852 + { -1 1.2768e-005 1.17008e-006 } + { 1 1 1 1 } + } + 281 { + -2.30746 5.12958 2.23852 + { -1 1.2768e-005 1.17008e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 282 { + -2.30746 5.12958 2.05044 + { -1 1.2768e-005 1.17008e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 283 { + -2.30748 3.3704 2.23852 + { 7.06683e-012 -7.83886e-012 1 } + { 1 1 1 1 } + } + 284 { + 1.82483 5.34053 2.23852 + { 7.06683e-012 -7.83886e-012 1 } + { 0.752941 0.752941 0.752941 1 } + } + 285 { + 1.82483 7.09573 2.23852 + { 7.06683e-012 -7.83886e-012 1 } + { 0.482353 0.560784 0.611765 1 } + } + 286 { + -2.30746 5.12958 2.23852 + { 7.06683e-012 -7.83886e-012 1 } + { 0.376471 0.498039 0.760784 1 } + } + 287 { + 1.82483 5.34053 2.23852 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 288 { + 1.82483 5.34053 0.350091 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 289 { + 1.82483 7.09573 0.350091 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 290 { + 1.82483 7.09573 2.23852 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 291 { + -0.423826 4.88145 0.120042 + { 0.00250514 -0.00827025 -0.999963 } + { 0.733333 0.462745 0.760784 1 } + } + 292 { + -0.900953 5.12958 0.115599 + { 0.00250514 -0.00827025 -0.999963 } + { 0.376471 0.498039 0.760784 1 } + } + 293 { + 0.997627 5.12958 0.115602 + { -0.00249668 -0.00826966 -0.999963 } + { 0.376471 0.498039 0.760784 1 } + } + 294 { + 0.519489 4.88145 0.120042 + { -0.00249668 -0.00826966 -0.999963 } + { 0.733333 0.462745 0.760784 1 } + } + 295 { + 0.997627 3.3704 0.115602 + { -0.00499447 0.00135754 -0.999987 } + { 0.733333 0.462745 0.760784 1 } + } + 296 { + 0.519489 3.3704 0.115602 + { -0.00499447 0.00135754 -0.999987 } + { 0.733333 0.462745 0.760784 1 } + } + 297 { + -0.423812 3.3704 0.115602 + { 0.00500867 0.00135636 -0.999987 } + { 0.733333 0.462745 0.760784 1 } + } + 298 { + -0.900963 3.3704 0.115602 + { 0.00500867 0.00135636 -0.999987 } + { 0.733333 0.462745 0.760784 1 } + } + 299 { + 2.99397 3.3704 3.18072 + { -1 0 0 } + { 0.733333 0.658824 0.760784 1 } + } + 300 { + 2.99397 4.51665 3.18072 + { -1 0 0 } + { 0.733333 0.462745 0.760784 1 } + } + 301 { + 0.997627 4.51665 3.18072 + { 0 -1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 302 { + 0.997627 4.51665 1.67402 + { 0 -1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 303 { + 2.99397 4.51665 1.67402 + { 0 -1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 304 { + 2.99397 4.51665 3.18072 + { 0 -1 0 } + { 0.733333 0.462745 0.760784 1 } + } + 305 { + -0.900953 5.12958 0.115599 + { 3.58039e-009 -1 -1.28204e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 306 { + -3.36172 5.12958 -3.42013 + { 3.57897e-009 -1 -1.28263e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 307 { + -2.36149 5.34053 -2.49425 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 308 { + -2.36149 7.09573 -2.49425 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 309 { + -0.485624 7.09573 -2.49425 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 310 { + -0.485624 5.34053 -2.49425 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 311 { + 0.997627 5.12958 0.115602 + { 1 -1.91264e-005 -7.71482e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 312 { + 0.997673 5.12958 1.67402 + { 1 -1.91264e-005 -7.71482e-006 } + { 0.376471 0.498039 0.760784 1 } + } + 313 { + 2.99397 4.51665 1.67402 + { 3.17248e-006 2.8011e-006 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 314 { + 0.997627 4.51665 1.67402 + { 3.17248e-006 2.8011e-006 -1 } + { 0.733333 0.462745 0.760784 1 } + } + 315 { + 0.997673 5.12958 1.67402 + { 3.17248e-006 2.8011e-006 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 316 { + 2.99397 5.12958 1.67403 + { 3.17248e-006 2.8011e-006 -1 } + { 0.376471 0.498039 0.760784 1 } + } + 317 { + -0.900949 5.12958 2.04979 + { 3.58324e-009 -1 -1.28086e-009 } + { 0.376471 0.498039 0.760784 1 } + } + 318 { + -3.36172 0 -1.36315 + { 0 1 0 } + { 0.505882 0.721569 0.788235 1 } + } + 319 { + 2.99397 0 -1.36315 + { 0 1 0 } + { 0.505882 0.721569 0.788235 1 } + } + 320 { + 1.82483 7.09573 2.23852 + { 0.146407 -0.989224 -1.4972e-007 } + { 0.482353 0.560784 0.611765 1 } + } + 321 { + 1.82483 7.09573 3.18072 + { 0.220231 -0.975448 -2.25215e-007 } + { 0.482353 0.560784 0.611765 1 } + } + 322 { + -2.30746 5.12958 3.18072 + { 0.220231 -0.975448 -2.25879e-007 } + { 0.376471 0.498039 0.760784 1 } + } + 323 { + 2.99397 7.09573 2.23852 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 324 { + 2.99397 7.09573 3.18072 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 325 { + 1.82483 7.09573 0.350091 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 326 { + 1.82483 7.09573 -0.718624 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 327 { + -1.90136 5.34053 -2.16895 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 328 { + 1.82483 5.34053 -0.718624 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 329 { + 1.82483 5.34053 -2.49425 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 330 { + 1.82483 7.09573 -2.49425 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 331 { + 1.82483 7.09573 -0.718624 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 332 { + 1.82483 5.34053 -2.49425 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 333 { + 1.82483 7.09573 -2.49425 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 334 { + -2.36149 5.34053 -2.49425 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 335 { + -2.36149 5.34053 1.0558 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 336 { + -2.36149 7.09573 1.0558 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 337 { + -2.36149 7.09573 -2.49425 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 338 { + 1.31869 5.34053 -2.1928 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 339 { + 1.31869 5.34053 2.04979 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 340 { + 1.31869 5.34053 0.350091 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 341 { + 1.31869 5.34053 2.04979 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 342 { + 1.31869 7.09573 2.04979 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 343 { + 1.31869 7.09573 0.350091 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 344 { + 1.31869 5.34053 0.350091 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 345 { + 1.31869 7.09573 0.350091 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 346 { + 1.82483 7.09573 0.350091 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 347 { + 1.82483 5.34053 0.350091 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 348 { + 1.31869 7.09573 2.04979 + { 0 0 -1 } + { 0.482353 0.560784 0.611765 1 } + } + 349 { + 1.31869 5.34053 2.04979 + { 0 0 -1 } + { 0.752941 0.752941 0.752941 1 } + } + 350 { + 1.31869 5.34053 -2.1928 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 351 { + 1.31869 5.34053 -0.718624 + { -1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 352 { + 1.31869 7.09573 -0.718624 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 353 { + 1.31869 7.09573 -2.1928 + { -1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 354 { + -1.90136 5.34053 -2.16895 + { 0.00740377 0 0.999973 } + { 0.752941 0.752941 0.752941 1 } + } + 355 { + 1.31869 5.34053 -2.1928 + { 0.00740377 0 0.999973 } + { 0.752941 0.752941 0.752941 1 } + } + 356 { + 1.31869 7.09573 -2.1928 + { 0.00740377 0 0.999973 } + { 0.482353 0.560784 0.611765 1 } + } + 357 { + -1.90136 7.09573 -2.16895 + { 0.00740377 0 0.999973 } + { 0.482353 0.560784 0.611765 1 } + } + 358 { + -1.90136 5.34053 1.0558 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 359 { + -1.90136 5.34053 -2.16895 + { 1 0 0 } + { 0.752941 0.752941 0.752941 1 } + } + 360 { + -1.90136 7.09573 -2.16895 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 361 { + -1.90136 7.09573 1.0558 + { 1 0 0 } + { 0.482353 0.560784 0.611765 1 } + } + 362 { + -1.90136 5.34053 1.0558 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 363 { + -1.90136 7.09573 1.0558 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 364 { + -2.36149 7.09573 1.0558 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 365 { + -2.36149 5.34053 1.0558 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 366 { + 1.31869 7.09573 -0.718624 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 367 { + 1.31869 5.34053 -0.718624 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 368 { + 1.82483 5.34053 -0.718624 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 369 { + 1.82483 7.09573 -0.718624 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 370 { + -3.36172 7.09573 -3.42013 + { 0 0 1 } + { 0.482353 0.560784 0.611765 1 } + } + 371 { + -3.36172 5.34053 -3.42013 + { 0 0 1 } + { 0.752941 0.752941 0.752941 1 } + } + 372 { + 1.31869 7.09573 -2.1928 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 373 { + -1.90136 7.09573 2.04979 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 374 { + 1.31869 7.09573 2.04979 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 375 { + -2.36149 7.09573 2.04979 + { 0 -1 0 } + { 0.482353 0.560784 0.611765 1 } + } + 376 { + 2.99397 5.34053 -3.42013 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 377 { + 1.82483 5.34053 -2.49425 + { 0 1 0 } + { 0.752941 0.752941 0.752941 1 } + } + 378 { + 4 0 -0.838305 + { 2.94739e-009 -4.60849e-010 1 } + { 0.729412 0.513726 0.341176 1 } + } + 379 { + 4 1.48611 -0.838305 + { 2.94739e-009 -4.60849e-010 1 } + { 0.729412 0.513726 0.341176 1 } + } + 380 { + 2.99397 0 -0.838305 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 381 { + 2.99397 0 0.527741 + { 0 0.983872 -0.178875 } + { 0.729412 0.192157 0.247059 1 } + } + 382 { + 4 0 0.527741 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 383 { + 4 0 -0.838305 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 384 { + 4 1.48611 0.527741 + { -1.20596e-007 -9.54098e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 385 { + 4 0 0.527741 + { -1.20596e-007 -9.54098e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 386 { + 2.99397 0 0.527741 + { -1.20596e-007 -9.54098e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 387 { + 2.99397 1.48611 0.527741 + { -1.20596e-007 -9.54098e-009 -1 } + { 0.729412 0.513726 0.341176 1 } + } + 388 { + 4 1.48611 -0.838305 + { 1.72907e-010 -1 1.45424e-010 } + { 0.729412 0.396078 0.341176 1 } + } + 389 { + 4 1.48611 0.527741 + { 1.72907e-010 -1 1.45424e-010 } + { 0.729412 0.396078 0.341176 1 } + } + 390 { + -4 0 -0.838305 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 391 { + 4 0 0.527741 + { 0 1 0 } + { 0.376471 0.498039 0 1 } + } + 392 { + 0.243417 0 -0.838305 + { 0 1 0 } + { 0.729412 0.192157 0.247059 1 } + } + 393 { + 2.04443 0 0.527741 + { 0 0.983872 -0.178875 } + { 0.729412 0.192157 0.247059 1 } + } + } + { + { 0 1 2 3 { levelgeo-ORG } } + } + { + { 4 5 6 7 { levelgeo-ORG } } + } + { + { 8 9 10 11 { levelgeo-ORG } } + } + { + { 12 13 14 15 { levelgeo-ORG } } + } + { + { 16 17 18 19 { levelgeo-ORG } } + } + { + { 20 21 22 23 { levelgeo-ORG } } + } + { + { 21 24 22 { levelgeo-ORG } } + } + { + { 25 26 27 { levelgeo-ORG } } + } + { + { 28 25 27 29 { levelgeo-ORG } } + } + { + { 30 31 32 33 { levelgeo-ORG } } + } + { + { 11 10 32 31 { levelgeo-ORG } } + } + { + { 34 35 36 37 { levelgeo-ORG } } + } + { + { 38 39 40 41 { levelgeo-ORG } } + } + { + { 42 43 44 { levelgeo-ORG } } + } + { + { 45 46 47 48 { levelgeo-ORG } } + } + { + { 49 50 51 52 { levelgeo-ORG } } + } + { + { 53 54 55 56 { levelgeo-ORG } } + } + { + { 57 58 59 60 { levelgeo-ORG } } + } + { + { 61 62 63 64 { levelgeo-ORG } } + } + { + { 65 66 67 68 { levelgeo-ORG } } + } + { + { 69 70 71 72 { levelgeo-ORG } } + } + { + { 73 74 75 76 { levelgeo-ORG } } + } + { + { 77 78 79 80 { levelgeo-ORG } } + } + { + { 81 82 83 84 { levelgeo-ORG } } + } + { + { 85 86 87 88 { levelgeo-ORG } } + } + { + { 80 79 74 73 { levelgeo-ORG } } + } + { + { 59 89 90 60 { levelgeo-ORG } } + } + { + { 91 92 93 94 { levelgeo-ORG } } + } + { + { 95 96 97 98 { levelgeo-ORG } } + } + { + { 99 100 52 51 { levelgeo-ORG } } + } + { + { 101 50 49 { levelgeo-ORG } } + } + { + { 102 103 104 { levelgeo-ORG } } + } + { + { 105 106 107 { levelgeo-ORG } } + } + { + { 108 109 110 { levelgeo-ORG } } + } + { + { 111 112 113 { levelgeo-ORG } } + } + { + { 114 115 116 117 { levelgeo-ORG } } + } + { + { 118 119 120 121 { levelgeo-ORG } } + } + { + { 122 123 124 125 { levelgeo-ORG } } + } + { + { 126 127 128 129 { levelgeo-ORG } } + } + { + { 130 131 132 133 { levelgeo-ORG } } + } + { + { 134 135 136 137 { levelgeo-ORG } } + } + { + { 138 139 140 141 { levelgeo-ORG } } + } + { + { 129 128 142 143 { levelgeo-ORG } } + } + { + { 133 132 144 145 { levelgeo-ORG } } + } + { + { 146 147 148 149 { levelgeo-ORG } } + } + { + { 141 140 150 151 { levelgeo-ORG } } + } + { + { 152 153 154 155 { levelgeo-ORG } } + } + { + { 141 151 156 157 { levelgeo-ORG } } + } + { + { 125 124 158 159 { levelgeo-ORG } } + } + { + { 144 132 160 161 { levelgeo-ORG } } + } + { + { 162 163 164 165 { levelgeo-ORG } } + } + { + { 157 156 166 167 { levelgeo-ORG } } + } + { + { 159 158 168 169 { levelgeo-ORG } } + } + { + { 161 160 170 171 { levelgeo-ORG } } + } + { + { 172 173 96 95 { levelgeo-ORG } } + } + { + { 157 167 92 91 { levelgeo-ORG } } + } + { + { 147 146 119 118 { levelgeo-ORG } } + } + { + { 170 160 163 162 { levelgeo-ORG } } + } + { + { 174 175 176 177 { levelgeo-ORG } } + } + { + { 110 109 107 106 { levelgeo-ORG } } + } + { + { 178 179 180 181 { levelgeo-ORG } } + } + { + { 182 108 183 184 { levelgeo-ORG } } + } + { + { 185 186 187 188 { levelgeo-ORG } } + } + { + { 189 190 191 105 { levelgeo-ORG } } + } + { + { 103 190 189 104 { levelgeo-ORG } } + } + { + { 103 102 192 193 { levelgeo-ORG } } + } + { + { 193 192 194 195 { levelgeo-ORG } } + } + { + { 193 195 196 { levelgeo-ORG } } + } + { + { 197 198 181 180 { levelgeo-ORG } } + } + { + { 179 178 199 200 { levelgeo-ORG } } + } + { + { 196 201 202 203 { levelgeo-ORG } } + } + { + { 204 205 206 207 { levelgeo-ORG } } + } + { + { 208 209 210 211 { levelgeo-ORG } } + } + { + { 182 109 108 { levelgeo-ORG } } + } + { + { 112 111 212 213 { levelgeo-ORG } } + } + { + { 214 215 216 217 { levelgeo-ORG } } + } + { + { 218 219 220 221 { levelgeo-ORG } } + } + { + { 215 222 223 216 { levelgeo-ORG } } + } + { + { 224 225 226 227 { levelgeo-ORG } } + } + { + { 228 229 230 231 { levelgeo-ORG } } + } + { + { 218 221 232 233 { levelgeo-ORG } } + } + { + { 234 235 236 237 { levelgeo-ORG } } + } + { + { 238 239 240 241 { levelgeo-ORG } } + } + { + { 242 238 241 243 { levelgeo-ORG } } + } + { + { 244 242 243 245 { levelgeo-ORG } } + } + { + { 246 247 248 249 { levelgeo-ORG } } + } + { + { 250 246 249 251 { levelgeo-ORG } } + } + { + { 252 253 254 255 { levelgeo-ORG } } + } + { + { 256 257 258 259 { levelgeo-ORG } } + } + { + { 260 261 262 263 { levelgeo-ORG } } + } + { + { 264 204 257 256 { levelgeo-ORG } } + } + { + { 265 266 267 268 { levelgeo-ORG } } + } + { + { 269 265 268 270 { levelgeo-ORG } } + } + { + { 271 272 273 274 { levelgeo-ORG } } + } + { + { 275 276 277 278 { levelgeo-ORG } } + } + { + { 279 280 281 282 { levelgeo-ORG } } + } + { + { 283 284 285 286 { levelgeo-ORG } } + } + { + { 287 288 289 290 { levelgeo-ORG } } + } + { + { 291 292 293 294 { levelgeo-ORG } } + } + { + { 293 295 296 294 { levelgeo-ORG } } + } + { + { 291 297 298 292 { levelgeo-ORG } } + } + { + { 205 204 264 { levelgeo-ORG } } + } + { + { 299 300 222 215 { levelgeo-ORG } } + } + { + { 301 302 303 304 { levelgeo-ORG } } + } + { + { 113 112 305 306 { levelgeo-ORG } } + } + { + { 307 308 309 310 { levelgeo-ORG } } + } + { + { 311 312 236 235 { levelgeo-ORG } } + } + { + { 313 314 315 316 { levelgeo-ORG } } + } + { + { 306 305 224 { levelgeo-ORG } } + } + { + { 224 305 317 225 { levelgeo-ORG } } + } + { + { 318 60 90 { levelgeo-ORG } } + } + { + { 319 89 59 { levelgeo-ORG } } + } + { + { 226 320 321 322 { levelgeo-ORG } } + } + { + { 323 324 321 320 { levelgeo-ORG } } + } + { + { 323 325 326 197 { levelgeo-ORG } } + } + { + { 202 327 207 203 { levelgeo-ORG } } + } + { + { 328 329 330 331 { levelgeo-ORG } } + } + { + { 332 310 309 333 { levelgeo-ORG } } + } + { + { 334 335 336 337 { levelgeo-ORG } } + } + { + { 338 327 202 { levelgeo-ORG } } + } + { + { 206 339 203 207 { levelgeo-ORG } } + } + { + { 340 341 342 343 { levelgeo-ORG } } + } + { + { 344 345 346 347 { levelgeo-ORG } } + } + { + { 348 349 269 270 { levelgeo-ORG } } + } + { + { 350 351 352 353 { levelgeo-ORG } } + } + { + { 354 355 356 357 { levelgeo-ORG } } + } + { + { 358 359 360 361 { levelgeo-ORG } } + } + { + { 362 363 364 365 { levelgeo-ORG } } + } + { + { 366 367 368 369 { levelgeo-ORG } } + } + { + { 325 187 186 326 { levelgeo-ORG } } + } + { + { 189 105 107 { levelgeo-ORG } } + } + { + { 370 371 255 254 { levelgeo-ORG } } + } + { + { 197 326 198 { levelgeo-ORG } } + } + { + { 372 186 185 { levelgeo-ORG } } + } + { + { 373 188 187 374 { levelgeo-ORG } } + } + { + { 320 325 323 { levelgeo-ORG } } + } + { + { 199 188 373 375 { levelgeo-ORG } } + } + { + { 200 199 375 { levelgeo-ORG } } + } + { + { 376 201 196 195 { levelgeo-ORG } } + } + { + { 377 201 376 { levelgeo-ORG } } + } + { + { 37 36 13 12 { levelgeo-ORG } } + } + { + { 378 379 54 53 { levelgeo-ORG } } + } + { + { 380 381 382 383 { levelgeo-ORG } } + } + { + { 384 385 386 387 { levelgeo-ORG } } + } + { + { 100 99 388 389 { levelgeo-ORG } } + } + { + { 390 23 27 26 { levelgeo-ORG } } + } + { + { 20 23 390 { levelgeo-ORG } } + } + { + { 391 29 22 24 { levelgeo-ORG } } + } + { + { 28 29 391 { levelgeo-ORG } } + } + { + { 381 380 392 393 { levelgeo-ORG } } + } + { + { 393 392 44 43 { levelgeo-ORG } } + } + { + { 377 376 259 258 { levelgeo-ORG } } + } + { + { 322 227 226 { levelgeo-ORG } } + } +} diff --git a/samples/culling/models/occluders.egg b/samples/culling/models/occluders.egg new file mode 100644 index 0000000000..e50e82550c --- /dev/null +++ b/samples/culling/models/occluders.egg @@ -0,0 +1,562 @@ + { Y-Up } + + occluder1 { + occluder { 1 } + occluder1-ORG { + 0 { + 4 7.57015 4 + } + 1 { + 4 7.57015 -4 + } + 2 { + -4 7.57015 -4 + } + 3 { + -4 7.57015 4 + } + } + { + { 0 1 2 3 { occluder1-ORG } } + } +} + occluder4 { + occluder { 1 } + occluder4-ORG { + 0 { + 4 2.96946e-016 -0.84965 + } + 1 { + 4 7.57015 -0.84965 + } + 2 { + 4 7.57015 -4 + } + 3 { + 4 0 -4 + } + } + { + { 0 1 2 3 { occluder4-ORG } } + } +} + occluder2 { + occluder { 1 } + occluder2-ORG { + 0 { + 4 0 -4 + } + 1 { + 4 7.57015 -4 + } + 2 { + -4 7.57015 -4 + } + 3 { + -4 0 -4 + } + } + { + { 0 1 2 3 { occluder2-ORG } } + } +} + occluder3 { + occluder { 1 } + occluder3-ORG { + 0 { + 4 0 4 + } + 1 { + 4 7.57015 4 + } + 2 { + -4 7.57015 4 + } + 3 { + -4 0 4 + } + } + { + { 0 1 2 3 { occluder3-ORG } } + } +} + occluder5 { + occluder { 1 } + occluder5-ORG { + 0 { + 4 0 4 + } + 1 { + 4 7.57015 4 + } + 2 { + 4 7.57015 0.551412 + } + 3 { + 4 -2.78684e-016 0.551412 + } + } + { + { 0 1 2 3 { occluder5-ORG } } + } +} + occluder6 { + occluder { 1 } + occluder6-ORG { + 0 { + 4 1.49418 4 + } + 1 { + 4 7.57015 4 + } + 2 { + 4 7.57015 -4 + } + 3 { + 4 1.49418 -4 + } + } + { + { 0 1 2 3 { occluder6-ORG } } + } +} + occluder7 { + occluder { 1 } + occluder7-ORG { + 0 { + -3.99576 2.96946e-016 -0.84965 + } + 1 { + -3.99576 7.57015 -0.84965 + } + 2 { + -3.99576 7.57015 -4 + } + 3 { + -3.99576 0 -4 + } + } + { + { 0 1 2 3 { occluder7-ORG } } + } +} + occluder8 { + occluder { 1 } + occluder8-ORG { + 0 { + -3.99576 0 4 + } + 1 { + -3.99576 7.57015 4 + } + 2 { + -3.99576 7.57015 0.551412 + } + 3 { + -3.99576 -2.78684e-016 0.551412 + } + } + { + { 0 1 2 3 { occluder8-ORG } } + } +} + occluder9 { + occluder { 1 } + occluder9-ORG { + 0 { + -3.99576 1.49418 4 + } + 1 { + -3.99576 7.57015 4 + } + 2 { + -3.99576 7.57015 -4 + } + 3 { + -3.99576 1.49418 -4 + } + } + { + { 0 1 2 3 { occluder9-ORG } } + } +} + occluder10 { + occluder { 1 } + occluder10-ORG { + 0 { + -4 1.49418 -1.13843 + } + 1 { + -4 3.24289 -1.13843 + } + 2 { + 4 3.24289 -1.13843 + } + 3 { + 4 1.49418 -1.13843 + } + } + { + { 0 1 2 3 { occluder10-ORG } } + } +} + occluder11 { + occluder { 1 } + occluder11-ORG { + 0 { + -4 0 -1.13843 + } + 1 { + -4 3.24289 -1.13843 + } + 2 { + -1.01962 3.24289 -1.13843 + } + 3 { + -1.01962 -2.78684e-016 -1.13843 + } + } + { + { 0 1 2 3 { occluder11-ORG } } + } +} + occluder12 { + occluder { 1 } + occluder12-ORG { + 0 { + 0.242712 2.96946e-016 -1.13843 + } + 1 { + 0.242712 3.24289 -1.13843 + } + 2 { + 4 3.24289 -1.13843 + } + 3 { + 4 0 -1.13843 + } + } + { + { 0 1 2 3 { occluder12-ORG } } + } +} + occluder13 { + occluder { 1 } + occluder13-ORG { + 0 { + -4 3.19567 0.643347 + } + 1 { + -4 3.19567 -3.99828 + } + 2 { + 4 3.19567 -3.99828 + } + 3 { + 4 3.19567 0.643347 + } + } + { + { 0 1 2 3 { occluder13-ORG } } + } +} + occluder14 { + occluder { 1 } + occluder14-ORG { + 0 { + -4 3.19567 4.00073 + } + 1 { + -4 3.19567 -3.99828 + } + 2 { + -0.508583 3.19567 -3.99828 + } + 3 { + -0.508583 3.19567 4.00073 + } + } + { + { 0 1 2 3 { occluder14-ORG } } + } +} + occluder15 { + occluder { 1 } + occluder15-ORG { + 0 { + 1.25869 3.19567 3.98826 + } + 1 { + 1.25869 3.19567 -3.99828 + } + 2 { + 4 3.19567 -3.99828 + } + 3 { + 4 3.19567 3.98826 + } + } + { + { 0 1 2 3 { occluder15-ORG } } + } +} + occluder16 { + occluder { 1 } + occluder16-ORG { + 0 { + -4 5.26173 2.21946 + } + 1 { + -4 5.26173 -3.99828 + } + 2 { + 4 5.26173 -3.99828 + } + 3 { + 4 5.26173 2.21946 + } + } + { + { 0 1 2 3 { occluder16-ORG } } + } +} + occluder17 { + occluder { 1 } + occluder17-ORG { + 0 { + 1.78343 5.26173 3.98826 + } + 1 { + 1.78343 5.26173 -3.99828 + } + 2 { + 4 5.26173 -3.99828 + } + 3 { + 4 5.26173 3.98826 + } + } + { + { 0 1 2 3 { occluder17-ORG } } + } +} + occluder18 { + occluder { 1 } + occluder18-ORG { + 0 { + -3.99383 5.23279 2.0951 + } + 1 { + 1.8032 5.23279 2.0951 + } + 2 { + 1.8032 7.56072 2.0951 + } + 3 { + -3.99383 7.56072 2.0951 + } + } + { + { 0 1 2 3 { occluder18-ORG } } + } +} + occluder19 { + occluder { 1 } + occluder19-ORG { + 0 { + -3.93576 0.0379318 0.6175 + } + 1 { + 1.94837 0.0379318 0.6175 + } + 2 { + 1.94837 3.21608 0.6175 + } + 3 { + -3.93576 3.21608 0.6175 + } + } + { + { 0 1 2 3 { occluder19-ORG } } + } +} + occluder20 { + occluder { 1 } + occluder20-ORG { + 0 { + 11.5843 -0.215275 11.5843 + } + 1 { + 11.5843 -0.215275 -11.5843 + } + 2 { + -11.5843 -0.215275 -11.5843 + } + 3 { + -11.5843 -0.215275 11.5843 + } + } + { + { 0 1 2 3 { occluder20-ORG } } + } +} + occluder21 { + occluder { 1 } + occluder21-ORG { + 0 { + -3.93576 1.56418 0.5914 + } + 1 { + 3.94341 1.56418 0.5914 + } + 2 { + 3.94341 3.26834 0.5914 + } + 3 { + -3.93576 3.26834 0.5914 + } + } + { + { 0 1 2 3 { occluder21-ORG } } + } +} + occluder22 { + occluder { 1 } + occluder22-ORG { + 0 { + -0.694875 1.49418 2.11153 + } + 1 { + -0.694875 5.26968 2.11153 + } + 2 { + -0.694875 5.26968 0.21275 + } + 3 { + -0.694875 1.49418 0.21275 + } + } + { + { 0 1 2 3 { occluder22-ORG } } + } +} + occluder23 { + occluder { 1 } + occluder23-ORG { + 0 { + 0.756062 1.49418 2.11153 + } + 1 { + 0.756062 5.26968 2.11153 + } + 2 { + 0.756062 5.26968 0.21275 + } + 3 { + 0.756062 1.49418 0.21275 + } + } + { + { 0 1 2 3 { occluder23-ORG } } + } +} + occluder24 { + occluder { 1 } + occluder24-ORG { + 0 { + 1.60244 5.2309 2.14179 + } + 1 { + 1.60244 7.52263 2.14179 + } + 2 { + 1.60244 7.52263 0.394334 + } + 3 { + 1.60244 5.2309 0.394334 + } + } + { + { 0 1 2 3 { occluder24-ORG } } + } +} + occluder25 { + occluder { 1 } + occluder25-ORG { + 0 { + 1.60244 5.2309 -0.763555 + } + 1 { + 1.60244 7.52263 -0.763555 + } + 2 { + 1.60244 7.52263 -2.42694 + } + 3 { + 1.60244 5.2309 -2.42694 + } + } + { + { 0 1 2 3 { occluder25-ORG } } + } +} + occluder26 { + occluder { 1 } + occluder26-ORG { + 0 { + 1.78944 5.2309 -2.34074 + } + 1 { + 1.78944 7.52263 -2.34074 + } + 2 { + -2.28798 7.52263 -2.34074 + } + 3 { + -2.28798 5.2309 -2.34074 + } + } + { + { 0 1 2 3 { occluder26-ORG } } + } +} + occluder27 { + occluder { 1 } + occluder27-ORG { + 0 { + -2.15855 5.2309 1.03841 + } + 1 { + -2.15855 7.52263 1.03841 + } + 2 { + -2.15855 7.52263 -2.44292 + } + 3 { + -2.15855 5.2309 -2.44292 + } + } + { + { 0 1 2 3 { occluder27-ORG } } + } +} + occluder28 { + occluder { 1 } + occluder28-ORG { + 0 { + -3.98717 1.54945 3.90197 + } + 1 { + -3.98717 1.54945 -1.30625 + } + 2 { + 1.28595 1.54945 -1.30625 + } + 3 { + 1.28595 1.54945 3.90197 + } + } + { + { 0 1 2 3 { occluder28-ORG } } + } +} diff --git a/samples/culling/models/portals.egg b/samples/culling/models/portals.egg new file mode 100644 index 0000000000..c7536d79dc --- /dev/null +++ b/samples/culling/models/portals.egg @@ -0,0 +1,674 @@ + { Y-Up } + + portal_10to11_1 { + portal { 1 } + portal_10to11_1-ORG { + 0 { + -2.36149 5.34053 2.04979 + { -1 0 0 } + { 0 0 1 1 } + } + 1 { + -2.36149 7.09573 2.04979 + { -1 0 0 } + { 0.001 0.001 0.999 1 } + } + 2 { + -2.36149 7.09573 1.0558 + { -1 0 0 } + { 0 0 1 1 } + } + 3 { + -2.36149 5.34053 1.0558 + { -1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_10to11_1-ORG } } + } +} + portal_1to2_1 { + portal { 1 } + portal_1to2_1-ORG { + 0 { + 4 4.44089e-016 -0.838305 + { 1 0 0 } + { 0 0 1 1 } + } + 1 { + 4 1.48611 -0.838305 + { 1 0 0 } + { 0 0 1 1 } + } + 2 { + 4 1.48611 0.527741 + { 1 0 0 } + { 0.001 0.001 0.999 1 } + } + 3 { + 4 4.44089e-016 0.527741 + { 1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_1to2_1-ORG } } + } +} + portal_1to2_2 { + portal { 1 } + portal_1to2_2-ORG { + 0 { + -4 0 0.527741 + { -1 0 0 } + { 0 0 1 1 } + } + 1 { + -4 1.48611 0.527741 + { -1 0 0 } + { 0.001 0.001 0.999 1 } + } + 2 { + -4 1.48611 -0.838305 + { -1 0 0 } + { 0 0 1 1 } + } + 3 { + -4 0 -0.838305 + { -1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_1to2_2-ORG } } + } +} + portal_2to3_1 { + portal { 1 } + portal_2to3_1-ORG { + 0 { + 0.243417 0 -0.838305 + { 0 -5.37589e-011 1 } + { 0 0 1 1 } + } + 1 { + 0.243412 1.48611 -0.838305 + { 0 -5.37589e-011 1 } + { 0 0 1 1 } + } + 2 { + -1.03253 1.48611 -0.838305 + { 0 -5.37589e-011 1 } + { 0.001 0.001 0.999 1 } + } + 3 { + -1.02178 0 -0.838305 + { 0 -5.37589e-011 1 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_2to3_1-ORG } } + } +} + portal_2to4_1 { + portal { 1 } + portal_2to4_1-ORG { + 0 { + 2.04443 0 0.527741 + { 0 1.40115e-009 -1 } + { 0 0 1 1 } + } + 1 { + 2.04443 1.48611 0.527741 + { 0 1.40115e-009 -1 } + { 0.001 0.001 0.999 1 } + } + 2 { + 2.99397 1.48611 0.527741 + { 0 1.40115e-009 -1 } + { 0 0 1 1 } + } + 3 { + 2.99397 0 0.527741 + { 0 1.40115e-009 -1 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_2to4_1-ORG } } + } +} + portal_4to5_1 { + portal { 1 } + portal_4to5_1-ORG { + 0 { + 0.519489 3.3704 0.115602 + { 0 -0.00293795 0.999996 } + { 0 0 1 1 } + } + 1 { + 0.519489 4.88145 0.120042 + { 0 -0.00293795 0.999996 } + { 0 0 1 1 } + } + 2 { + -0.423826 4.88145 0.120042 + { 0 -0.00293795 0.999996 } + { 0.001 0.001 0.999 1 } + } + 3 { + -0.423812 3.3704 0.115602 + { 0 -0.00293795 0.999996 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_4to5_1-ORG } } + } +} + portal_5to6_1 { + portal { 1 } + portal_5to6_1-ORG { + 0 { + 0.997627 3.3704 0.115602 + { 1.00717e-006 1.20465e-006 -1 } + { 0 0 1 1 } + } + 1 { + 0.997627 5.12958 0.115602 + { 1.00717e-006 1.20465e-006 -1 } + { 0.001 0.001 0.999 1 } + } + 2 { + 2.99397 5.12958 0.115606 + { 1.00717e-006 1.20465e-006 -1 } + { 0 0 1 1 } + } + 3 { + 2.99397 3.3704 0.115602 + { 1.00717e-006 1.20465e-006 -1 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_5to6_1-ORG } } + } +} + portal_5to7_1 { + portal { 1 } + portal_5to7_1-ORG { + 0 { + -3.36172 3.3704 0.115602 + { 1.02518e-006 -3.5707e-006 -1 } + { 0 0 1 1 } + } + 1 { + -3.36172 5.12958 0.115593 + { 1.02518e-006 -3.5707e-006 -1 } + { 0.001 0.001 0.999 1 } + } + 2 { + -0.900953 5.12958 0.115599 + { 1.02518e-006 -3.5707e-006 -1 } + { 0 0 1 1 } + } + 3 { + -0.900963 3.3704 0.115602 + { 1.02518e-006 -3.5707e-006 -1 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_5to7_1-ORG } } + } +} + portal_7to8_1 { + portal { 1 } + portal_7to8_1-ORG { + 0 { + -2.30748 3.3704 3.18072 + { -1 1.34401e-005 1.02127e-006 } + { 0 0 1 1 } + } + 1 { + -2.30746 5.12958 3.18072 + { -1 1.34401e-005 1.02127e-006 } + { 0.001 0.001 0.999 1 } + } + 2 { + -2.30746 5.12958 2.23852 + { -1 1.34401e-005 1.02127e-006 } + { 0 0 1 1 } + } + 3 { + -2.30748 3.3704 2.23852 + { -1 1.34401e-005 1.02127e-006 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_7to8_1-ORG } } + } +} + portal_8to9_1 { + portal { 1 } + portal_8to9_1-ORG { + 0 { + 1.82483 5.34053 3.18072 + { -1 0 0 } + { 0 0 1 1 } + } + 1 { + 1.82483 7.09573 3.18072 + { -1 0 0 } + { 0.001 0.001 0.999 1 } + } + 2 { + 1.82483 7.09573 2.23852 + { -1 0 0 } + { 0 0 1 1 } + } + 3 { + 1.82483 5.34053 2.23852 + { -1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_8to9_1-ORG } } + } +} + portal_9to10_1 { + portal { 1 } + portal_9to10_1-ORG { + 0 { + -0.485624 5.34053 -3.42013 + { 1 0 0 } + { 0 0 1 1 } + } + 1 { + -0.485624 7.09573 -3.42013 + { 1 0 0 } + { 0 0 1 1 } + } + 2 { + -0.485624 7.09573 -2.49425 + { 1 0 0 } + { 0.001 0.001 0.999 1 } + } + 3 { + -0.485624 5.34053 -2.49425 + { 1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_9to10_1-ORG } } + } +} + portal_9to11_1 { + portal { 1 } + portal_9to11_1-ORG { + 0 { + 1.82483 5.34053 -0.718624 + { 1 0 0 } + { 0 0 1 1 } + } + 1 { + 1.82483 7.09573 -0.718624 + { 1 0 0 } + { 0 0 1 1 } + } + 2 { + 1.82483 7.09573 0.350091 + { 1 0 0 } + { 0.001 0.001 0.999 1 } + } + 3 { + 1.82483 5.34053 0.350091 + { 1 0 0 } + { 0 0 1 1 } + } + } + { + { 0 1 2 3 { portal_9to11_1-ORG } } + } +} + portal_11to9_1 { + portal { 1 } + portal_11to9_1-ORG { + 0 { + 1.82483 5.34053 0.350091 + { -1 0 0 } + { 1 0 0 1 } + } + 1 { + 1.82483 7.09573 0.350091 + { -1 0 0 } + { 0.999 0.001 0.001 1 } + } + 2 { + 1.82483 7.09573 -0.718624 + { -1 0 0 } + { 1 0 0 1 } + } + 3 { + 1.82483 5.34053 -0.718624 + { -1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_11to9_1-ORG } } + } +} + portal_10to9_1 { + portal { 1 } + portal_10to9_1-ORG { + 0 { + -0.485624 5.34053 -2.49425 + { -1 0 0 } + { 1 0 0 1 } + } + 1 { + -0.485624 7.09573 -2.49425 + { -1 0 0 } + { 0.999 0.001 0.001 1 } + } + 2 { + -0.485624 7.09573 -3.42013 + { -1 0 0 } + { 1 0 0 1 } + } + 3 { + -0.485624 5.34053 -3.42013 + { -1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_10to9_1-ORG } } + } +} + portal_9to8_1 { + portal { 1 } + portal_9to8_1-ORG { + 0 { + 1.82483 5.34053 2.23852 + { 1 0 0 } + { 1 0 0 1 } + } + 1 { + 1.82483 7.09573 2.23852 + { 1 0 0 } + { 1 0 0 1 } + } + 2 { + 1.82483 7.09573 3.18072 + { 1 0 0 } + { 0.999 0.001 0.001 1 } + } + 3 { + 1.82483 5.34053 3.18072 + { 1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_9to8_1-ORG } } + } +} + portal_8to7_1 { + portal { 1 } + portal_8to7_1-ORG { + 0 { + -2.30748 3.3704 2.23852 + { 1 -1.34401e-005 -1.02127e-006 } + { 1 0 0 1 } + } + 1 { + -2.30746 5.12958 2.23852 + { 1 -1.34401e-005 -1.02127e-006 } + { 1 0 0 1 } + } + 2 { + -2.30746 5.12958 3.18072 + { 1 -1.34401e-005 -1.02127e-006 } + { 0.999 0.001 0.001 1 } + } + 3 { + -2.30748 3.3704 3.18072 + { 1 -1.34401e-005 -1.02127e-006 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_8to7_1-ORG } } + } +} + portal_7to5_1 { + portal { 1 } + portal_7to5_1-ORG { + 0 { + -0.900963 3.3704 0.115602 + { -1.02518e-006 3.5707e-006 1 } + { 1 0 0 1 } + } + 1 { + -0.900953 5.12958 0.115599 + { -1.02518e-006 3.5707e-006 1 } + { 1 0 0 1 } + } + 2 { + -3.36172 5.12958 0.115593 + { -1.02518e-006 3.5707e-006 1 } + { 0.999 0.001 0.001 1 } + } + 3 { + -3.36172 3.3704 0.115602 + { -1.02518e-006 3.5707e-006 1 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_7to5_1-ORG } } + } +} + portal_6to5_1 { + portal { 1 } + portal_6to5_1-ORG { + 0 { + 2.99397 3.3704 0.115602 + { -1.00717e-006 -1.20465e-006 1 } + { 1 0 0 1 } + } + 1 { + 2.99397 5.12958 0.115606 + { -1.00717e-006 -1.20465e-006 1 } + { 1 0 0 1 } + } + 2 { + 0.997627 5.12958 0.115602 + { -1.00717e-006 -1.20465e-006 1 } + { 0.999 0.001 0.001 1 } + } + 3 { + 0.997627 3.3704 0.115602 + { -1.00717e-006 -1.20465e-006 1 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_6to5_1-ORG } } + } +} + portal_5to4_1 { + portal { 1 } + portal_5to4_1-ORG { + 0 { + -0.423812 3.3704 0.115602 + { 0 0.00293795 -0.999996 } + { 1 0 0 1 } + } + 1 { + -0.423826 4.88145 0.120042 + { 0 0.00293795 -0.999996 } + { 0.999 0.001 0.001 1 } + } + 2 { + 0.519489 4.88145 0.120042 + { 0 0.00293795 -0.999996 } + { 1 0 0 1 } + } + 3 { + 0.519489 3.3704 0.115602 + { 0 0.00293795 -0.999996 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_5to4_1-ORG } } + } +} + portal_4to2_1 { + portal { 1 } + portal_4to2_1-ORG { + 0 { + 2.99397 0 0.527741 + { 0 -1.40115e-009 1 } + { 1 0 0 1 } + } + 1 { + 2.99397 1.48611 0.527741 + { 0 -1.40115e-009 1 } + { 1 0 0 1 } + } + 2 { + 2.04443 1.48611 0.527741 + { 0 -1.40115e-009 1 } + { 0.999 0.001 0.001 1 } + } + 3 { + 2.04443 0 0.527741 + { 0 -1.40115e-009 1 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_4to2_1-ORG } } + } +} + portal_3to2_1 { + portal { 1 } + portal_3to2_1-ORG { + 0 { + -1.02178 0 -0.838305 + { 0 5.37589e-011 -1 } + { 1 0 0 1 } + } + 1 { + -1.03253 1.48611 -0.838305 + { 0 5.37589e-011 -1 } + { 0.999 0.001 0.001 1 } + } + 2 { + 0.243412 1.48611 -0.838305 + { 0 5.37589e-011 -1 } + { 1 0 0 1 } + } + 3 { + 0.243417 0 -0.838305 + { 0 5.37589e-011 -1 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_3to2_1-ORG } } + } +} + portal_2to1_2 { + portal { 1 } + portal_2to1_2-ORG { + 0 { + -4 0 -0.838305 + { 1 0 0 } + { 1 0 0 1 } + } + 1 { + -4 1.48611 -0.838305 + { 1 0 0 } + { 1 0 0 1 } + } + 2 { + -4 1.48611 0.527741 + { 1 0 0 } + { 0.999 0.001 0.001 1 } + } + 3 { + -4 0 0.527741 + { 1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_2to1_2-ORG } } + } +} + portal_2to1_1 { + portal { 1 } + portal_2to1_1-ORG { + 0 { + 4 4.44089e-016 0.527741 + { -1 0 0 } + { 1 0 0 1 } + } + 1 { + 4 1.48611 0.527741 + { -1 0 0 } + { 0.999 0.001 0.001 1 } + } + 2 { + 4 1.48611 -0.838305 + { -1 0 0 } + { 1 0 0 1 } + } + 3 { + 4 4.44089e-016 -0.838305 + { -1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_2to1_1-ORG } } + } +} + portal_11to10_1 { + portal { 1 } + portal_11to10_1-ORG { + 0 { + -2.36149 5.34053 1.0558 + { 1 0 0 } + { 1 0 0 1 } + } + 1 { + -2.36149 7.09573 1.0558 + { 1 0 0 } + { 1 0 0 1 } + } + 2 { + -2.36149 7.09573 2.04979 + { 1 0 0 } + { 0.999 0.001 0.001 1 } + } + 3 { + -2.36149 5.34053 2.04979 + { 1 0 0 } + { 1 0 0 1 } + } + } + { + { 0 1 2 3 { portal_11to10_1-ORG } } + } +} diff --git a/samples/culling/models/tex_0.png b/samples/culling/models/tex_0.png new file mode 100644 index 0000000000..48239f4784 Binary files /dev/null and b/samples/culling/models/tex_0.png differ diff --git a/samples/culling/models/tex_1.png b/samples/culling/models/tex_1.png new file mode 100644 index 0000000000..4f8ca64d38 Binary files /dev/null and b/samples/culling/models/tex_1.png differ diff --git a/samples/culling/models/tex_2.png b/samples/culling/models/tex_2.png new file mode 100644 index 0000000000..a628c0c563 Binary files /dev/null and b/samples/culling/models/tex_2.png differ diff --git a/samples/culling/models/tex_3.png b/samples/culling/models/tex_3.png new file mode 100644 index 0000000000..58b46129b6 Binary files /dev/null and b/samples/culling/models/tex_3.png differ diff --git a/samples/culling/models/tex_4.png b/samples/culling/models/tex_4.png new file mode 100644 index 0000000000..58b46129b6 Binary files /dev/null and b/samples/culling/models/tex_4.png differ diff --git a/samples/culling/models/tex_5.png b/samples/culling/models/tex_5.png new file mode 100644 index 0000000000..a628c0c563 Binary files /dev/null and b/samples/culling/models/tex_5.png differ diff --git a/samples/culling/models/tex_6.png b/samples/culling/models/tex_6.png new file mode 100644 index 0000000000..4f8ca64d38 Binary files /dev/null and b/samples/culling/models/tex_6.png differ diff --git a/samples/culling/models/tex_7.png b/samples/culling/models/tex_7.png new file mode 100644 index 0000000000..48239f4784 Binary files /dev/null and b/samples/culling/models/tex_7.png differ diff --git a/samples/culling/occluder_culling.py b/samples/culling/occluder_culling.py new file mode 100755 index 0000000000..9743c159c0 --- /dev/null +++ b/samples/culling/occluder_culling.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +""" +Author: Josh Enes +Last Updated: 2015-03-13 + +This is a demo of Panda's occluder-culling system. It demonstrates loading +occluder from an EGG file and adding them to a CullTraverser. +""" + +# Load PRC data +from panda3d.core import loadPrcFileData +loadPrcFileData('', 'window-title Occluder Demo') +loadPrcFileData('', 'sync-video false') +loadPrcFileData('', 'show-frame-rate-meter true') +loadPrcFileData('', 'texture-minfilter linear-mipmap-linear') +#loadPrcFileData('', 'fake-view-frustum-cull true') # show culled nodes in red + +# Import needed modules +import random +from direct.showbase.ShowBase import ShowBase +from direct.gui.OnscreenText import OnscreenText +from panda3d.core import PerspectiveLens, TextNode, \ +TexGenAttrib, TextureStage, TransparencyAttrib, LPoint3, Texture + + +def add_instructions(pos, msg): + """Function to put instructions on the screen.""" + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.05) + +def add_title(text): + """Function to put title on the screen.""" + return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + + +class Game(ShowBase): + """Sets up the game, camera, controls, and loads models.""" + def __init__(self): + ShowBase.__init__(self) + self.xray_mode = False + self.show_model_bounds = False + + # Display instructions + add_title("Panda3D Tutorial: Occluder Culling") + add_instructions(0.06, "[Esc]: Quit") + add_instructions(0.12, "[W]: Move Forward") + add_instructions(0.18, "[A]: Move Left") + add_instructions(0.24, "[S]: Move Right") + add_instructions(0.30, "[D]: Move Back") + add_instructions(0.36, "Arrow Keys: Look Around") + add_instructions(0.42, "[F]: Toggle Wireframe") + add_instructions(0.48, "[X]: Toggle X-Ray Mode") + add_instructions(0.54, "[B]: Toggle Bounding Volumes") + + # Setup controls + self.keys = {} + for key in ['arrow_left', 'arrow_right', 'arrow_up', 'arrow_down', + 'a', 'd', 'w', 's']: + self.keys[key] = 0 + self.accept(key, self.push_key, [key, 1]) + self.accept('shift-%s' % key, self.push_key, [key, 1]) + self.accept('%s-up' % key, self.push_key, [key, 0]) + self.accept('f', self.toggleWireframe) + self.accept('x', self.toggle_xray_mode) + self.accept('b', self.toggle_model_bounds) + self.accept('escape', __import__('sys').exit, [0]) + self.disableMouse() + + # Setup camera + self.lens = PerspectiveLens() + self.lens.setFov(60) + self.lens.setNear(0.01) + self.lens.setFar(1000.0) + self.cam.node().setLens(self.lens) + self.camera.setPos(-9, -0.5, 1) + self.heading = -95.0 + self.pitch = 0.0 + + # Load level geometry + self.level_model = self.loader.loadModel('models/level') + self.level_model.reparentTo(self.render) + self.level_model.setTexGen(TextureStage.getDefault(), + TexGenAttrib.MWorldPosition) + self.level_model.setTexProjector(TextureStage.getDefault(), + self.render, self.level_model) + self.level_model.setTexScale(TextureStage.getDefault(), 4) + tex = self.loader.load3DTexture('models/tex_#.png') + self.level_model.setTexture(tex) + + # Load occluders + occluder_model = self.loader.loadModel('models/occluders') + occluder_nodepaths = occluder_model.findAllMatches('**/+OccluderNode') + for occluder_nodepath in occluder_nodepaths: + self.render.setOccluder(occluder_nodepath) + occluder_nodepath.node().setDoubleSided(True) + + # Randomly spawn some models to test the occluders + self.models = [] + box_model = self.loader.loadModel('box') + + for dummy in xrange(0, 500): + pos = LPoint3((random.random() - 0.5) * 9, + (random.random() - 0.5) * 9, + random.random() * 8) + box = box_model.copy_to(self.render) + box.setScale(random.random() * 0.2 + 0.1) + box.setPos(pos) + box.setHpr(random.random() * 360, + random.random() * 360, + random.random() * 360) + box.reparentTo(self.render) + self.models.append(box) + + self.taskMgr.add(self.update, 'main loop') + + def push_key(self, key, value): + """Stores a value associated with a key.""" + self.keys[key] = value + + def update(self, task): + """Updates the camera based on the keyboard input.""" + delta = globalClock.getDt() + move_x = delta * 3 * -self.keys['a'] + delta * 3 * self.keys['d'] + move_z = delta * 3 * self.keys['s'] + delta * 3 * -self.keys['w'] + self.camera.setPos(self.camera, move_x, -move_z, 0) + self.heading += (delta * 90 * self.keys['arrow_left'] + + delta * 90 * -self.keys['arrow_right']) + self.pitch += (delta * 90 * self.keys['arrow_up'] + + delta * 90 * -self.keys['arrow_down']) + self.camera.setHpr(self.heading, self.pitch, 0) + return task.cont + + def toggle_xray_mode(self): + """Toggle X-ray mode on and off. This is useful for seeing the + effectiveness of the occluder culling.""" + self.xray_mode = not self.xray_mode + if self.xray_mode: + self.level_model.setColorScale((1, 1, 1, 0.5)) + self.level_model.setTransparency(TransparencyAttrib.MDual) + else: + self.level_model.setColorScaleOff() + self.level_model.setTransparency(TransparencyAttrib.MNone) + + def toggle_model_bounds(self): + """Toggle bounding volumes on and off on the models.""" + self.show_model_bounds = not self.show_model_bounds + if self.show_model_bounds: + for model in self.models: + model.showBounds() + else: + for model in self.models: + model.hideBounds() + +game = Game() +game.run() diff --git a/samples/culling/portal_culling.py b/samples/culling/portal_culling.py new file mode 100755 index 0000000000..26ae074dd8 --- /dev/null +++ b/samples/culling/portal_culling.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python + +""" +Author: Josh Enes +Last Updated: 2015-03-13 + +This is a demo of Panda's portal-culling system. It demonstrates loading +portals from an EGG file, and shows an example method of selecting the +current cell using geoms and a collision ray. +""" + +# Some config options which can be changed. +ENABLE_PORTALS = True # Set False to disable portal culling and see FPS drop! +DEBUG_PORTALS = False # Set True to see visually which portals are used + +# Load PRC data +from panda3d.core import loadPrcFileData +if ENABLE_PORTALS: + loadPrcFileData('', 'allow-portal-cull true') + if DEBUG_PORTALS: + loadPrcFileData('', 'debug-portal-cull true') +loadPrcFileData('', 'window-title Portal Demo') +loadPrcFileData('', 'sync-video false') +loadPrcFileData('', 'show-frame-rate-meter true') +loadPrcFileData('', 'texture-minfilter linear-mipmap-linear') + +# Import needed modules +import random +from direct.showbase.ShowBase import ShowBase +from direct.gui.OnscreenText import OnscreenText +from panda3d.core import PerspectiveLens, NodePath, LVector3, LPoint3, \ + TexGenAttrib, TextureStage, TransparencyAttrib, CollisionTraverser, \ + CollisionHandlerQueue, TextNode, CollisionRay, CollisionNode + + +def add_instructions(pos, msg): + """Function to put instructions on the screen.""" + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.05) + +def add_title(text): + """Function to put title on the screen.""" + return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + + +class Game(ShowBase): + """Sets up the game, camera, controls, and loads models.""" + def __init__(self): + ShowBase.__init__(self) + self.cellmanager = CellManager(self) + self.xray_mode = False + self.show_model_bounds = False + + # Display instructions + add_title("Panda3D Tutorial: Portal Culling") + add_instructions(0.06, "[Esc]: Quit") + add_instructions(0.12, "[W]: Move Forward") + add_instructions(0.18, "[A]: Move Left") + add_instructions(0.24, "[S]: Move Right") + add_instructions(0.30, "[D]: Move Back") + add_instructions(0.36, "Arrow Keys: Look Around") + add_instructions(0.42, "[F]: Toggle Wireframe") + add_instructions(0.48, "[X]: Toggle X-Ray Mode") + add_instructions(0.54, "[B]: Toggle Bounding Volumes") + + # Setup controls + self.keys = {} + for key in ['arrow_left', 'arrow_right', 'arrow_up', 'arrow_down', + 'a', 'd', 'w', 's']: + self.keys[key] = 0 + self.accept(key, self.push_key, [key, 1]) + self.accept('shift-%s' % key, self.push_key, [key, 1]) + self.accept('%s-up' % key, self.push_key, [key, 0]) + self.accept('f', self.toggleWireframe) + self.accept('x', self.toggle_xray_mode) + self.accept('b', self.toggle_model_bounds) + self.accept('escape', __import__('sys').exit, [0]) + self.disableMouse() + + # Setup camera + lens = PerspectiveLens() + lens.setFov(60) + lens.setNear(0.01) + lens.setFar(1000.0) + self.cam.node().setLens(lens) + self.camera.setPos(-9, -0.5, 1) + self.heading = -95.0 + self.pitch = 0.0 + + # Load level geometry + self.level_model = self.loader.loadModel('models/level') + self.level_model.reparentTo(self.render) + self.level_model.setTexGen(TextureStage.getDefault(), + TexGenAttrib.MWorldPosition) + self.level_model.setTexProjector(TextureStage.getDefault(), + self.render, self.level_model) + self.level_model.setTexScale(TextureStage.getDefault(), 4) + tex = self.loader.load3DTexture('models/tex_#.png') + self.level_model.setTexture(tex) + + # Load cells + self.cellmanager.load_cells_from_model('models/cells') + # Load portals + self.cellmanager.load_portals_from_model('models/portals') + + # Randomly spawn some models to test the portals + self.models = [] + for dummy in xrange(0, 500): + pos = LPoint3((random.random() - 0.5) * 6, + (random.random() - 0.5) * 6, + random.random() * 7) + cell = self.cellmanager.get_cell(pos) + if cell is None: # skip if the random position is not over a cell + continue + dist = self.cellmanager.get_dist_to_cell(pos) + if dist > 1.5: # skip if the random position is too far from ground + continue + box = self.loader.loadModel('box') + box.setScale(random.random() * 0.2 + 0.1) + box.setPos(pos) + box.setHpr(random.random() * 360, + random.random() * 360, + random.random() * 360) + box.reparentTo(cell.nodepath) + self.models.append(box) + self.taskMgr.add(self.update, 'main loop') + + def push_key(self, key, value): + """Stores a value associated with a key.""" + self.keys[key] = value + + def update(self, task): + """Updates the camera based on the keyboard input. Once this is + done, then the CellManager's update function is called.""" + delta = globalClock.getDt() + move_x = delta * 3 * -self.keys['a'] + delta * 3 * self.keys['d'] + move_z = delta * 3 * self.keys['s'] + delta * 3 * -self.keys['w'] + self.camera.setPos(self.camera, move_x, -move_z, 0) + self.heading += (delta * 90 * self.keys['arrow_left'] + + delta * 90 * -self.keys['arrow_right']) + self.pitch += (delta * 90 * self.keys['arrow_up'] + + delta * 90 * -self.keys['arrow_down']) + self.camera.setHpr(self.heading, self.pitch, 0) + if ENABLE_PORTALS: + self.cellmanager.update() + return task.cont + + def toggle_xray_mode(self): + """Toggle X-ray mode on and off. This is useful for seeing the + effectiveness of the portal culling.""" + self.xray_mode = not self.xray_mode + if self.xray_mode: + self.level_model.setColorScale((1, 1, 1, 0.5)) + self.level_model.setTransparency(TransparencyAttrib.MDual) + else: + self.level_model.setColorScaleOff() + self.level_model.setTransparency(TransparencyAttrib.MNone) + + def toggle_model_bounds(self): + """Toggle bounding volumes on and off on the models.""" + self.show_model_bounds = not self.show_model_bounds + if self.show_model_bounds: + for model in self.models: + model.showBounds() + else: + for model in self.models: + model.hideBounds() + + +class CellManager(object): + """Creates a collision ray and collision traverser to use for + selecting the current cell.""" + def __init__(self, game): + self.game = game + self.cells = {} + self.cells_by_collider = {} + self.cell_picker_world = NodePath('cell_picker_world') + self.ray = CollisionRay() + self.ray.setDirection(LVector3.down()) + cnode = CollisionNode('cell_raycast_cnode') + self.ray_nodepath = self.cell_picker_world.attachNewNode(cnode) + self.ray_nodepath.node().addSolid(self.ray) + self.ray_nodepath.node().setIntoCollideMask(0) # not for colliding into + self.ray_nodepath.node().setFromCollideMask(1) + self.traverser = CollisionTraverser('traverser') + self.last_known_cell = None + + def add_cell(self, collider, name): + """Add a new cell.""" + cell = Cell(self, name, collider) + self.cells[name] = cell + self.cells_by_collider[collider.node()] = cell + + def get_cell(self, pos): + """Given a position, return the nearest cell below that position. + If no cell is found, returns None.""" + self.ray.setOrigin(pos) + queue = CollisionHandlerQueue() + self.traverser.addCollider(self.ray_nodepath, queue) + self.traverser.traverse(self.cell_picker_world) + self.traverser.removeCollider(self.ray_nodepath) + queue.sortEntries() + if not queue.getNumEntries(): + return None + entry = queue.getEntry(0) + cnode = entry.getIntoNode() + try: + return self.cells_by_collider[cnode] + except KeyError: + raise Warning('collision ray collided with something ' + 'other than a cell: %s' % cnode) + + def get_dist_to_cell(self, pos): + """Given a position, return the distance to the nearest cell + below that position. If no cell is found, returns None.""" + self.ray.setOrigin(pos) + queue = CollisionHandlerQueue() + self.traverser.addCollider(self.ray_nodepath, queue) + self.traverser.traverse(self.cell_picker_world) + self.traverser.removeCollider(self.ray_nodepath) + queue.sortEntries() + if not queue.getNumEntries(): + return None + entry = queue.getEntry(0) + return (entry.getSurfacePoint(self.cell_picker_world) - pos).length() + + def load_cells_from_model(self, modelpath): + """Loads cells from an EGG file. Cells must be named in the + format "cell#" to be loaded by this function.""" + cell_model = self.game.loader.loadModel(modelpath) + for collider in cell_model.findAllMatches('**/+GeomNode'): + name = collider.getName() + if name.startswith('cell'): + self.add_cell(collider, name[4:]) + cell_model.removeNode() + + def load_portals_from_model(self, modelpath): + """Loads portals from an EGG file. Portals must be named in the + format "portal_#to#_*" to be loaded by this function, whereby the + first # is the from cell, the second # is the into cell, and * can + be anything.""" + portal_model = loader.loadModel(modelpath) + portal_nodepaths = portal_model.findAllMatches('**/+PortalNode') + for portal_nodepath in portal_nodepaths: + name = portal_nodepath.getName() + if name.startswith('portal_'): + from_cell_id, into_cell_id = name.split('_')[1].split('to') + try: + from_cell = self.cells[from_cell_id] + except KeyError: + print ('could not load portal "%s" because cell "%s"' + 'does not exist' % (name, from_cell_id)) + continue + try: + into_cell = self.cells[into_cell_id] + except KeyError: + print ('could not load portal "%s" because cell "%s"' + 'does not exist' % (name, into_cell_id)) + continue + from_cell.add_portal(portal_nodepath, into_cell) + portal_model.removeNode() + + def update(self): + """Show the cell the camera is currently in and hides the rest. + If the camera is not in a cell, use the last known cell that the + camera was in. If the camera has not yet been in a cell, then all + cells will be hidden.""" + camera_pos = self.game.camera.getPos(self.game.render) + for cell in self.cells: + self.cells[cell].nodepath.hide() + current_cell = self.get_cell(camera_pos) + if current_cell is None: + if self.last_known_cell is None: + return + self.last_known_cell.nodepath.show() + else: + self.last_known_cell = current_cell + current_cell.nodepath.show() + + +class Cell(object): + """The Cell class is a handy way to keep an association between + all the related nodes and information of a cell.""" + def __init__(self, cellmanager, name, collider): + self.cellmanager = cellmanager + self.name = name + self.collider = collider + self.collider.reparentTo(self.cellmanager.cell_picker_world) + self.collider.setCollideMask(1) + self.collider.hide() + self.nodepath = NodePath('cell_%s_root' % name) + self.nodepath.reparentTo(self.cellmanager.game.render) + self.portals = [] + + def add_portal(self, portal, cell_out): + """Add a portal from this cell going into another one.""" + portal.reparentTo(self.nodepath) + portal.node().setCellIn(self.nodepath) + portal.node().setCellOut(cell_out.nodepath) + self.portals.append(portal) + +game = Game() +game.run() diff --git a/samples/disco-lights/main.py b/samples/disco-lights/main.py new file mode 100755 index 0000000000..8e3bad3cf6 --- /dev/null +++ b/samples/disco-lights/main.py @@ -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() diff --git a/samples/disco-lights/models/disco_hall.egg.pz b/samples/disco-lights/models/disco_hall.egg.pz new file mode 100644 index 0000000000..a02ef2fd46 Binary files /dev/null and b/samples/disco-lights/models/disco_hall.egg.pz differ diff --git a/samples/disco-lights/models/sphere.egg.pz b/samples/disco-lights/models/sphere.egg.pz new file mode 100644 index 0000000000..2257d21365 Binary files /dev/null and b/samples/disco-lights/models/sphere.egg.pz differ diff --git a/samples/distortion/distortion.sha b/samples/distortion/distortion.sha new file mode 100644 index 0000000000..a4c5b081f6 --- /dev/null +++ b/samples/distortion/distortion.sha @@ -0,0 +1,53 @@ +//Cg +// +// time +// +// You need to pass the frame time here. +// +// desat.x +// +// Desaturation level. If zero, the bloom's color is equal to +// the color of the input pixel. If one, the bloom's color is +// white. +// +// trigger.x +// +// Must be equal to mintrigger. +// +// mintrigger is the minimum brightness to trigger a bloom, +// and maxtrigger is the brightness at which the bloom +// reaches maximum intensity. +// +// trigger.y +// +// Must be equal to (1.0/(maxtrigger-mintrigger)) where +// +// mintrigger is the minimum brightness to trigger a bloom, +// and maxtrigger is the brightness at which the bloom +// reaches maximum intensity. +// + +void vshader(float4 vtx_position : POSITION, + uniform float4x4 mat_modelproj, + uniform float4x4 trans_model_to_clip, + out float4 l_position : POSITION, + out float4 l_texcoord0 : TEXCOORD0) +{ + l_position = mul(mat_modelproj, vtx_position); + l_texcoord0 = mul(trans_model_to_clip, vtx_position); + l_texcoord0.z = l_texcoord0.w; +} + +void fshader(float4 l_texcoord0 : TEXCOORD0, + uniform sampler2D k_screen : TEXUNIT1, + uniform sampler2D k_waves : TEXUNIT0, + uniform float4 texpad_screen, + in uniform float sys_time, + out float4 o_color : COLOR) +{ + float3 screen = l_texcoord0.xyz / l_texcoord0.w; + float2 texcoords = float2(screen.xy) * texpad_screen.xy + texpad_screen.xy; + float4 disturbance = tex2D(k_waves, texcoords); + //o_color = tex2D(k_screen, texcoords + disturbance.xy * 0.05 * disturbance.z * sys_time.x * 1); + o_color = tex2D(k_screen, texcoords + disturbance.xy * 0.05 * disturbance.z * sin(sys_time.x) * 1); +} diff --git a/samples/distortion/main.py b/samples/distortion/main.py new file mode 100755 index 0000000000..387156aa25 --- /dev/null +++ b/samples/distortion/main.py @@ -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() diff --git a/samples/distortion/models/boat.egg.pz b/samples/distortion/models/boat.egg.pz new file mode 100644 index 0000000000..a5604dc525 Binary files /dev/null and b/samples/distortion/models/boat.egg.pz differ diff --git a/samples/distortion/models/ocean.jpg b/samples/distortion/models/ocean.jpg new file mode 100644 index 0000000000..e84a1a91cd Binary files /dev/null and b/samples/distortion/models/ocean.jpg differ diff --git a/samples/distortion/models/plane.egg.pz b/samples/distortion/models/plane.egg.pz new file mode 100644 index 0000000000..0a88c05d92 --- /dev/null +++ b/samples/distortion/models/plane.egg.pz @@ -0,0 +1,2 @@ +xÚ’Ákƒ0ÆïƒþÞc»[¶zÒÒÂN#£©˜¼³Q)þï}±Ñ‰l‡…h4ß÷|¿|D¾A´§O-œÜ·“ª€+¼&Ýbñ@ß RR;Òi°T¢¹¬*0µÐ2Uïá…¤%UøÏøÖâ—) ¢‡>•Þ~sVèæŒVõ}¦U¦/`¡??Jëä¥D¬kÿ!Œdé7M¨ë +`£déã}ÊF¿TÔ>WF#a}²àŽƒ>wÛç'/³0«›£ó9:‚̢ȫ 9.3‹Ì¼ž‘#Àÿˆ|Ÿy‰u[¡.~NÿONØèNž½¹¢ýæÔ•‡õ/?T7AùûxÒ³I \ No newline at end of file diff --git a/samples/distortion/models/water.png b/samples/distortion/models/water.png new file mode 100644 index 0000000000..1ba0c0f65e Binary files /dev/null and b/samples/distortion/models/water.png differ diff --git a/samples/fireflies/light.sha b/samples/fireflies/light.sha new file mode 100644 index 0000000000..e074b15abd --- /dev/null +++ b/samples/fireflies/light.sha @@ -0,0 +1,45 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + out float4 l_position : POSITION, + out float4 l_pos : TEXCOORD0, + uniform float4x4 mat_modelproj, + uniform float4x4 trans_model_to_clip) +{ + l_position=mul(mat_modelproj, vtx_position); + l_pos=mul(trans_model_to_clip, vtx_position); + l_pos.z = l_pos.w; +} + +void fshader(float4 l_pos: TEXCOORD0, + float4 l_scale: TEXCOORD1, + uniform sampler2D k_texnormal : TEXUNIT0, + uniform sampler2D k_texalbedo : TEXUNIT1, + uniform sampler2D k_texdepth : TEXUNIT2, + uniform float4 texpad_texnormal, + uniform float4 k_proj, + uniform float4 vspos_model, + uniform float4 k_lightcolor, + uniform float4 row0_model_to_view, + out float4 o_color: COLOR) +{ + float3 screen = l_pos.xyz / l_pos.w; + float2 texcoords = float2(screen.xy) * texpad_texnormal.xy + texpad_texnormal.xy; + + float4 albedo = tex2D(k_texalbedo, texcoords); + float4 normal = tex2D(k_texnormal, texcoords); + float depth = tex2D(k_texdepth, texcoords); + + float3 view = (screen.xzy * k_proj.xyz) / (depth + k_proj.w); + + float3 lightvec = float3(vspos_model) - view; + float lightdist = length(lightvec); + float3 lightdir = lightvec / lightdist; + float scaledist = (lightdist / row0_model_to_view.x); + float falloff = saturate(1.0 - scaledist); + float brite = falloff * falloff * dot(lightdir, float3(normal)); + o_color = albedo * k_lightcolor * brite; + o_color.a = 1; +} diff --git a/samples/fireflies/main.py b/samples/fireflies/main.py new file mode 100755 index 0000000000..994a7d09ec --- /dev/null +++ b/samples/fireflies/main.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python + +# Author: Josh Yelon +# Date: 7/11/2005 +# +# See the associated manual page for an explanation. +# +from direct.showbase.ShowBase import ShowBase +from panda3d.core import FrameBufferProperties, WindowProperties +from panda3d.core import GraphicsPipe, GraphicsOutput +from panda3d.core import Filename, Texture, Shader +from panda3d.core import RenderState, CardMaker +from panda3d.core import PandaNode, TextNode, NodePath +from panda3d.core import RenderAttrib, AlphaTestAttrib, ColorBlendAttrib +from panda3d.core import CullFaceAttrib, DepthTestAttrib, DepthWriteAttrib +from panda3d.core import LPoint3, LVector3, BitMask32 +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.interval.MetaInterval import Sequence +from direct.task.Task import Task +from direct.actor.Actor import Actor +import sys +import os +import random + +# Function to put instructions on the screen. +def addInstructions(pos, msg): + return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.05) + +# Function to put title on the screen. +def addTitle(text): + return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08, + parent=base.a2dBottomRight, align=TextNode.ARight, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + + +class FireflyDemo(ShowBase): + def __init__(self): + # Initialize the ShowBase class from which we inherit, which will + # create a window and set up everything we need for rendering into it. + ShowBase.__init__(self) + self.setBackgroundColor((0, 0, 0, 0)) + + # Preliminary capabilities check. + + if not self.win.getGsg().getSupportsBasicShaders(): + self.t = addTitle("Firefly Demo: Video driver reports that Cg " + "shaders are not supported.") + return + if not self.win.getGsg().getSupportsDepthTexture(): + self.t = addTitle("Firefly Demo: Video driver reports that depth " + "textures are not supported.") + return + + # This algorithm uses two offscreen buffers, one of which has + # an auxiliary bitplane, and the offscreen buffers share a single + # depth buffer. This is a heck of a complicated buffer setup. + + self.modelbuffer = self.makeFBO("model buffer", 1) + self.lightbuffer = self.makeFBO("light buffer", 0) + + # Creation of a high-powered buffer can fail, if the graphics card + # doesn't support the necessary OpenGL extensions. + + if self.modelbuffer is None or self.lightbuffer is None: + self.t = addTitle("Toon Shader: Video driver does not support " + "multiple render targets") + return + + # Create four render textures: depth, normal, albedo, and final. + # attach them to the various bitplanes of the offscreen buffers. + + self.texDepth = Texture() + self.texDepth.setFormat(Texture.FDepthStencil) + self.texAlbedo = Texture() + self.texNormal = Texture() + self.texFinal = Texture() + + self.modelbuffer.addRenderTexture(self.texDepth, + GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepthStencil) + self.modelbuffer.addRenderTexture(self.texAlbedo, + GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) + self.modelbuffer.addRenderTexture(self.texNormal, + GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0) + + self.lightbuffer.addRenderTexture(self.texFinal, + GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) + + # Set the near and far clipping planes. + + self.cam.node().getLens().setNear(50.0) + self.cam.node().getLens().setFar(500.0) + lens = self.cam.node().getLens() + + # This algorithm uses three cameras: one to render the models into the + # model buffer, one to render the lights into the light buffer, and + # one to render "plain" stuff (non-deferred shaded) stuff into the + # light buffer. Each camera has a bitmask to identify it. + + self.modelMask = 1 + self.lightMask = 2 + self.plainMask = 4 + + self.modelcam = self.makeCamera(self.modelbuffer, + lens=lens, scene=render, mask=self.modelMask) + self.lightcam = self.makeCamera(self.lightbuffer, + lens=lens, scene=render, mask=self.lightMask) + self.plaincam = self.makeCamera(self.lightbuffer, + lens=lens, scene=render, mask=self.plainMask) + + # Panda's main camera is not used. + + self.cam.node().setActive(0) + + # Take explicit control over the order in which the three + # buffers are rendered. + + self.modelbuffer.setSort(1) + self.lightbuffer.setSort(2) + self.win.setSort(3) + + # Within the light buffer, control the order of the two cams. + + self.lightcam.node().getDisplayRegion(0).setSort(1) + self.plaincam.node().getDisplayRegion(0).setSort(2) + + # By default, panda usually clears the screen before every + # camera and before every window. Tell it not to do that. + # Then, tell it specifically when to clear and what to clear. + + self.modelcam.node().getDisplayRegion(0).disableClears() + self.lightcam.node().getDisplayRegion(0).disableClears() + self.plaincam.node().getDisplayRegion(0).disableClears() + self.cam.node().getDisplayRegion(0).disableClears() + self.cam2d.node().getDisplayRegion(0).disableClears() + self.modelbuffer.disableClears() + self.win.disableClears() + + self.modelbuffer.setClearColorActive(1) + self.modelbuffer.setClearDepthActive(1) + self.lightbuffer.setClearColorActive(1) + self.lightbuffer.setClearColor((0, 0, 0, 1)) + + # Miscellaneous stuff. + + self.disableMouse() + self.camera.setPos(-9.112, -211.077, 46.951) + self.camera.setHpr(0, -7.5, 2.4) + random.seed() + + # Calculate the projection parameters for the final shader. + # The math here is too complex to explain in an inline comment, + # I've put in a full explanation into the HTML intro. + + proj = self.cam.node().getLens().getProjectionMat() + proj_x = 0.5 * proj.getCell(3, 2) / proj.getCell(0, 0) + proj_y = 0.5 * proj.getCell(3, 2) + proj_z = 0.5 * proj.getCell(3, 2) / proj.getCell(2, 1) + proj_w = -0.5 - 0.5 * proj.getCell(1, 2) + + # Configure the render state of the model camera. + + tempnode = NodePath(PandaNode("temp node")) + tempnode.setAttrib( + AlphaTestAttrib.make(RenderAttrib.MGreaterEqual, 0.5)) + tempnode.setShader(loader.loadShader("model.sha")) + tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MLessEqual)) + self.modelcam.node().setInitialState(tempnode.getState()) + + # Configure the render state of the light camera. + + tempnode = NodePath(PandaNode("temp node")) + tempnode.setShader(loader.loadShader("light.sha")) + tempnode.setShaderInput("texnormal", self.texNormal) + tempnode.setShaderInput("texalbedo", self.texAlbedo) + tempnode.setShaderInput("texdepth", self.texDepth) + tempnode.setShaderInput("proj", (proj_x, proj_y, proj_z, proj_w)) + tempnode.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd, + ColorBlendAttrib.OOne, ColorBlendAttrib.OOne)) + tempnode.setAttrib( + CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise)) + # The next line causes problems on Linux. + # tempnode.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreaterEqual)) + tempnode.setAttrib(DepthWriteAttrib.make(DepthWriteAttrib.MOff)) + self.lightcam.node().setInitialState(tempnode.getState()) + + # Configure the render state of the plain camera. + + rs = RenderState.makeEmpty() + self.plaincam.node().setInitialState(rs) + + # Clear any render attribs on the root node. This is necessary + # because by default, panda assigns some attribs to the root + # node. These default attribs will override the + # carefully-configured render attribs that we just attached + # to the cameras. The simplest solution is to just clear + # them all out. + + render.setState(RenderState.makeEmpty()) + + # My artist created a model in which some of the polygons + # don't have textures. This confuses the shader I wrote. + # This little hack guarantees that everything has a texture. + + white = loader.loadTexture("models/white.jpg") + render.setTexture(white, 0) + + # Create two subroots, to help speed cull traversal. + + self.lightroot = NodePath(PandaNode("lightroot")) + self.lightroot.reparentTo(render) + self.modelroot = NodePath(PandaNode("modelroot")) + self.modelroot.reparentTo(render) + self.lightroot.hide(BitMask32(self.modelMask)) + self.modelroot.hide(BitMask32(self.lightMask)) + self.modelroot.hide(BitMask32(self.plainMask)) + + # Load the model of a forest. Make it visible to the model camera. + # This is a big model, so we load it asynchronously while showing a + # load text. We do this by passing in a callback function. + self.loading = addTitle("Loading models...") + + self.forest = NodePath(PandaNode("Forest Root")) + self.forest.reparentTo(render) + self.forest.hide(BitMask32(self.lightMask | self.plainMask)) + loader.loadModel([ + "models/background", + "models/foliage01", + "models/foliage02", + "models/foliage03", + "models/foliage04", + "models/foliage05", + "models/foliage06", + "models/foliage07", + "models/foliage08", + "models/foliage09"], + callback=self.finishLoading) + + # Cause the final results to be rendered into the main window on a + # card. + + self.card = self.lightbuffer.getTextureCard() + self.card.setTexture(self.texFinal) + self.card.reparentTo(render2d) + + # Panda contains a built-in viewer that lets you view the results of + # your render-to-texture operations. This code configures the viewer. + + self.bufferViewer.setPosition("llcorner") + self.bufferViewer.setCardSize(0, 0.40) + self.bufferViewer.setLayout("vline") + self.toggleCards() + self.toggleCards() + + # Firefly parameters + + self.fireflies = [] + self.sequences = [] + self.scaleseqs = [] + self.glowspheres = [] + self.fireflysize = 1.0 + self.spheremodel = loader.loadModel("misc/sphere") + + # Create the firefly model, a fuzzy dot + dotSize = 1.0 + cm = CardMaker("firefly") + cm.setFrame(-dotSize, dotSize, -dotSize, dotSize) + self.firefly = NodePath(cm.generate()) + self.firefly.setTexture(loader.loadTexture("models/firefly.png")) + self.firefly.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.M_add, + ColorBlendAttrib.O_incoming_alpha, ColorBlendAttrib.O_one)) + + # these allow you to change parameters in realtime + + self.accept("escape", sys.exit, [0]) + self.accept("arrow_up", self.incFireflyCount, [1.1111111]) + self.accept("arrow_down", self.decFireflyCount, [0.9000000]) + self.accept("arrow_right", self.setFireflySize, [1.1111111]) + self.accept("arrow_left", self.setFireflySize, [0.9000000]) + self.accept("v", self.toggleCards) + self.accept("V", self.toggleCards) + + def finishLoading(self, models): + # This function is used as callback to loader.loadModel, and called + # when all of the models have finished loading. + + # Attach the models to the scene graph. + for model in models: + model.reparentTo(self.forest) + + # Show the instructions. + self.loading.destroy() + self.title = addTitle("Panda3D: Tutorial - Fireflies using Deferred Shading") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Up/Down: More / Fewer Fireflies (Count: unknown)") + self.inst3 = addInstructions(0.18, "Right/Left: Bigger / Smaller Fireflies (Radius: unknown)") + self.inst4 = addInstructions(0.24, "V: View the render-to-texture results") + + self.setFireflySize(25.0) + while len(self.fireflies) < 5: + self.addFirefly() + self.updateReadout() + + self.nextadd = 0 + taskMgr.add(self.spawnTask, "spawner") + + def makeFBO(self, name, auxrgba): + # This routine creates an offscreen buffer. All the complicated + # parameters are basically demanding capabilities from the offscreen + # buffer - we demand that it be able to render to texture on every + # bitplane, that it can support aux bitplanes, that it track + # the size of the host window, that it can render to texture + # cumulatively, and so forth. + winprops = WindowProperties() + props = FrameBufferProperties() + props.setRgbColor(True) + props.setRgbaBits(8, 8, 8, 8) + props.setDepthBits(1) + props.setAuxRgba(auxrgba) + return self.graphicsEngine.makeOutput( + self.pipe, "model buffer", -2, + props, winprops, + GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFCanBindEvery | + GraphicsPipe.BFRttCumulative | GraphicsPipe.BFRefuseWindow, + self.win.getGsg(), self.win) + + def addFirefly(self): + pos1 = LPoint3(random.uniform(-50, 50), random.uniform(-100, 150), random.uniform(-10, 80)) + dir = LVector3(random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)) + dir.normalize() + pos2 = pos1 + (dir * 20) + fly = self.lightroot.attachNewNode(PandaNode("fly")) + glow = fly.attachNewNode(PandaNode("glow")) + dot = fly.attachNewNode(PandaNode("dot")) + color_r = 1.0 + color_g = random.uniform(0.8, 1.0) + color_b = min(color_g, random.uniform(0.5, 1.0)) + fly.setColor(color_r, color_g, color_b, 1.0) + fly.setShaderInput("lightcolor", color_r, color_g, color_b, 1.0) + int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2) + int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1) + si1 = fly.scaleInterval(random.uniform(0.8, 1.5), + LPoint3(0.2, 0.2, 0.2), LPoint3(0.2, 0.2, 0.2)) + si2 = fly.scaleInterval(random.uniform(1.5, 0.8), + LPoint3(1.0, 1.0, 1.0), LPoint3(0.2, 0.2, 0.2)) + si3 = fly.scaleInterval(random.uniform(1.0, 2.0), + LPoint3(0.2, 0.2, 0.2), LPoint3(1.0, 1.0, 1.0)) + siseq = Sequence(si1, si2, si3) + siseq.loop() + siseq.setT(random.uniform(0, 1000)) + seq = Sequence(int1, int2) + seq.loop() + self.spheremodel.instanceTo(glow) + self.firefly.instanceTo(dot) + glow.setScale(self.fireflysize * 1.1) + glow.hide(BitMask32(self.modelMask | self.plainMask)) + dot.hide(BitMask32(self.modelMask | self.lightMask)) + dot.setColor(color_r, color_g, color_b, 1.0) + self.fireflies.append(fly) + self.sequences.append(seq) + self.glowspheres.append(glow) + self.scaleseqs.append(siseq) + + def updateReadout(self): + self.inst2.destroy() + self.inst2 = addInstructions(0.12, + "Up/Down: More / Fewer Fireflies (Currently: %d)" % len(self.fireflies)) + self.inst3.destroy() + self.inst3 = addInstructions(0.18, + "Right/Left: Bigger / Smaller Fireflies (Radius: %d ft)" % self.fireflysize) + + def toggleCards(self): + self.bufferViewer.toggleEnable() + # When the cards are not visible, I also disable the color clear. + # This color-clear is actually not necessary, the depth-clear is + # sufficient for the purposes of the algorithm. + if (self.bufferViewer.isEnabled()): + self.modelbuffer.setClearColorActive(True) + else: + self.modelbuffer.setClearColorActive(False) + + def incFireflyCount(self, scale): + n = int((len(self.fireflies) * scale) + 1) + while (n > len(self.fireflies)): + self.addFirefly() + self.updateReadout() + + def decFireflyCount(self, scale): + n = int(len(self.fireflies) * scale) + if (n < 1): + n = 1 + while (len(self.fireflies) > n): + self.glowspheres.pop() + self.sequences.pop().finish() + self.scaleseqs.pop().finish() + self.fireflies.pop().removeNode() + self.updateReadout() + + def setFireflySize(self, n): + n = n * self.fireflysize + self.fireflysize = n + for x in self.glowspheres: + x.setScale(self.fireflysize * 1.1) + self.updateReadout() + + def spawnTask(self, task): + if task.time > self.nextadd: + self.nextadd = task.time + 1.0 + if (len(self.fireflies) < 300): + self.incFireflyCount(1.03) + return Task.cont + +demo = FireflyDemo() +demo.run() diff --git a/samples/fireflies/model.sha b/samples/fireflies/model.sha new file mode 100644 index 0000000000..f1867ee5da --- /dev/null +++ b/samples/fireflies/model.sha @@ -0,0 +1,35 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + float4 vtx_normal : NORMAL, + float4 vtx_color : COLOR, + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0, + out float4 l_color : COLOR, + out float3 l_normal : TEXCOORD1, + uniform float4x4 mat_modelproj, + uniform float4x4 itp_modelview) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord0 = vtx_texcoord0; + l_color = vtx_color; + l_normal = (float3)mul(itp_modelview, vtx_normal); +} + +void fshader(float2 l_texcoord0: TEXCOORD0, + float4 l_color: COLOR, + float3 l_normal: TEXCOORD1, + uniform sampler2D tex_0 : TEXUNIT0, + out float4 o_color: COLOR0, + out float4 o_normal: COLOR1) +{ + l_normal = normalize(l_normal); + o_color = l_color * tex2D(tex_0, l_texcoord0); + o_normal.rgb = (l_normal * 0.5) + float3(0.5, 0.5, 0.5); + o_normal.a = o_color.a; +} + + diff --git a/samples/fireflies/models/Bark.jpg b/samples/fireflies/models/Bark.jpg new file mode 100644 index 0000000000..f20500e6d2 Binary files /dev/null and b/samples/fireflies/models/Bark.jpg differ diff --git a/samples/fireflies/models/Bark03.jpg b/samples/fireflies/models/Bark03.jpg new file mode 100644 index 0000000000..f5a3d982f6 Binary files /dev/null and b/samples/fireflies/models/Bark03.jpg differ diff --git a/samples/fireflies/models/background.egg.pz b/samples/fireflies/models/background.egg.pz new file mode 100644 index 0000000000..b3981f5df6 Binary files /dev/null and b/samples/fireflies/models/background.egg.pz differ diff --git a/samples/fireflies/models/bigleaf3.png b/samples/fireflies/models/bigleaf3.png new file mode 100644 index 0000000000..39ed4c8640 Binary files /dev/null and b/samples/fireflies/models/bigleaf3.png differ diff --git a/samples/fireflies/models/elmleaf.png b/samples/fireflies/models/elmleaf.png new file mode 100644 index 0000000000..c3bc9a742f Binary files /dev/null and b/samples/fireflies/models/elmleaf.png differ diff --git a/samples/fireflies/models/firefly.png b/samples/fireflies/models/firefly.png new file mode 100644 index 0000000000..0e95d39cde Binary files /dev/null and b/samples/fireflies/models/firefly.png differ diff --git a/samples/fireflies/models/foliage01.egg.pz b/samples/fireflies/models/foliage01.egg.pz new file mode 100644 index 0000000000..50249f973e Binary files /dev/null and b/samples/fireflies/models/foliage01.egg.pz differ diff --git a/samples/fireflies/models/foliage02.egg.pz b/samples/fireflies/models/foliage02.egg.pz new file mode 100644 index 0000000000..f8cc0d2957 Binary files /dev/null and b/samples/fireflies/models/foliage02.egg.pz differ diff --git a/samples/fireflies/models/foliage03.egg.pz b/samples/fireflies/models/foliage03.egg.pz new file mode 100644 index 0000000000..764cda7ebc Binary files /dev/null and b/samples/fireflies/models/foliage03.egg.pz differ diff --git a/samples/fireflies/models/foliage04.egg.pz b/samples/fireflies/models/foliage04.egg.pz new file mode 100644 index 0000000000..b0531ee51f Binary files /dev/null and b/samples/fireflies/models/foliage04.egg.pz differ diff --git a/samples/fireflies/models/foliage05.egg.pz b/samples/fireflies/models/foliage05.egg.pz new file mode 100644 index 0000000000..5c8d0a75e6 Binary files /dev/null and b/samples/fireflies/models/foliage05.egg.pz differ diff --git a/samples/fireflies/models/foliage06.egg.pz b/samples/fireflies/models/foliage06.egg.pz new file mode 100644 index 0000000000..ceed26624d Binary files /dev/null and b/samples/fireflies/models/foliage06.egg.pz differ diff --git a/samples/fireflies/models/foliage07.egg.pz b/samples/fireflies/models/foliage07.egg.pz new file mode 100644 index 0000000000..dd7c6909e0 Binary files /dev/null and b/samples/fireflies/models/foliage07.egg.pz differ diff --git a/samples/fireflies/models/foliage08.egg.pz b/samples/fireflies/models/foliage08.egg.pz new file mode 100644 index 0000000000..968a496444 Binary files /dev/null and b/samples/fireflies/models/foliage08.egg.pz differ diff --git a/samples/fireflies/models/foliage09.egg.pz b/samples/fireflies/models/foliage09.egg.pz new file mode 100644 index 0000000000..dfd7583f08 Binary files /dev/null and b/samples/fireflies/models/foliage09.egg.pz differ diff --git a/samples/fireflies/models/indt04S.jpg b/samples/fireflies/models/indt04S.jpg new file mode 100644 index 0000000000..ba52ae94fc Binary files /dev/null and b/samples/fireflies/models/indt04S.jpg differ diff --git a/samples/fireflies/models/noise.jpg b/samples/fireflies/models/noise.jpg new file mode 100644 index 0000000000..12231717db Binary files /dev/null and b/samples/fireflies/models/noise.jpg differ diff --git a/samples/fireflies/models/oakleaf.png b/samples/fireflies/models/oakleaf.png new file mode 100644 index 0000000000..5aa44c1e11 Binary files /dev/null and b/samples/fireflies/models/oakleaf.png differ diff --git a/samples/fireflies/models/shrb03S.jpg b/samples/fireflies/models/shrb03S.jpg new file mode 100644 index 0000000000..3fcffb2896 Binary files /dev/null and b/samples/fireflies/models/shrb03S.jpg differ diff --git a/samples/fireflies/models/white.jpg b/samples/fireflies/models/white.jpg new file mode 100644 index 0000000000..6707c09aa6 Binary files /dev/null and b/samples/fireflies/models/white.jpg differ diff --git a/samples/fireflies/models/wood.jpg b/samples/fireflies/models/wood.jpg new file mode 100644 index 0000000000..3b74af8699 Binary files /dev/null and b/samples/fireflies/models/wood.jpg differ diff --git a/samples/fireflies/models/wood02S.jpg b/samples/fireflies/models/wood02S.jpg new file mode 100644 index 0000000000..5df8ee6a1d Binary files /dev/null and b/samples/fireflies/models/wood02S.jpg differ diff --git a/samples/fractal-plants/barkTexture.jpg b/samples/fractal-plants/barkTexture.jpg new file mode 100644 index 0000000000..85651f1304 Binary files /dev/null and b/samples/fractal-plants/barkTexture.jpg differ diff --git a/samples/fractal-plants/main.py b/samples/fractal-plants/main.py new file mode 100755 index 0000000000..fb16a5b1bc --- /dev/null +++ b/samples/fractal-plants/main.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python + +# Author: Kwasi Mensah (kmensah@andrew.cmu.edu) +# Date: 8/05/2005 +# +# This demo shows how to make quasi-fractal trees in Panda. +# Its primarily meant to be a more complex example on how to use +# Panda's Geom interface. +# +from direct.showbase.ShowBase import ShowBase +from panda3d.core import Filename, InternalName +from panda3d.core import GeomVertexArrayFormat, GeomVertexFormat +from panda3d.core import Geom, GeomNode, GeomTrifans, GeomTristrips +from panda3d.core import GeomVertexReader, GeomVertexWriter +from panda3d.core import GeomVertexRewriter, GeomVertexData +from panda3d.core import PerspectiveLens, TextNode +from panda3d.core import TransformState, CullFaceAttrib +from panda3d.core import Light, AmbientLight, Spotlight +from panda3d.core import NodePath +from panda3d.core import LVector3, LMatrix4 +from direct.task.Task import Task +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +import math +import random +import time +import sys +import os + +random.seed() +base = ShowBase() +base.disableMouse() +base.camera.setPos(0, -180, 30) +numPrimitives = 0 + +title = OnscreenText(text="Panda3D: Tutorial - Procedurally Making a Tree", + style=1, fg=(1, 1, 1, 1), parent=base.a2dBottomCenter, + pos=(0, 0.1), scale=.08) +qEvent = OnscreenText( + text="Q: Start Scene Over", + parent=base.a2dTopLeft, align=TextNode.ALeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.10), + scale=.05) +wEvent = OnscreenText( + text="W: Add Another Tree", + parent=base.a2dTopLeft, align=TextNode.ALeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.16), + scale=.05) + + +# this computes the new Axis which we'll make a branch grow alowng when we +# split +def randomAxis(vecList): + fwd = vecList[0] + perp1 = vecList[1] + perp2 = vecList[2] + + nfwd = fwd + perp1 * (2 * random.random() - 1) + \ + perp2 * (2 * random.random() - 1) + nfwd.normalize() + + nperp2 = nfwd.cross(perp1) + nperp2.normalize() + + nperp1 = nfwd.cross(nperp2) + nperp1.normalize() + + return [nfwd, nperp1, nperp2] + + +# this makes smalle variations in direction when we are growing a branch +# but not splitting +def smallRandomAxis(vecList): + fwd = vecList[0] + perp1 = vecList[1] + perp2 = vecList[2] + + nfwd = fwd + perp1 * (1 * random.random() - 0.5) + \ + perp2 * (1 * random.random() - 0.5) + nfwd.normalize() + + nperp2 = nfwd.cross(perp1) + nperp2.normalize() + + nperp1 = nfwd.cross(nperp2) + nperp1.normalize() + + return [nfwd, nperp1, nperp2] + + +# this draws the body of the tree. This draws a ring of vertices and connects the rings with +# triangles to form the body. +# this keepDrawing paramter tells the function wheter or not we're at an end +# if the vertices before you were an end, dont draw branches to it +def drawBody(nodePath, vdata, pos, vecList, radius=1, keepDrawing=True, numVertices=8): + + circleGeom = Geom(vdata) + + vertWriter = GeomVertexWriter(vdata, "vertex") + colorWriter = GeomVertexWriter(vdata, "color") + normalWriter = GeomVertexWriter(vdata, "normal") + drawReWriter = GeomVertexRewriter(vdata, "drawFlag") + texReWriter = GeomVertexRewriter(vdata, "texcoord") + + startRow = vdata.getNumRows() + vertWriter.setRow(startRow) + colorWriter.setRow(startRow) + normalWriter.setRow(startRow) + + sCoord = 0 + + if (startRow != 0): + texReWriter.setRow(startRow - numVertices) + sCoord = texReWriter.getData2f().getX() + 1 + + drawReWriter.setRow(startRow - numVertices) + if(drawReWriter.getData1f() == False): + sCoord -= 1 + + drawReWriter.setRow(startRow) + texReWriter.setRow(startRow) + + angleSlice = 2 * math.pi / numVertices + currAngle = 0 + + #axisAdj=LMatrix4.rotateMat(45, axis)*LMatrix4.scaleMat(radius)*LMatrix4.translateMat(pos) + + perp1 = vecList[1] + perp2 = vecList[2] + + # vertex information is written here + for i in range(numVertices): + adjCircle = pos + \ + (perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)) * \ + radius + normal = perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle) + normalWriter.addData3f(normal) + vertWriter.addData3f(adjCircle) + texReWriter.addData2f(sCoord, (i + 0.001) / (numVertices - 1)) + colorWriter.addData4f(0.5, 0.5, 0.5, 1) + drawReWriter.addData1f(keepDrawing) + currAngle += angleSlice + + drawReader = GeomVertexReader(vdata, "drawFlag") + drawReader.setRow(startRow - numVertices) + + # we cant draw quads directly so we use Tristrips + if (startRow != 0) & (drawReader.getData1f() != False): + lines = GeomTristrips(Geom.UHStatic) + half = int(numVertices * 0.5) + for i in range(numVertices): + lines.addVertex(i + startRow) + if i < half: + lines.addVertex(i + startRow - half) + else: + lines.addVertex(i + startRow - half - numVertices) + + lines.addVertex(startRow) + lines.addVertex(startRow - half) + lines.closePrimitive() + lines.decompose() + circleGeom.addPrimitive(lines) + + circleGeomNode = GeomNode("Debug") + circleGeomNode.addGeom(circleGeom) + + # I accidentally made the front-face face inwards. Make reverse makes the tree render properly and + # should cause any surprises to any poor programmer that tries to use + # this code + circleGeomNode.setAttrib(CullFaceAttrib.makeReverse(), 1) + global numPrimitives + numPrimitives += numVertices * 2 + + nodePath.attachNewNode(circleGeomNode) + + +# this draws leafs when we reach an end +def drawLeaf(nodePath, vdata, pos=LVector3(0, 0, 0), vecList=[LVector3(0, 0, 1), LVector3(1, 0, 0), LVector3(0, -1, 0)], scale=0.125): + + # use the vectors that describe the direction the branch grows to make the right + # rotation matrix + newCs = LMatrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + newCs.setRow(0, vecList[2]) # right + newCs.setRow(1, vecList[1]) # up + newCs.setRow(2, vecList[0]) # forward + newCs.setRow(3, (0, 0, 0)) + newCs.setCol(3, (0, 0, 0, 1)) + + axisAdj = LMatrix4.scaleMat(scale) * newCs * LMatrix4.translateMat(pos) + + # orginlly made the leaf out of geometry but that didnt look good + # I also think there should be a better way to handle the leaf texture other than + # hardcoding the filename + leafModel = loader.loadModel('models/shrubbery') + leafTexture = loader.loadTexture('models/material-10-cl.png') + + leafModel.reparentTo(nodePath) + leafModel.setTexture(leafTexture, 1) + leafModel.setTransform(TransformState.makeMat(axisAdj)) + + +# recursive algorthim to make the tree +def makeFractalTree(bodydata, nodePath, length, pos=LVector3(0, 0, 0), numIterations=11, numCopies=4, vecList=[LVector3(0, 0, 1), LVector3(1, 0, 0), LVector3(0, -1, 0)]): + if numIterations > 0: + + drawBody(nodePath, bodydata, pos, vecList, length.getX()) + + # move foward along the right axis + newPos = pos + vecList[0] * length.length() + + # only branch every third level (sorta) + if numIterations % 3 == 0: + # decrease dimensions when we branch + length = LVector3( + length.getX() / 2, length.getY() / 2, length.getZ() / 1.1) + for i in range(numCopies): + makeFractalTree(bodydata, nodePath, length, newPos, + numIterations - 1, numCopies, randomAxis(vecList)) + else: + # just make another branch connected to this one with a small + # variation in direction + makeFractalTree(bodydata, nodePath, length, newPos, + numIterations - 1, numCopies, smallRandomAxis(vecList)) + + else: + drawBody(nodePath, bodydata, pos, vecList, length.getX(), False) + drawLeaf(nodePath, bodydata, pos, vecList) + + +alight = AmbientLight('alight') +alight.setColor((0.5, 0.5, 0.5, 1)) +alnp = render.attachNewNode(alight) +render.setLight(alnp) + +slight = Spotlight('slight') +slight.setColor((1, 1, 1, 1)) +lens = PerspectiveLens() +slight.setLens(lens) +slnp = render.attachNewNode(slight) +render.setLight(slnp) + +slnp.setPos(0, 0, 40) + +# rotating light to show that normals are calculated correctly +def updateLight(task): + global slnp + currPos = slnp.getPos() + currPos.setX(100 * math.cos(task.time) / 2) + currPos.setY(100 * math.sin(task.time) / 2) + slnp.setPos(currPos) + + slnp.lookAt(render) + return Task.cont + +taskMgr.add(updateLight, "rotating Light") + + +# add some interactivity to the program +class MyTapper(DirectObject): + + def __init__(self): + formatArray = GeomVertexArrayFormat() + formatArray.addColumn( + InternalName.make("drawFlag"), 1, Geom.NTUint8, Geom.COther) + + format = GeomVertexFormat(GeomVertexFormat.getV3n3cpt2()) + format.addArray(formatArray) + self.format = GeomVertexFormat.registerFormat(format) + + bodydata = GeomVertexData("body vertices", format, Geom.UHStatic) + + self.barkTexture = loader.loadTexture("barkTexture.jpg") + treeNodePath = NodePath("Tree Holder") + makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7)) + + treeNodePath.setTexture(self.barkTexture, 1) + treeNodePath.reparentTo(render) + + self.accept("q", self.regenTree) + self.accept("w", self.addTree) + self.accept("arrow_up", self.upIterations) + self.accept("arrow_down", self.downIterations) + self.accept("arrow_right", self.upCopies) + self.accept("arrow_left", self.downCopies) + + self.numIterations = 11 + self.numCopies = 4 + + self.upDownEvent = OnscreenText( + text="Up/Down: Increase/Decrease the number of iterations (" + str( + self.numIterations) + ")", + parent=base.a2dTopLeft, align=TextNode.ALeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.22), + scale=.05, mayChange=True) + + self.leftRightEvent = OnscreenText( + text="Left/Right: Increase/Decrease branching (" + str( + self.numCopies) + ")", + parent=base.a2dTopLeft, align=TextNode.ALeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.28), + scale=.05, mayChange=True) + + def upIterations(self): + self.numIterations += 1 + self.upDownEvent.setText( + "Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")") + + def downIterations(self): + self.numIterations -= 1 + self.upDownEvent.setText( + "Up/Down: Increase/Decrease the number of iterations (" + str(self.numIterations) + ")") + + def upCopies(self): + self.numCopies += 1 + self.leftRightEvent.setText( + "Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")") + + def downCopies(self): + self.numCopies -= 1 + self.leftRightEvent.setText( + "Left/Right: Increase/Decrease branching (" + str(self.numCopies) + ")") + + def regenTree(self): + forest = render.findAllMatches("Tree Holder") + forest.detach() + + bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic) + + treeNodePath = NodePath("Tree Holder") + makeFractalTree(bodydata, treeNodePath, LVector3(4, 4, 7), LVector3( + 0, 0, 0), self.numIterations, self.numCopies) + + treeNodePath.setTexture(self.barkTexture, 1) + treeNodePath.reparentTo(render) + + def addTree(self): + bodydata = GeomVertexData("body vertices", self.format, Geom.UHStatic) + + randomPlace = LVector3( + 200 * random.random() - 100, 200 * random.random() - 100, 0) + # randomPlace.normalize() + + treeNodePath = NodePath("Tree Holder") + makeFractalTree(bodydata, treeNodePath, LVector3( + 4, 4, 7), randomPlace, self.numIterations, self.numCopies) + + treeNodePath.setTexture(self.barkTexture, 1) + treeNodePath.reparentTo(render) + +t = MyTapper() +print(numPrimitives) + +base.run() diff --git a/samples/fractal-plants/models/material-10-cl.png b/samples/fractal-plants/models/material-10-cl.png new file mode 100644 index 0000000000..e4d6ad1d08 Binary files /dev/null and b/samples/fractal-plants/models/material-10-cl.png differ diff --git a/samples/fractal-plants/models/shrubbery.egg.pz b/samples/fractal-plants/models/shrubbery.egg.pz new file mode 100644 index 0000000000..7bfc21da6e Binary files /dev/null and b/samples/fractal-plants/models/shrubbery.egg.pz differ diff --git a/samples/glow-filter/advanced.py b/samples/glow-filter/advanced.py new file mode 100755 index 0000000000..7d74780906 --- /dev/null +++ b/samples/glow-filter/advanced.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +# Author: Kwasi Mensah (kmensah@andrew.cmu.edu) +# Date: 7/25/2005 + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import Filename, Shader +from panda3d.core import PandaNode, NodePath +from panda3d.core import ColorBlendAttrib +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import TextNode, LPoint3, LVector4 +from direct.showbase.DirectObject import DirectObject +from direct.gui.OnscreenText import OnscreenText +from direct.actor.Actor import Actor +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, fg=(1, 1, 1, 1), + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), scale=.08) + + +# This function is responsible for setting up the two blur filters. +# It just makes a temp Buffer, puts a screen aligned card, and then sets +# the appropiate shader to do all the work. Gaussian blurs are decomposable +# into a two-pass algorithm which is faster than the equivalent one-pass +# algorithm, so we do it in two passes: one pass that blurs in the horizontal +# direction, and one in the vertical direction. +def makeFilterBuffer(srcbuffer, name, sort, prog): + blurBuffer = base.win.makeTextureBuffer(name, 512, 512) + blurBuffer.setSort(sort) + blurBuffer.setClearColor(LVector4(1, 0, 0, 1)) + blurCamera = base.makeCamera2d(blurBuffer) + blurScene = NodePath("new Scene") + blurCamera.node().setScene(blurScene) + shader = loader.loadShader(prog) + card = srcbuffer.getTextureCard() + card.reparentTo(blurScene) + card.setShader(shader) + return blurBuffer + + +class GlowDemo(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) + + base.disableMouse() + base.setBackgroundColor(0, 0, 0) + camera.setPos(0, -50, 0) + + # Check video card capabilities. + if not base.win.getGsg().getSupportsBasicShaders(): + addTitle( + "Glow Filter: Video driver reports that Cg shaders are not supported.") + return + + # Post the instructions + self.title = addTitle("Panda3D: Tutorial - Glow Filter") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Space: Toggle Glow Filter On/Off") + self.inst3 = addInstructions(0.18, "Enter: Toggle Running/Spinning") + self.inst4 = addInstructions(0.24, "V: View the render-to-texture results") + + # Create the shader that will determime what parts of the scene will + # glow + glowShader = loader.loadShader("shaders/glowShader.sha") + + # load our model + self.tron = Actor() + self.tron.loadModel("models/tron") + self.tron.loadAnims({"running": "models/tron_anim"}) + self.tron.reparentTo(render) + self.interval = self.tron.hprInterval(60, LPoint3(360, 0, 0)) + self.interval.loop() + self.isRunning = False + + # put some lighting on the tron model + dlight = DirectionalLight('dlight') + alight = AmbientLight('alight') + dlnp = render.attachNewNode(dlight) + alnp = render.attachNewNode(alight) + dlight.setColor(LVector4(1.0, 0.7, 0.2, 1)) + alight.setColor(LVector4(0.2, 0.2, 0.2, 1)) + dlnp.setHpr(0, -60, 0) + render.setLight(dlnp) + render.setLight(alnp) + + # create the glow buffer. This buffer renders like a normal scene, + # except that only the glowing materials should show up nonblack. + glowBuffer = base.win.makeTextureBuffer("Glow scene", 512, 512) + glowBuffer.setSort(-3) + glowBuffer.setClearColor(LVector4(0, 0, 0, 1)) + + # We have to attach a camera to the glow buffer. The glow camera + # must have the same frustum as the main camera. As long as the aspect + # ratios match, the rest will take care of itself. + glowCamera = base.makeCamera( + glowBuffer, lens=base.cam.node().getLens()) + + # Tell the glow camera to use the glow shader + tempnode = NodePath(PandaNode("temp node")) + tempnode.setShader(glowShader) + glowCamera.node().setInitialState(tempnode.getState()) + + # set up the pipeline: from glow scene to blur x to blur y to main + # window. + blurXBuffer = makeFilterBuffer( + glowBuffer, "Blur X", -2, "shaders/XBlurShader.sha") + blurYBuffer = makeFilterBuffer( + blurXBuffer, "Blur Y", -1, "shaders/YBlurShader.sha") + self.finalcard = blurYBuffer.getTextureCard() + self.finalcard.reparentTo(render2d) + + # This attribute is used to add the results of the post-processing + # effects to the existing framebuffer image, rather than replace it. + # This is mainly useful for glow effects like ours. + self.finalcard.setAttrib(ColorBlendAttrib.make(ColorBlendAttrib.MAdd)) + + # 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", base.bufferViewer.toggleEnable) + self.accept("V", base.bufferViewer.toggleEnable) + base.bufferViewer.setPosition("llcorner") + base.bufferViewer.setLayout("hline") + base.bufferViewer.setCardSize(0.652, 0) + + # event handling + self.accept("space", self.toggleGlow) + self.accept("enter", self.toggleDisplay) + self.accept("escape", sys.exit, [0]) + + self.glowOn = True + + def toggleGlow(self): + if self.glowOn: + self.finalcard.reparentTo(hidden) + else: + self.finalcard.reparentTo(render2d) + self.glowOn = not(self.glowOn) + + def toggleDisplay(self): + self.isRunning = not(self.isRunning) + if not(self.isRunning): + camera.setPos(0, -50, 0) + self.tron.stop("running") + self.tron.pose("running", 0) + self.interval.loop() + else: + camera.setPos(0, -170, 3) + self.interval.finish() + self.tron.setHpr(0, 0, 0) + self.tron.loop("running") + +demo = GlowDemo() +demo.run() diff --git a/samples/glow-filter/basic.py b/samples/glow-filter/basic.py new file mode 100755 index 0000000000..adba975ce3 --- /dev/null +++ b/samples/glow-filter/basic.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +# Author: Kwasi Mensah (kmensah@andrew.cmu.edu) +# Date: 7/25/2005 + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import Filename, Shader +from panda3d.core import PandaNode, NodePath +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import TextNode, LPoint3 +from direct.showbase.DirectObject import DirectObject +from direct.filter.CommonFilters import CommonFilters +from direct.gui.OnscreenText import OnscreenText +from direct.actor.Actor import Actor +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, fg=(1, 1, 1, 1), + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), scale=.08) + + +class GlowDemo(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) + + base.disableMouse() + base.setBackgroundColor(0, 0, 0) + camera.setPos(0, -50, 0) + + # Check video card capabilities. + if not base.win.getGsg().getSupportsBasicShaders(): + addTitle( + "Glow Filter: Video driver reports that Cg shaders are not supported.") + return + + # Use class 'CommonFilters' to enable a bloom filter. + # The brightness of a pixel is measured using a weighted average + # of R,G,B,A. We put all the weight on Alpha, meaning that for + # us, the framebuffer's alpha channel alpha controls bloom. + + self.filters = CommonFilters(base.win, base.cam) + filterok = self.filters.setBloom( + blend=(0, 0, 0, 1), desat=-0.5, intensity=3.0, size="small") + if (filterok == False): + addTitle( + "Toon Shader: Video card not powerful enough to do image postprocessing") + return + self.glowSize = 1 + + # Post the instructions + self.title = addTitle("Panda3D: Tutorial - Glow Filter") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Space: Toggle Glow Filter Small/Med/Large/Off") + self.inst3 = addInstructions(0.18, "Enter: Toggle Running/Spinning") + self.inst4 = addInstructions(0.24, "V: View the render-to-texture results") + + # load our model + + self.tron = Actor() + self.tron.loadModel("models/tron") + self.tron.loadAnims({"running": "models/tron_anim"}) + self.tron.reparentTo(render) + self.interval = self.tron.hprInterval(60, LPoint3(360, 0, 0)) + self.interval.loop() + self.isRunning = False + + # put some lighting on the model + + dlight = DirectionalLight('dlight') + alight = AmbientLight('alight') + dlnp = render.attachNewNode(dlight) + alnp = render.attachNewNode(alight) + dlight.setColor((1.0, 0.7, 0.2, 1)) + alight.setColor((0.2, 0.2, 0.2, 1)) + dlnp.setHpr(0, -60, 0) + render.setLight(dlnp) + render.setLight(alnp) + + # 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", base.bufferViewer.toggleEnable) + self.accept("V", base.bufferViewer.toggleEnable) + base.bufferViewer.setPosition("llcorner") + base.bufferViewer.setLayout("hline") + # base.camLens.setFov(100) + # event handling + self.accept("space", self.toggleGlow) + self.accept("enter", self.toggleDisplay) + self.accept("escape", sys.exit, [0]) + + def toggleGlow(self): + self.glowSize = self.glowSize + 1 + if self.glowSize == 4: + self.glowSize = 0 + self.filters.setBloom(blend=(0, 0, 0, 1), desat=-0.5, intensity=3.0, + size=self.glowSize) + + def toggleDisplay(self): + self.isRunning = not self.isRunning + if not self.isRunning: + camera.setPos(0, -50, 0) + self.tron.stop("running") + self.tron.pose("running", 0) + self.interval.loop() + else: + camera.setPos(0, -170, 3) + self.interval.finish() + self.tron.setHpr(0, 0, 0) + self.tron.loop("running") + +demo = GlowDemo() +demo.run() diff --git a/samples/glow-filter/models/tron-color.png b/samples/glow-filter/models/tron-color.png new file mode 100644 index 0000000000..df4bb8fd7e Binary files /dev/null and b/samples/glow-filter/models/tron-color.png differ diff --git a/samples/glow-filter/models/tron-glow.png b/samples/glow-filter/models/tron-glow.png new file mode 100644 index 0000000000..7f8f0cb1cd Binary files /dev/null and b/samples/glow-filter/models/tron-glow.png differ diff --git a/samples/glow-filter/models/tron.egg.pz b/samples/glow-filter/models/tron.egg.pz new file mode 100644 index 0000000000..59b640940d Binary files /dev/null and b/samples/glow-filter/models/tron.egg.pz differ diff --git a/samples/glow-filter/models/tron_anim.egg.pz b/samples/glow-filter/models/tron_anim.egg.pz new file mode 100644 index 0000000000..e31a780bac Binary files /dev/null and b/samples/glow-filter/models/tron_anim.egg.pz differ diff --git a/samples/glow-filter/shaders/XBlurShader.sha b/samples/glow-filter/shaders/XBlurShader.sha new file mode 100644 index 0000000000..08049d83ab --- /dev/null +++ b/samples/glow-filter/shaders/XBlurShader.sha @@ -0,0 +1,28 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0, + uniform float4x4 mat_modelproj) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord0=vtx_texcoord0; +} + + +void fshader(float2 l_texcoord0 : TEXCOORD0, + out float4 o_color : COLOR, + uniform sampler2D tex_0 : TEXUNIT0) +{ + float3 offset = float3(1.0/1024.0, 5.0/1024.0, 9.0/1024.0); + o_color = tex2D(tex_0, float2(l_texcoord0.x - offset.z, l_texcoord0.y)) * 5.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x - offset.y, l_texcoord0.y)) * 8.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x - offset.x, l_texcoord0.y)) * 10.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x + offset.x, l_texcoord0.y)) * 10.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x + offset.y, l_texcoord0.y)) * 8.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x + offset.z, l_texcoord0.y)) * 5.0; + o_color = o_color * 0.030; +} diff --git a/samples/glow-filter/shaders/YBlurShader.sha b/samples/glow-filter/shaders/YBlurShader.sha new file mode 100644 index 0000000000..5a84701035 --- /dev/null +++ b/samples/glow-filter/shaders/YBlurShader.sha @@ -0,0 +1,30 @@ +//Cg +// +//Cg profile arbvp1 arbfp1 + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0, + uniform float4x4 mat_modelproj) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord0=vtx_texcoord0; +} + + +void fshader(float2 l_texcoord0 : TEXCOORD0, + out float4 o_color : COLOR, + uniform sampler2D tex_0 : TEXUNIT0) +{ + float3 offset = float3(1.0/1024.0, 5.0/1024.0, 9.0/1024.0); + o_color = tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y - offset.z)) * 5.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y - offset.y)) * 8.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y - offset.x)) * 10.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y + offset.x)) * 10.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y + offset.y)) * 8.0; + o_color += tex2D(tex_0, float2(l_texcoord0.x, l_texcoord0.y + offset.z)) * 5.0; + o_color = o_color * 0.030; +} + + diff --git a/samples/glow-filter/shaders/glowShader.sha b/samples/glow-filter/shaders/glowShader.sha new file mode 100644 index 0000000000..f94c22cc98 --- /dev/null +++ b/samples/glow-filter/shaders/glowShader.sha @@ -0,0 +1,21 @@ +//Cg +// + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + uniform float4x4 mat_modelproj, + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0) +{ + l_position=mul(mat_modelproj, vtx_position); + l_texcoord0=vtx_texcoord0; +} + +void fshader(float2 l_texcoord0 : TEXCOORD0, + uniform sampler2D tex_0 : TEXUNIT0, + out float4 o_color : COLOR) +{ + float4 texColor=tex2D(tex_0, l_texcoord0); + o_color=texColor*2*(texColor.w - 0.5); +} + diff --git a/samples/infinite-tunnel/main.py b/samples/infinite-tunnel/main.py new file mode 100755 index 0000000000..bc32fde065 --- /dev/null +++ b/samples/infinite-tunnel/main.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial will cover fog and how it can be used to make a finite length +# tunnel seem endless by hiding its endpoint in darkness. Fog in panda works by +# coloring objects based on their distance from the camera. Fog is not a 3D +# volume object like real world fog. +# With the right settings, Fog in panda can mimic the appearence of real world # fog. +# +# The tunnel and texture was originally created by Vamsi Bandaru and Victoria +# Webb for the Entertainment Technology Center class Building Virtual Worlds + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import Fog +from panda3d.core import TextNode +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.interval.MetaInterval import Sequence +from direct.interval.LerpInterval import LerpFunc +from direct.interval.FunctionInterval import Func +import sys + +# Global variables for the tunnel dimensions and speed of travel +TUNNEL_SEGMENT_LENGTH = 50 +TUNNEL_TIME = 2 # Amount of time for one segment to travel the +# distance of TUNNEL_SEGMENT_LENGTH + + +class FogDemo(ShowBase): + + # Macro-like function used to reduce the amount to code needed to create the + # on screen instructions + def genLabelText(self, i, text): + return OnscreenText(text=text, parent=base.a2dTopLeft, scale=.05, + pos=(0.06, -.065 * 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) + + # Standard initialization stuff + # Standard title that's on screen in every tutorial + self.title = OnscreenText(text="Panda3D: Tutorial - Fog", style=1, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5), parent=base.a2dBottomRight, + align=TextNode.ARight, pos=(-0.1, 0.1), scale=.08) + + # Code to generate the on screen instructions + self.escapeEventText = self.genLabelText(1, "ESC: Quit") + self.pkeyEventText = self.genLabelText(2, "[P]: Pause") + self.tkeyEventText = self.genLabelText(3, "[T]: Toggle Fog") + self.dkeyEventText = self.genLabelText(4, "[D]: Make fog color black") + self.sdkeyEventText = self.genLabelText(5, "[SHIFT+D]: Make background color black") + self.rkeyEventText = self.genLabelText(6, "[R]: Make fog color red") + self.srkeyEventText = self.genLabelText(7, "[SHIFT+R]: Make background color red") + self.bkeyEventText = self.genLabelText(8, "[B]: Make fog color blue") + self.sbkeyEventText = self.genLabelText(9, "[SHIFT+B]: Make background color blue") + self.gkeyEventText = self.genLabelText(10, "[G]: Make fog color green") + self.sgkeyEventText = self.genLabelText(11, "[SHIFT+G]: Make background color green") + self.lkeyEventText = self.genLabelText(12, "[L]: Make fog color light grey") + self.slkeyEventText = self.genLabelText(13, "[SHIFT+L]: Make background color light grey") + self.pluskeyEventText = self.genLabelText(14, "[+]: Increase fog density") + self.minuskeyEventText = self.genLabelText(15, "[-]: Decrease fog density") + + # disable mouse control so that we can place the camera + base.disableMouse() + camera.setPosHpr(0, 0, 10, 0, -90, 0) + base.setBackgroundColor(0, 0, 0) # set the background color to black + + # World specific-code + + # Create an instance of fog called 'distanceFog'. + #'distanceFog' is just a name for our fog, not a specific type of fog. + self.fog = Fog('distanceFog') + # Set the initial color of our fog to black. + self.fog.setColor(0, 0, 0) + # Set the density/falloff of the fog. The range is 0-1. + # The higher the numer, the "bigger" the fog effect. + self.fog.setExpDensity(.08) + # We will set fog on render which means that everything in our scene will + # be affected by fog. Alternatively, you could only set fog on a specific + # object/node and only it and the nodes below it would be affected by + # the fog. + render.setFog(self.fog) + + # Define the keyboard input + # Escape closes the demo + self.accept('escape', sys.exit) + # Handle pausing the tunnel + self.accept('p', self.handlePause) + # Handle turning the fog on and off + self.accept('t', toggleFog, [render, self.fog]) + # Sets keys to set the fog to various colors + self.accept('r', self.fog.setColor, [1, 0, 0]) + self.accept('g', self.fog.setColor, [0, 1, 0]) + self.accept('b', self.fog.setColor, [0, 0, 1]) + self.accept('l', self.fog.setColor, [.7, .7, .7]) + self.accept('d', self.fog.setColor, [0, 0, 0]) + # Sets keys to change the background colors + self.accept('shift-r', base.setBackgroundColor, [1, 0, 0]) + self.accept('shift-g', base.setBackgroundColor, [0, 1, 0]) + self.accept('shift-b', base.setBackgroundColor, [0, 0, 1]) + self.accept('shift-l', base.setBackgroundColor, [.7, .7, .7]) + self.accept('shift-d', base.setBackgroundColor, [0, 0, 0]) + # Increases the fog density when "+" key is pressed + self.accept('+', self.addFogDensity, [.01]) + # This is to handle the other "+" key (it's over = on the keyboard) + self.accept('=', self.addFogDensity, [.01]) + self.accept('shift-=', self.addFogDensity, [.01]) + # Decreases the fog density when the "-" key is pressed + self.accept('-', self.addFogDensity, [-.01]) + + # Load the tunel and start the tunnel + self.initTunnel() + self.contTunnel() + + # This function will change the fog density by the amount passed into it + # This function is needed so that it can look up the current value and + # change it when the key is pressed. If you wanted to bind a key to set it + # at a given value you could call self.fog.setExpDensity directly + def addFogDensity(self, change): + # The min() statement makes sure the density is never over 1 + # The max() statement makes sure the density is never below 0 + self.fog.setExpDensity( + min(1, max(0, self.fog.getExpDensity() + change))) + + # Code to initialize the tunnel + def initTunnel(self): + self.tunnel = [None] * 4 + + for x in range(4): + # Load a copy of the tunnel + self.tunnel[x] = loader.loadModel('models/tunnel') + # The front segment needs to be attached to render + if x == 0: + self.tunnel[x].reparentTo(render) + # The rest of the segments parent to the previous one, so that by moving + # the front segement, the entire tunnel is moved + else: + self.tunnel[x].reparentTo(self.tunnel[x - 1]) + # We have to offset each segment by its length so that they stack onto + # each other. Otherwise, they would all occupy the same space. + self.tunnel[x].setPos(0, 0, -TUNNEL_SEGMENT_LENGTH) + # Now we have a tunnel consisting of 4 repeating segments with a + # hierarchy like this: + # render<-tunnel[0]<-tunnel[1]<-tunnel[2]<-tunnel[3] + + # This function is called to snap the front of the tunnel to the back + # to simulate traveling through it + def contTunnel(self): + # This line uses slices to take the front of the list and put it on the + # back. For more information on slices check the Python manual + self.tunnel = self.tunnel[1:] + self.tunnel[0:1] + # Set the front segment (which was at TUNNEL_SEGMENT_LENGTH) to 0, which + # is where the previous segment started + self.tunnel[0].setZ(0) + # Reparent the front to render to preserve the hierarchy outlined above + self.tunnel[0].reparentTo(render) + # Set the scale to be apropriate (since attributes like scale are + # inherited, the rest of the segments have a scale of 1) + self.tunnel[0].setScale(.155, .155, .305) + # Set the new back to the values that the rest of teh segments have + self.tunnel[3].reparentTo(self.tunnel[2]) + self.tunnel[3].setZ(-TUNNEL_SEGMENT_LENGTH) + self.tunnel[3].setScale(1) + + # Set up the tunnel to move one segment and then call contTunnel again + # to make the tunnel move infinitely + self.tunnelMove = Sequence( + LerpFunc(self.tunnel[0].setZ, + duration=TUNNEL_TIME, + fromData=0, + toData=TUNNEL_SEGMENT_LENGTH * .305), + Func(self.contTunnel) + ) + self.tunnelMove.start() + + # This function calls toggle interval to pause or unpause the tunnel. + # Like addFogDensity, toggleInterval could not be passed directly in the + # accept command since the value of self.tunnelMove changes whenever + #self.contTunnel is called + def handlePause(self): + toggleInterval(self.tunnelMove) +# End Class World + + +# This function will toggle any interval passed to it between playing and +# paused +def toggleInterval(interval): + if interval.isPlaying(): + interval.pause() + else: + interval.resume() + + +# This function will toggle fog on a node +def toggleFog(node, fog): + # If the fog attached to the node is equal to the one we passed in, then + # fog is on and we should clear it + if node.getFog() == fog: + node.clearFog() + # Otherwise fog is not set so we should set it + else: + node.setFog(fog) + + +demo = FogDemo() +demo.run() diff --git a/samples/infinite-tunnel/models/tunnel.egg.pz b/samples/infinite-tunnel/models/tunnel.egg.pz new file mode 100644 index 0000000000..bfb460b2a8 Binary files /dev/null and b/samples/infinite-tunnel/models/tunnel.egg.pz differ diff --git a/samples/infinite-tunnel/models/tunnel.jpg b/samples/infinite-tunnel/models/tunnel.jpg new file mode 100644 index 0000000000..870403aa0b Binary files /dev/null and b/samples/infinite-tunnel/models/tunnel.jpg differ diff --git a/samples/looking-and-gripping/main.py b/samples/looking-and-gripping/main.py new file mode 100755 index 0000000000..186073d097 --- /dev/null +++ b/samples/looking-and-gripping/main.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Models and Textures by: Shaun Budhram, Will Houng, and David Tucker +# Last Updated: 2015-03-13 +# +# This tutorial will cover exposing joints and manipulating them. Specifically, +# we will take control of the neck joint of a humanoid character and rotate that +# joint to always face the mouse cursor. This will in turn make the head of the +# character "look" at the mouse cursor. We will also expose the hand joint and +# use it as a point to "attach" objects that the character can hold. By +# parenting an object to a hand joint, the object will stay in the character's +# hand even if the hand is moving through an animation. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import TextNode, NodePath, LightAttrib +from panda3d.core import LVector3 +from direct.actor.Actor import Actor +from direct.task.Task import Task +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +import sys + +# A simple function to make sure a value is in a given range, -1 to 1 by +# default +def clamp(i, mn=-1, mx=1): + return min(max(i, mn), mx) + +# 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, scale=.06, + pos=(0.06, -.08 * i), fg=(1, 1, 1, 1), + shadow=(0, 0, 0, .5), align=TextNode.ALeft) + + +class LookingGrippingDemo(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 - Joint Manipulation", + fg=(1, 1, 1, 1), parent=base.a2dBottomRight, + align=TextNode.ARight, pos=(-0.1, 0.1), + shadow=(0, 0, 0, .5), scale=.08) + + self.onekeyText = genLabelText("ESC: Quit", 1) + self.onekeyText = genLabelText("[1]: Teapot", 2) + self.twokeyText = genLabelText("[2]: Candy cane", 3) + self.threekeyText = genLabelText("[3]: Banana", 4) + self.fourkeyText = genLabelText("[4]: Sword", 5) + + # Set up key input + self.accept('escape', sys.exit) + self.accept('1', self.switchObject, [0]) + self.accept('2', self.switchObject, [1]) + self.accept('3', self.switchObject, [2]) + self.accept('4', self.switchObject, [3]) + + base.disableMouse() # Disable mouse-based camera-control + camera.setPos(0, -15, 2) # Position the camera + + self.eve = Actor("models/eve", # Load our animated charachter + {'walk': "models/eve_walk"}) + self.eve.reparentTo(render) # Put it in the scene + + # Now we use controlJoint to get a NodePath that's in control of her neck + # This must be done before any animations are played + self.eveNeck = self.eve.controlJoint(None, 'modelRoot', 'Neck') + + # We now play an animation. An animation must be played, or at least posed + # for the nodepath we just got from controlJoint to actually effect the + # model + self.eve.actorInterval("walk", playRate=2).loop() + + # Now we add a task that will take care of turning the head + taskMgr.add(self.turnHead, "turnHead") + + # Now we will expose the joint the hand joint. ExposeJoint allows us to + # get the position of a joint while it is animating. This is different than + # controlJonit which stops that joint from animating but lets us move it. + # This is particularly usefull for putting an object (like a weapon) in an + # actor's hand + self.rightHand = self.eve.exposeJoint(None, 'modelRoot', 'RightHand') + + # This is a table with models, positions, rotations, and scales of objects to + # be attached to our exposed joint. These are stock models and so they needed + # to be repositioned to look right. + positions = [("teapot", (0, -.66, -.95), (90, 0, 90), .4), + ("models/candycane", (.15, -.99, -.22), (90, 0, 90), 1), + ("models/banana", (.08, -.1, .09), (0, -90, 0), 1.75), + ("models/sword", (.11, .19, .06), (0, 0, 90), 1)] + + self.models = [] # A list that will store our models objects + for row in positions: + np = loader.loadModel(row[0]) # Load the model + np.setPos(row[1][0], row[1][1], row[1][2]) # Position it + np.setHpr(row[2][0], row[2][1], row[2][2]) # Rotate it + np.setScale(row[3]) # Scale it + # Reparent the model to the exposed joint. That way when the joint moves, + # the model we just loaded will move with it. + np.reparentTo(self.rightHand) + self.models.append(np) # Add it to our models list + + self.switchObject(0) # Make object 0 the first shown + self.setupLights() # Put in some default lighting + + # This is what we use to change which object it being held. It just hides all of + # the objects and then unhides the one that was selected + def switchObject(self, i): + for np in self.models: + np.hide() + self.models[i].show() + + # This task gets the position of mouse each frame, and rotates the neck based + # on it. + def turnHead(self, task): + # Check to make sure the mouse is readable + if base.mouseWatcherNode.hasMouse(): + # get the mouse position as a LVector2. The values for each axis are from -1 to + # 1. The top-left is (-1,-1), the bottom right is (1,1) + mpos = base.mouseWatcherNode.getMouse() + # Here we multiply the values to get the amount of degrees to turn + # Restrain is used to make sure the values returned by getMouse are in the + # valid range. If this particular model were to turn more than this, + # significant tearing would be visable + self.eveNeck.setP(clamp(mpos.getX()) * 50) + self.eveNeck.setH(clamp(mpos.getY()) * 20) + + return Task.cont # Task continues infinitely + + def setupLights(self): # Sets up some default lighting + 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)) + +demo = LookingGrippingDemo() # Create an instance of our class +demo.run() # Run the simulation diff --git a/samples/looking-and-gripping/models/banana.egg.pz b/samples/looking-and-gripping/models/banana.egg.pz new file mode 100644 index 0000000000..c8081ae403 Binary files /dev/null and b/samples/looking-and-gripping/models/banana.egg.pz differ diff --git a/samples/looking-and-gripping/models/candycane.egg.pz b/samples/looking-and-gripping/models/candycane.egg.pz new file mode 100644 index 0000000000..00fd4d9ee6 Binary files /dev/null and b/samples/looking-and-gripping/models/candycane.egg.pz differ diff --git a/samples/looking-and-gripping/models/eve.egg.pz b/samples/looking-and-gripping/models/eve.egg.pz new file mode 100644 index 0000000000..83ab673c46 Binary files /dev/null and b/samples/looking-and-gripping/models/eve.egg.pz differ diff --git a/samples/looking-and-gripping/models/eve_run.egg.pz b/samples/looking-and-gripping/models/eve_run.egg.pz new file mode 100644 index 0000000000..3ea3950afd Binary files /dev/null and b/samples/looking-and-gripping/models/eve_run.egg.pz differ diff --git a/samples/looking-and-gripping/models/eve_walk.egg.pz b/samples/looking-and-gripping/models/eve_walk.egg.pz new file mode 100644 index 0000000000..ae565590bf Binary files /dev/null and b/samples/looking-and-gripping/models/eve_walk.egg.pz differ diff --git a/samples/looking-and-gripping/models/sword.egg.pz b/samples/looking-and-gripping/models/sword.egg.pz new file mode 100644 index 0000000000..22e204bd34 Binary files /dev/null and b/samples/looking-and-gripping/models/sword.egg.pz differ diff --git a/samples/looking-and-gripping/models/textures/banana.jpg b/samples/looking-and-gripping/models/textures/banana.jpg new file mode 100644 index 0000000000..3473625f85 Binary files /dev/null and b/samples/looking-and-gripping/models/textures/banana.jpg differ diff --git a/samples/looking-and-gripping/models/textures/candycane.jpg b/samples/looking-and-gripping/models/textures/candycane.jpg new file mode 100644 index 0000000000..9d63aec222 Binary files /dev/null and b/samples/looking-and-gripping/models/textures/candycane.jpg differ diff --git a/samples/looking-and-gripping/models/textures/eve.jpg b/samples/looking-and-gripping/models/textures/eve.jpg new file mode 100644 index 0000000000..3c2aad6517 Binary files /dev/null and b/samples/looking-and-gripping/models/textures/eve.jpg differ diff --git a/samples/looking-and-gripping/models/textures/eveface.jpg b/samples/looking-and-gripping/models/textures/eveface.jpg new file mode 100644 index 0000000000..7ae72b80e1 Binary files /dev/null and b/samples/looking-and-gripping/models/textures/eveface.jpg differ diff --git a/samples/looking-and-gripping/models/textures/sword.jpg b/samples/looking-and-gripping/models/textures/sword.jpg new file mode 100644 index 0000000000..eab2267c0d Binary files /dev/null and b/samples/looking-and-gripping/models/textures/sword.jpg differ diff --git a/samples/media-player/PandaSneezes.ogv b/samples/media-player/PandaSneezes.ogv new file mode 100644 index 0000000000..b58e308687 Binary files /dev/null and b/samples/media-player/PandaSneezes.ogv differ diff --git a/samples/media-player/main.py b/samples/media-player/main.py new file mode 100755 index 0000000000..838cb54961 --- /dev/null +++ b/samples/media-player/main.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +from panda3d.core import * +# Tell Panda3D to use OpenAL, not FMOD +loadPrcFileData("", "audio-library-name p3openal_audio") + +from direct.showbase.DirectObject import DirectObject +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.ShowBase import ShowBase + +# Function to put instructions on the screen. +def addInstructions(pos, msg): + return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1), + parent=base.a2dTopLeft, align=TextNode.ALeft, + pos=(0.08, -pos - 0.04), scale=.06) + +# 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 MediaPlayer(ShowBase): + + def __init__(self, media_file): + # 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.title = addTitle("Panda3D: Tutorial - Media Player") + self.inst1 = addInstructions(0.06, "P: Play/Pause") + self.inst2 = addInstructions(0.12, "S: Stop and Rewind") + self.inst3 = addInstructions(0.18, + "M: Slow Motion / Normal Motion toggle") + + # Load the texture. We could use loader.loadTexture for this, + # but we want to make sure we get a MovieTexture, since it + # implements synchronizeTo. + self.tex = MovieTexture("name") + assert self.tex.read(media_file), "Failed to load video!" + + # Set up a fullscreen card to set the video texture on. + cm = CardMaker("My Fullscreen Card") + cm.setFrameFullscreenQuad() + + # Tell the CardMaker to create texture coordinates that take into + # account the padding region of the texture. + cm.setUvRange(self.tex) + + # Now place the card in the scene graph and apply the texture to it. + card = NodePath(cm.generate()) + card.reparentTo(self.render2d) + card.setTexture(self.tex) + + self.sound = loader.loadSfx(media_file) + # Synchronize the video to the sound. + self.tex.synchronizeTo(self.sound) + + self.accept('p', self.playpause) + self.accept('P', self.playpause) + self.accept('s', self.stopsound) + self.accept('S', self.stopsound) + self.accept('m', self.fastforward) + self.accept('M', self.fastforward) + + def stopsound(self): + self.sound.stop() + self.sound.setPlayRate(1.0) + + def fastforward(self): + if self.sound.status() == AudioSound.PLAYING: + t = self.sound.getTime() + self.sound.stop() + if self.sound.getPlayRate() == 1.0: + self.sound.setPlayRate(0.5) + else: + self.sound.setPlayRate(1.0) + self.sound.setTime(t) + self.sound.play() + + def playpause(self): + if self.sound.status() == AudioSound.PLAYING: + t = self.sound.getTime() + self.sound.stop() + self.sound.setTime(t) + else: + self.sound.play() + +player = MediaPlayer("PandaSneezes.ogv") +player.run() diff --git a/samples/motion-trails/main.py b/samples/motion-trails/main.py new file mode 100755 index 0000000000..b0764e5cf9 --- /dev/null +++ b/samples/motion-trails/main.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python + +# Author: Josh Yelon +# +# This is a tutorial to show one of the simplest applications +# of copy-to-texture: motion trails. +# + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import GraphicsOutput +from panda3d.core import Filename, Texture +from panda3d.core import CardMaker +from panda3d.core import NodePath, TextNode +from panda3d.core import AmbientLight, DirectionalLight +from direct.showbase.DirectObject import DirectObject +from direct.gui.OnscreenText import OnscreenText +from direct.task.Task import Task +from direct.actor.Actor import Actor +from random import uniform +import sys +import os + +def addInstructions(pos, msg): + return OnscreenText(text=msg, parent=base.a2dTopLeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -pos - 0.03), + align=TextNode.ALeft, scale=.05) + + +class MotionTrails(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.camera.setPos(0, -26, 4) + self.setBackgroundColor(0, 0, 0) + + # Create a texture into which we can copy the main window. + # We set it to RTMTriggeredCopyTexture mode, which tells it that we + # want it to copy the window contents into a texture every time we + # call self.win.triggerCopy(). + self.tex = Texture() + self.tex.setMinfilter(Texture.FTLinear) + self.win.addRenderTexture(self.tex, + GraphicsOutput.RTMTriggeredCopyTexture) + + # Set the initial color to clear the texture to, before rendering it. + # This is necessary because we don't clear the texture while rendering, + # and otherwise the user might see garbled random data from GPU memory. + self.tex.setClearColor((0, 0, 0, 1)) + self.tex.clearImage() + + # Create another 2D camera. Tell it to render before the main camera. + self.backcam = self.makeCamera2d(self.win, sort=-10) + self.background = NodePath("background") + self.backcam.reparentTo(self.background) + self.background.setDepthTest(0) + self.background.setDepthWrite(0) + self.backcam.node().getDisplayRegion(0).setClearDepthActive(0) + + # Obtain two texture cards. One renders before the dragon, the other + # after. + self.bcard = self.win.getTextureCard() + self.bcard.reparentTo(self.background) + self.bcard.setTransparency(1) + self.fcard = self.win.getTextureCard() + self.fcard.reparentTo(self.render2d) + self.fcard.setTransparency(1) + + # Initialize one of the nice effects. + self.chooseEffectGhost() + + # Add the task that initiates the screenshots. + taskMgr.add(self.takeSnapShot, "takeSnapShot") + + # Create some black squares on top of which we will + # place the instructions. + blackmaker = CardMaker("blackmaker") + blackmaker.setColor(0, 0, 0, 1) + blackmaker.setFrame(-1.00, -0.50, 0.65, 1.00) + instcard = NodePath(blackmaker.generate()) + instcard.reparentTo(self.render2d) + blackmaker.setFrame(-0.5, 0.5, -1.00, -0.85) + titlecard = NodePath(blackmaker.generate()) + titlecard.reparentTo(self.render2d) + + # Panda does its best to hide the differences between DirectX and + # OpenGL. But there are a few differences that it cannot hide. + # One such difference is that when OpenGL copies from a + # visible window to a texture, it gets it right-side-up. When + # DirectX does it, it gets it upside-down. There is nothing panda + # can do to compensate except to expose a flag and let the + # application programmer deal with it. You should only do this + # in the rare event that you're copying from a visible window + # to a texture. + if self.win.getGsg().getCopyTextureInverted(): + print("Copy texture is inverted.") + self.bcard.setScale(1, 1, -1) + self.fcard.setScale(1, 1, -1) + + # Put up the instructions + title = OnscreenText(text="Panda3D: Tutorial - Motion Trails", + fg=(1, 1, 1, 1), parent=base.a2dBottomCenter, + pos=(0, 0.1), scale=.08) + + instr0 = addInstructions(0.06, "Press ESC to exit") + instr1 = addInstructions(0.12, "Press 1: Ghost effect") + instr2 = addInstructions(0.18, "Press 2: PaintBrush effect") + instr3 = addInstructions(0.24, "Press 3: Double Vision effect") + instr4 = addInstructions(0.30, "Press 4: Wings of Blue effect") + instr5 = addInstructions(0.36, "Press 5: Whirlpool effect") + + # Enable the key events + self.accept("escape", sys.exit, [0]) + self.accept("1", self.chooseEffectGhost) + self.accept("2", self.chooseEffectPaintBrush) + self.accept("3", self.chooseEffectDoubleVision) + self.accept("4", self.chooseEffectWingsOfBlue) + self.accept("5", self.chooseEffectWhirlpool) + + def takeSnapShot(self, task): + if task.time > self.nextclick: + self.nextclick += 1.0 / self.clickrate + if self.nextclick < task.time: + self.nextclick = task.time + self.win.triggerCopy() + return Task.cont + + def chooseEffectGhost(self): + self.setBackgroundColor(0, 0, 0, 1) + self.bcard.hide() + self.fcard.show() + self.fcard.setColor(1.0, 1.0, 1.0, 0.99) + self.fcard.setScale(1.00) + self.fcard.setPos(0, 0, 0) + self.fcard.setR(0) + self.clickrate = 30 + self.nextclick = 0 + + def chooseEffectPaintBrush(self): + self.setBackgroundColor(0, 0, 0, 1) + self.bcard.show() + self.fcard.hide() + self.bcard.setColor(1, 1, 1, 1) + self.bcard.setScale(1.0) + self.bcard.setPos(0, 0, 0) + self.bcard.setR(0) + self.clickrate = 10000 + self.nextclick = 0 + + def chooseEffectDoubleVision(self): + self.setBackgroundColor(0, 0, 0, 1) + self.bcard.show() + self.bcard.setColor(1, 1, 1, 1) + self.bcard.setScale(1.0) + self.bcard.setPos(-0.05, 0, 0) + self.bcard.setR(0) + self.fcard.show() + self.fcard.setColor(1, 1, 1, 0.60) + self.fcard.setScale(1.0) + self.fcard.setPos(0.05, 0, 0) + self.fcard.setR(0) + self.clickrate = 10000 + self.nextclick = 0 + + def chooseEffectWingsOfBlue(self): + self.setBackgroundColor(0, 0, 0, 1) + self.fcard.hide() + self.bcard.show() + self.bcard.setColor(1.0, 0.90, 1.0, 254.0 / 255.0) + self.bcard.setScale(1.1, 1, 0.95) + self.bcard.setPos(0, 0, 0.05) + self.bcard.setR(0) + self.clickrate = 30 + self.nextclick = 0 + + def chooseEffectWhirlpool(self): + self.setBackgroundColor(0, 0, 0, 1) + self.bcard.show() + self.fcard.hide() + self.bcard.setColor(1, 1, 1, 1) + self.bcard.setScale(0.999) + self.bcard.setPos(0, 0, 0) + self.bcard.setR(1) + self.clickrate = 10000 + self.nextclick = 0 + + +t = MotionTrails() + +character = Actor() +character.loadModel('models/dancer') +character.reparentTo(t.render) +character.loadAnims({'win': 'models/dancer'}) +character.loop('win') +# character.hprInterval(15, LPoint3(360, 0,0)).loop() + +# put some lighting on the model +dlight = DirectionalLight('dlight') +alight = AmbientLight('alight') +dlnp = t.render.attachNewNode(dlight) +alnp = t.render.attachNewNode(alight) +dlight.setColor((1.0, 0.9, 0.8, 1)) +alight.setColor((0.2, 0.3, 0.4, 1)) +dlnp.setHpr(0, -60, 0) +t.render.setLight(dlnp) +t.render.setLight(alnp) + +t.run() diff --git a/samples/motion-trails/models/dancer.egg.pz b/samples/motion-trails/models/dancer.egg.pz new file mode 100644 index 0000000000..bc1260e71f Binary files /dev/null and b/samples/motion-trails/models/dancer.egg.pz differ diff --git a/samples/motion-trails/models/tex_skratixOpt.jpg b/samples/motion-trails/models/tex_skratixOpt.jpg new file mode 100644 index 0000000000..74112ec797 Binary files /dev/null and b/samples/motion-trails/models/tex_skratixOpt.jpg differ diff --git a/samples/music-box/main.py b/samples/music-box/main.py new file mode 100755 index 0000000000..774168f782 --- /dev/null +++ b/samples/music-box/main.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman, and Elan Ruskin +# Last Updated: 2015-03-13 +# +# This tutorial shows how to load, play, and manipulate sounds +# and sound intervals in a panda project. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import NodePath, TextNode +from panda3d.core import PointLight, AmbientLight +from direct.gui.OnscreenText import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.interval.SoundInterval import SoundInterval +from direct.gui.DirectSlider import DirectSlider +from direct.gui.DirectButton import DirectButton +from direct.interval.MetaInterval import Parallel +from direct.interval.LerpInterval import LerpHprInterval +import sys + +# Create an instance of ShowBase, which will open a window and set up a +# scene graph and camera. +base = ShowBase() + +class MusicBox(DirectObject): + def __init__(self): + # Our standard title and instructions text + self.title = OnscreenText(text="Panda3D: Tutorial - Music Box", + parent=base.a2dBottomCenter, + pos=(0, 0.08), scale=0.08, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5)) + self.escapeText = OnscreenText(text="ESC: Quit", parent=base.a2dTopLeft, + fg=(1, 1, 1, 1), pos=(0.06, -0.1), + align=TextNode.ALeft, scale=.05) + + # Set up the key input + self.accept('escape', sys.exit) + + # Fix the camera position + base.disableMouse() + + # Loading sounds is done in a similar way to loading other things + # Loading the main music box song + self.musicBoxSound = base.loadMusic('music/musicbox.ogg') + self.musicBoxSound.setVolume(.5) # Volume is a percentage from 0 to 1 + # 0 means loop forever, 1 (default) means + # play once. 2 or higher means play that many times + self.musicBoxSound.setLoopCount(0) + + # Set up a simple light. + self.plight = PointLight("light") + self.plight.setColor((0.7, 0.7, 0.5, 1)) + light_path = base.render.attachNewNode(self.plight) + light_path.setPos(0, 0, 20) + base.render.setLight(light_path) + + alight = AmbientLight("ambient") + alight.setColor((0.3, 0.3, 0.4, 1)) + base.render.setLight(base.render.attachNewNode(alight)) + + # Enable per-pixel lighting + base.render.setShaderAuto() + + # Sound objects do not have a pause function, just play and stop. So we will + # Use this variable to keep track of where the sound is at when it was stoped + # to impliment pausing + self.musicTime = 0 + + # Loading the open/close effect + # loadSFX and loadMusic are identical. They are often used for organization + #(loadMusic is used for background music, loadSfx is used for other effects) + self.lidSfx = base.loadSfx('music/openclose.ogg') + # The open/close file has both effects in it. Fortunatly we can use intervals + # to easily define parts of a sound file to play + self.lidOpenSfx = SoundInterval(self.lidSfx, duration=2, startTime=0) + self.lidCloseSfx = SoundInterval(self.lidSfx, startTime=5) + + # For this tutorial, it seemed appropriate to have on screen controls. + # The following code creates them. + # This is a label for a slider + self.sliderText = OnscreenText("Volume", pos=(-0.1, 0.87), scale=.07, + fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1)) + # The slider itself. It calls self.setMusicBoxVolume when changed + self.slider = DirectSlider(pos=(-0.1, 0, .75), scale=0.8, value=.50, + command=self.setMusicBoxVolume) + # A button that calls self.toggleMusicBox when pressed + self.button = DirectButton(pos=(.9, 0, .75), text="Open", + scale=.1, pad=(.2, .2), + rolloverSound=None, clickSound=None, + command=self.toggleMusicBox) + + # A variable to represent the state of the simulation. It starts closed + self.boxOpen = False + + # Here we load and set up the music box. It was modeled in a complex way, so + # setting it up will be complicated + self.musicBox = loader.loadModel('models/MusicBox') + self.musicBox.setPos(0, 60, -9) + self.musicBox.reparentTo(render) + # Just like the scene graph contains hierarchies of nodes, so can + # models. You can get the NodePath for the node using the find + # function, and then you can animate the model by moving its parts + # To see the hierarchy of a model, use, the ls function + # self.musicBox.ls() prints out the entire hierarchy of the model + + # Finding pieces of the model + self.Lid = self.musicBox.find('**/lid') + self.Panda = self.musicBox.find('**/turningthing') + + # This model was made with the hinge in the wrong place + # this is here so we have something to turn + self.HingeNode = self.musicBox.find( + '**/box').attachNewNode('nHingeNode') + self.HingeNode.setPos(.8659, 6.5, 5.4) + # WRT - ie with respect to. Reparents the object without changing + # its position, size, or orientation + self.Lid.wrtReparentTo(self.HingeNode) + self.HingeNode.setHpr(0, 90, 0) + + # This sets up an interval to play the close sound and actually close the box + # at the same time. + self.lidClose = Parallel( + self.lidCloseSfx, + LerpHprInterval(self.HingeNode, 2.0, (0, 90, 0), blendType='easeInOut')) + + # Same thing for opening the box + self.lidOpen = Parallel( + self.lidOpenSfx, + LerpHprInterval(self.HingeNode, 2.0, (0, 0, 0), blendType='easeInOut')) + + # The interval for turning the panda + self.PandaTurn = self.Panda.hprInterval(7, (360, 0, 0)) + # Do a quick loop and pause to set it as a looping interval so it can be + # started with resume and loop properly + self.PandaTurn.loop() + self.PandaTurn.pause() + + def setMusicBoxVolume(self): + # Simply reads the current value from the slider and sets it in the + # sound + newVol = self.slider.guiItem.getValue() + self.musicBoxSound.setVolume(newVol) + + def toggleMusicBox(self): + #if self.lidOpen.isPlaying() or self.lidClose.isPlaying(): + # # It's currently already opening or closing + # return + + if self.boxOpen: + self.lidOpen.pause() + + self.lidClose.start() # Start the close box interval + self.PandaTurn.pause() # Pause the figurine turning + # Save the current time of the music + self.musicTime = self.musicBoxSound.getTime() + self.musicBoxSound.stop() # Stop the music + self.button['text'] = "Open" # Prepare to change button label + else: + self.lidClose.pause() + + self.lidOpen.start() # Start the open box interval + self.PandaTurn.resume() # Resume the figuring turning + # Reset the time of the music so it starts where it left off + self.musicBoxSound.setTime(self.musicTime) + self.musicBoxSound.play() # Play the music + self.button['text'] = "Close" # Prepare to change button label + + self.button.setText() # Actually change the button label + # Set our state to opposite what it was + self.boxOpen = not self.boxOpen + #(closed to open or open to closed) + +# and we can run! +mb = MusicBox() +base.run() diff --git a/samples/music-box/models/MusicBox.egg.pz b/samples/music-box/models/MusicBox.egg.pz new file mode 100644 index 0000000000..9eac077186 Binary files /dev/null and b/samples/music-box/models/MusicBox.egg.pz differ diff --git a/samples/music-box/models/box.jpg b/samples/music-box/models/box.jpg new file mode 100644 index 0000000000..4e713dd1bc Binary files /dev/null and b/samples/music-box/models/box.jpg differ diff --git a/samples/music-box/models/panda.jpg b/samples/music-box/models/panda.jpg new file mode 100644 index 0000000000..df6459280a Binary files /dev/null and b/samples/music-box/models/panda.jpg differ diff --git a/samples/music-box/music/musicbox.ogg b/samples/music-box/music/musicbox.ogg new file mode 100755 index 0000000000..cc6b062490 Binary files /dev/null and b/samples/music-box/music/musicbox.ogg differ diff --git a/samples/music-box/music/openclose.ogg b/samples/music-box/music/openclose.ogg new file mode 100644 index 0000000000..46c2bb69ac Binary files /dev/null and b/samples/music-box/music/openclose.ogg differ diff --git a/samples/particles/dust.png b/samples/particles/dust.png new file mode 100644 index 0000000000..2379566221 Binary files /dev/null and b/samples/particles/dust.png differ diff --git a/samples/particles/dust.ptf b/samples/particles/dust.ptf new file mode 100644 index 0000000000..908b56ea08 --- /dev/null +++ b/samples/particles/dust.ptf @@ -0,0 +1,54 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("SphereVolumeEmitter") +p0.setPoolSize(10000) +p0.setBirthRate(0.0500) +p0.setLitterSize(10) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(2.0000) +p0.factory.setLifespanSpread(0.2500) +p0.factory.setMassBase(2.0000) +p0.factory.setMassSpread(0.0100) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.07) +# Sprite parameters +p0.renderer.setTexture(loader.loadTexture('dust.png')) +p0.renderer.setColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(0) +p0.renderer.setYScaleFlag(0) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.0100) +p0.renderer.setFinalXScale(0.0200) +p0.renderer.setInitialYScale(0.0100) +p0.renderer.setFinalYScale(0.0200) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(LVector3(0.0000, 0.0000, 0.0000)) +p0.emitter.setExplicitLaunchVector(LVector3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Sphere Volume parameters +p0.emitter.setRadius(0.1000) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('gravity') +# Force parameters +self.addForceGroup(f0) diff --git a/samples/particles/fireish.ptf b/samples/particles/fireish.ptf new file mode 100644 index 0000000000..3e0ba4a631 --- /dev/null +++ b/samples/particles/fireish.ptf @@ -0,0 +1,51 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("DiscEmitter") +p0.setPoolSize(1024) +p0.setBirthRate(0.0200) +p0.setLitterSize(10) +p0.setLitterSpread(0) +p0.setSystemLifespan(1200.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(0.5000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.22) +# Sprite parameters +p0.renderer.setTexture(loader.loadTexture('sparkle.png')) +p0.renderer.setColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(1) +p0.renderer.setYScaleFlag(1) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.0050) +p0.renderer.setFinalXScale(0.0200) +p0.renderer.setInitialYScale(0.0100) +p0.renderer.setFinalYScale(0.0200) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(LVector3(0.0000, 0.0000, 3.0000)) +p0.emitter.setExplicitLaunchVector(LVector3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Disc parameters +p0.emitter.setRadius(0.5000) +self.addParticles(p0) diff --git a/samples/particles/fountain.ptf b/samples/particles/fountain.ptf new file mode 100644 index 0000000000..19efa0bdc0 --- /dev/null +++ b/samples/particles/fountain.ptf @@ -0,0 +1,58 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(2.000, 2.000, 2.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("PointParticleRenderer") +p0.setEmitter("DiscEmitter") +p0.setPoolSize(10000) +p0.setBirthRate(0.0200) +p0.setLitterSize(300) +p0.setLitterSpread(100) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(0.5000) +p0.factory.setLifespanSpread(0.2500) +p0.factory.setMassBase(2.0000) +p0.factory.setMassSpread(0.0100) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.45) +# Point parameters +p0.renderer.setPointSize(3.00) +p0.renderer.setStartColor(LVector4(0.25, 0.90, 1.00, 1.00)) +p0.renderer.setEndColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setBlendType(PointParticleRenderer.PPONECOLOR) +p0.renderer.setBlendMethod(BaseParticleRenderer.PPNOBLEND) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETCUSTOM) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(LVector3(0.0000, 0.0000, 4.0000)) +p0.emitter.setExplicitLaunchVector(LVector3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Disc parameters +p0.emitter.setRadius(0.0500) +p0.emitter.setOuterAngle(356.1859) +p0.emitter.setInnerAngle(0.0000) +p0.emitter.setOuterMagnitude(2.0000) +p0.emitter.setInnerMagnitude(1.0000) +p0.emitter.setCubicLerping(0) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('gravity') +# Force parameters +force0 = LinearVectorForce(LVector3(0.0000, 0.0000, -1.0000), 25.0000, 1) +force0.setActive(1) +f0.addForce(force0) +force1 = LinearJitterForce(3.0000, 1) +force1.setActive(1) +f0.addForce(force1) +self.addForceGroup(f0) diff --git a/samples/particles/particle_panel.py b/samples/particles/particle_panel.py new file mode 100755 index 0000000000..d74e42155c --- /dev/null +++ b/samples/particles/particle_panel.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# Author: Shao Zhang, Phil Saltzman +# Last Updated: 2015-03-13 +# +# This file contians the minimum code needed to load the particle panel tool +# See readme.txt for more information + +import sys +try: + import _tkinter +except: + sys.exit("Please install python module 'Tkinter'") + +try: + import Pmw +except: + sys.exit("Please install Python megawidgets") + +# Open the Panda window +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +# Makes sure that Panda is configured to play nice with Tkinter +base.startTk() + +from direct.tkpanels.ParticlePanel import ParticlePanel + +pp = ParticlePanel() # Create the panel +base.disableMouse() # Disable camera control to place it +base.camera.setY(-10) # Place the camera +base.setBackgroundColor(0, 0, 0) # Most particle systems show up better on black backgrounds +base.run() diff --git a/samples/particles/smoke.png b/samples/particles/smoke.png new file mode 100644 index 0000000000..2d4cbfbc1e Binary files /dev/null and b/samples/particles/smoke.png differ diff --git a/samples/particles/smoke.ptf b/samples/particles/smoke.ptf new file mode 100644 index 0000000000..3972c60499 --- /dev/null +++ b/samples/particles/smoke.ptf @@ -0,0 +1,54 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("SphereSurfaceEmitter") +p0.setPoolSize(10000) +p0.setBirthRate(0.0500) +p0.setLitterSize(10) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(2.0000) +p0.factory.setLifespanSpread(0.2500) +p0.factory.setMassBase(2.0000) +p0.factory.setMassSpread(0.0100) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.13) +# Sprite parameters +p0.renderer.setTexture(loader.loadTexture('smoke.png')) +p0.renderer.setColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(0) +p0.renderer.setYScaleFlag(0) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.0100) +p0.renderer.setFinalXScale(0.0200) +p0.renderer.setInitialYScale(0.0100) +p0.renderer.setFinalYScale(0.0200) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPBLENDLINEAR) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(LVector3(0.0000, 0.0000, 0.0000)) +p0.emitter.setExplicitLaunchVector(LVector3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Sphere Surface parameters +p0.emitter.setRadius(0.0100) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('gravity') +# Force parameters +self.addForceGroup(f0) diff --git a/samples/particles/smokering.ptf b/samples/particles/smokering.ptf new file mode 100644 index 0000000000..d679afa929 --- /dev/null +++ b/samples/particles/smokering.ptf @@ -0,0 +1,60 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("DiscEmitter") +p0.setPoolSize(1024) +p0.setBirthRate(0.0200) +p0.setLitterSize(10) +p0.setLitterSpread(0) +p0.setSystemLifespan(1200.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(10.0000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.10) +# Sprite parameters +p0.renderer.setTexture(loader.loadTexture('smoke.png')) +p0.renderer.setColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(1) +p0.renderer.setYScaleFlag(1) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.0050) +p0.renderer.setFinalXScale(0.0400) +p0.renderer.setInitialYScale(0.0100) +p0.renderer.setFinalYScale(0.0400) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.0000) +p0.emitter.setOffsetForce(LVector3(0.1000, 0.0000, 0.1000)) +p0.emitter.setExplicitLaunchVector(LVector3(1.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Disc parameters +p0.emitter.setRadius(0.5000) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('vertex') +# Force parameters +force0 = LinearCylinderVortexForce(4.0000, 1.0000, 1.0000, 1.0000, 0) +force0.setActive(1) +f0.addForce(force0) +force1 = LinearVectorForce(LVector3(0.0000, 0.0000, 1.0000), 0.0500, 0) +force1.setActive(1) +f0.addForce(force1) +self.addForceGroup(f0) diff --git a/samples/particles/sparkle.png b/samples/particles/sparkle.png new file mode 100644 index 0000000000..5f6cde1802 Binary files /dev/null and b/samples/particles/sparkle.png differ diff --git a/samples/particles/steam.png b/samples/particles/steam.png new file mode 100644 index 0000000000..bef6c043b1 Binary files /dev/null and b/samples/particles/steam.png differ diff --git a/samples/particles/steam.ptf b/samples/particles/steam.ptf new file mode 100644 index 0000000000..2fe6b63d0f --- /dev/null +++ b/samples/particles/steam.ptf @@ -0,0 +1,57 @@ + +self.reset() +self.setPos(0.000, 0.000, 0.000) +self.setHpr(0.000, 0.000, 0.000) +self.setScale(1.000, 1.000, 1.000) +p0 = Particles.Particles('particles-1') +# Particles parameters +p0.setFactory("PointParticleFactory") +p0.setRenderer("SpriteParticleRenderer") +p0.setEmitter("DiscEmitter") +p0.setPoolSize(10000) +p0.setBirthRate(0.2500) +p0.setLitterSize(20) +p0.setLitterSpread(0) +p0.setSystemLifespan(0.0000) +p0.setLocalVelocityFlag(1) +p0.setSystemGrowsOlderFlag(0) +# Factory parameters +p0.factory.setLifespanBase(8.0000) +p0.factory.setLifespanSpread(0.0000) +p0.factory.setMassBase(1.0000) +p0.factory.setMassSpread(0.0000) +p0.factory.setTerminalVelocityBase(400.0000) +p0.factory.setTerminalVelocitySpread(0.0000) +# Point factory parameters +# Renderer parameters +p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT) +p0.renderer.setUserAlpha(0.10) +# Sprite parameters +p0.renderer.setTexture(loader.loadTexture('steam.png')) +p0.renderer.setColor(LVector4(1.00, 1.00, 1.00, 1.00)) +p0.renderer.setXScaleFlag(1) +p0.renderer.setYScaleFlag(1) +p0.renderer.setAnimAngleFlag(0) +p0.renderer.setInitialXScale(0.0025) +p0.renderer.setFinalXScale(0.0400) +p0.renderer.setInitialYScale(0.0025) +p0.renderer.setFinalYScale(0.0400) +p0.renderer.setNonanimatedTheta(0.0000) +p0.renderer.setAlphaBlendMethod(BaseParticleRenderer.PPNOBLEND) +p0.renderer.setAlphaDisable(0) +# Emitter parameters +p0.emitter.setEmissionType(BaseParticleEmitter.ETEXPLICIT) +p0.emitter.setAmplitude(1.0000) +p0.emitter.setAmplitudeSpread(0.7500) +p0.emitter.setOffsetForce(LVector3(0.0000, 0.0000, 1.0000)) +p0.emitter.setExplicitLaunchVector(LVector3(0.0000, 0.0000, 0.0000)) +p0.emitter.setRadiateOrigin(LPoint3(0.0000, 0.0000, 0.0000)) +# Disc parameters +p0.emitter.setRadius(0.5000) +self.addParticles(p0) +f0 = ForceGroup.ForceGroup('vertex') +# Force parameters +force0 = LinearNoiseForce(0.1500, 0) +force0.setActive(1) +f0.addForce(force0) +self.addForceGroup(f0) diff --git a/samples/particles/steam_example.py b/samples/particles/steam_example.py new file mode 100755 index 0000000000..4a003f5ab0 --- /dev/null +++ b/samples/particles/steam_example.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial shows how to take an existing particle effect taken from a +# .ptf file and run it in a general Panda project. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode +from panda3d.core import AmbientLight, DirectionalLight +from panda3d.core import LPoint3, LVector3 +from panda3d.core import Filename +from panda3d.physics import BaseParticleEmitter, BaseParticleRenderer +from panda3d.physics import PointParticleFactory, SpriteParticleRenderer +from panda3d.physics import LinearNoiseForce, DiscEmitter +from direct.particles.Particles import Particles +from direct.particles.ParticleEffect import ParticleEffect +from direct.particles.ForceGroup import ForceGroup +from direct.gui.OnscreenText import OnscreenText +import sys + +HELP_TEXT = """ +1: Load Steam +2: Load Dust +3: Load Fountain +4: Load Smoke +5: Load Smokering +6: Load Fireish +ESC: Quit +""" + +class ParticleDemo(ShowBase): + + def __init__(self): + ShowBase.__init__(self) + + # Standard title and instruction text + self.title = OnscreenText( + text="Panda3D: Tutorial - Particles", + parent=base.a2dBottomCenter, + style=1, fg=(1, 1, 1, 1), pos=(0, 0.1), scale=.08) + self.escapeEvent = OnscreenText( + text=HELP_TEXT, parent=base.a2dTopLeft, + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.06), + align=TextNode.ALeft, scale=.05) + + # More standard initialization + self.accept('escape', sys.exit) + self.accept('1', self.loadParticleConfig, ['steam.ptf']) + self.accept('2', self.loadParticleConfig, ['dust.ptf']) + self.accept('3', self.loadParticleConfig, ['fountain.ptf']) + self.accept('4', self.loadParticleConfig, ['smoke.ptf']) + self.accept('5', self.loadParticleConfig, ['smokering.ptf']) + self.accept('6', self.loadParticleConfig, ['fireish.ptf']) + + self.accept('escape', sys.exit) + base.disableMouse() + base.camera.setPos(0, -20, 2) + base.camLens.setFov(25) + base.setBackgroundColor(0, 0, 0) + + # This command is required for Panda to render particles + base.enableParticles() + self.t = loader.loadModel("teapot") + self.t.setPos(0, 10, 0) + self.t.reparentTo(render) + self.setupLights() + self.p = ParticleEffect() + self.loadParticleConfig('steam.ptf') + + def loadParticleConfig(self, filename): + # Start of the code from steam.ptf + self.p.cleanup() + self.p = ParticleEffect() + self.p.loadConfig(Filename(filename)) + # Sets particles to birth relative to the teapot, but to render at + # toplevel + self.p.start(self.t) + self.p.setPos(3.000, 0.000, 2.250) + + # Setup lighting + def setupLights(self): + 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)) + # Set lighting on teapot so steam doesn't get affected + self.t.setLight(self.t.attachNewNode(directionalLight)) + self.t.setLight(self.t.attachNewNode(ambientLight)) + +demo = ParticleDemo() +demo.run() diff --git a/samples/procedural-cube/main.py b/samples/procedural-cube/main.py new file mode 100755 index 0000000000..2f21cd9a0f --- /dev/null +++ b/samples/procedural-cube/main.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python + +# Author: Kwasi Mensah (kmensah@andrew.cmu.edu) +# Date: 8/02/2005 +# +# This is meant to be a simple example of how to draw a cube +# using Panda's new Geom Interface. Quads arent directly supported +# since they get broken down to trianlges anyway. +# + +from direct.showbase.ShowBase import ShowBase +from direct.showbase.DirectObject import DirectObject +from direct.gui.DirectGui import * +from direct.interval.IntervalGlobal import * +from panda3d.core import lookAt +from panda3d.core import GeomVertexFormat, GeomVertexData +from panda3d.core import Geom, GeomTriangles, GeomVertexWriter +from panda3d.core import Texture, GeomNode +from panda3d.core import PerspectiveLens +from panda3d.core import CardMaker +from panda3d.core import Light, Spotlight +from panda3d.core import TextNode +from panda3d.core import LVector3 +import sys +import os + +base = ShowBase() +base.disableMouse() +base.camera.setPos(0, -10, 0) + +title = OnscreenText(text="Panda3D: Tutorial - Making a Cube Procedurally", + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07, + parent=base.a2dBottomRight, align=TextNode.ARight) +escapeEvent = OnscreenText(text="1: Set a Texture onto the Cube", + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.08), + align=TextNode.ALeft, scale=.05, + parent=base.a2dTopLeft) +spaceEvent = OnscreenText(text="2: Toggle Light from the front On/Off", + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.14), + align=TextNode.ALeft, scale=.05, + parent=base.a2dTopLeft) +upDownEvent = OnscreenText(text="3: Toggle Light from on top On/Off", + style=1, fg=(1, 1, 1, 1), pos=(0.06, -0.20), + align=TextNode.ALeft, scale=.05, + parent=base.a2dTopLeft) + + +# You can't normalize inline so this is a helper function +def normalized(*args): + myVec = LVector3(*args) + myVec.normalize() + return myVec + +# helper function to make a square given the Lower-Left-Hand and +# Upper-Right-Hand corners + +def makeSquare(x1, y1, z1, x2, y2, z2): + format = GeomVertexFormat.getV3n3cpt2() + vdata = GeomVertexData('square', format, Geom.UHDynamic) + + vertex = GeomVertexWriter(vdata, 'vertex') + normal = GeomVertexWriter(vdata, 'normal') + color = GeomVertexWriter(vdata, 'color') + texcoord = GeomVertexWriter(vdata, 'texcoord') + + # make sure we draw the sqaure in the right plane + if x1 != x2: + vertex.addData3(x1, y1, z1) + vertex.addData3(x2, y1, z1) + vertex.addData3(x2, y2, z2) + vertex.addData3(x1, y2, z2) + + normal.addData3(normalized(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1)) + normal.addData3(normalized(2 * x2 - 1, 2 * y1 - 1, 2 * z1 - 1)) + normal.addData3(normalized(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1)) + normal.addData3(normalized(2 * x1 - 1, 2 * y2 - 1, 2 * z2 - 1)) + + else: + vertex.addData3(x1, y1, z1) + vertex.addData3(x2, y2, z1) + vertex.addData3(x2, y2, z2) + vertex.addData3(x1, y1, z2) + + normal.addData3(normalized(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1)) + normal.addData3(normalized(2 * x2 - 1, 2 * y2 - 1, 2 * z1 - 1)) + normal.addData3(normalized(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1)) + normal.addData3(normalized(2 * x1 - 1, 2 * y1 - 1, 2 * z2 - 1)) + + # adding different colors to the vertex for visibility + color.addData4f(1.0, 0.0, 0.0, 1.0) + color.addData4f(0.0, 1.0, 0.0, 1.0) + color.addData4f(0.0, 0.0, 1.0, 1.0) + color.addData4f(1.0, 0.0, 1.0, 1.0) + + texcoord.addData2f(0.0, 1.0) + texcoord.addData2f(0.0, 0.0) + texcoord.addData2f(1.0, 0.0) + texcoord.addData2f(1.0, 1.0) + + # Quads aren't directly supported by the Geom interface + # you might be interested in the CardMaker class if you are + # interested in rectangle though + tris = GeomTriangles(Geom.UHDynamic) + tris.addVertices(0, 1, 3) + tris.addVertices(1, 2, 3) + + square = Geom(vdata) + square.addPrimitive(tris) + return square + +# Note: it isn't particularly efficient to make every face as a separate Geom. +# instead, it would be better to create one Geom holding all of the faces. +square0 = makeSquare(-1, -1, -1, 1, -1, 1) +square1 = makeSquare(-1, 1, -1, 1, 1, 1) +square2 = makeSquare(-1, 1, 1, 1, -1, 1) +square3 = makeSquare(-1, 1, -1, 1, -1, -1) +square4 = makeSquare(-1, -1, -1, -1, 1, 1) +square5 = makeSquare(1, -1, -1, 1, 1, 1) +snode = GeomNode('square') +snode.addGeom(square0) +snode.addGeom(square1) +snode.addGeom(square2) +snode.addGeom(square3) +snode.addGeom(square4) +snode.addGeom(square5) + +cube = render.attachNewNode(snode) +cube.hprInterval(1.5, (360, 360, 360)).loop() + +# OpenGl by default only draws "front faces" (polygons whose vertices are +# specified CCW). +cube.setTwoSided(True) + + +class MyTapper(DirectObject): + + def __init__(self): + self.testTexture = loader.loadTexture("maps/envir-reeds.png") + self.accept("1", self.toggleTex) + self.accept("2", self.toggleLightsSide) + self.accept("3", self.toggleLightsUp) + + self.LightsOn = False + self.LightsOn1 = False + slight = Spotlight('slight') + slight.setColor((1, 1, 1, 1)) + lens = PerspectiveLens() + slight.setLens(lens) + self.slnp = render.attachNewNode(slight) + self.slnp1 = render.attachNewNode(slight) + + def toggleTex(self): + global cube + if cube.hasTexture(): + cube.setTextureOff(1) + else: + cube.setTexture(self.testTexture) + + def toggleLightsSide(self): + global cube + self.LightsOn = not self.LightsOn + + if self.LightsOn: + render.setLight(self.slnp) + self.slnp.setPos(cube, 10, -400, 0) + self.slnp.lookAt(10, 0, 0) + else: + render.setLightOff(self.slnp) + + def toggleLightsUp(self): + global cube + self.LightsOn1 = not self.LightsOn1 + + if self.LightsOn1: + render.setLight(self.slnp1) + self.slnp1.setPos(cube, 10, 0, 400) + self.slnp1.lookAt(10, 0, 0) + else: + render.setLightOff(self.slnp1) + +t = MyTapper() +base.run() diff --git a/samples/render-to-texture/main.py b/samples/render-to-texture/main.py new file mode 100755 index 0000000000..ec6836bb4d --- /dev/null +++ b/samples/render-to-texture/main.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import Filename, Texture +from panda3d.core import AmbientLight, DirectionalLight, PointLight +from panda3d.core import NodePath, TextNode +from direct.task.Task import Task +from direct.actor.Actor import Actor +from direct.gui.OnscreenText import OnscreenText +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), 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)) + +random.seed() + +class TeapotOnTVDemo(ShowBase): + + def __init__(self): + ShowBase.__init__(self) + self.disableMouse() + self.setBackgroundColor((0, 0, 0, 1)) + + # Post the instructions. + self.title = addTitle("Panda3D: Tutorial - Using Render-to-Texture") + self.inst1 = addInstructions(0.06, "ESC: Quit") + self.inst2 = addInstructions(0.12, "Up/Down: Zoom in/out on the Teapot") + self.inst3 = addInstructions(0.18, "Left/Right: Move teapot left/right") + self.inst4 = addInstructions(0.24, "V: View the render-to-texture results") + + # we now get buffer thats going to hold the texture of our new scene + altBuffer = self.win.makeTextureBuffer("hello", 256, 256) + + # now we have to setup a new scene graph to make this scene + altRender = NodePath("new render") + + # this takes care of setting up ther camera properly + self.altCam = self.makeCamera(altBuffer) + self.altCam.reparentTo(altRender) + self.altCam.setPos(0, -10, 0) + + # get the teapot and rotates it for a simple animation + self.teapot = loader.loadModel('teapot') + self.teapot.reparentTo(altRender) + self.teapot.setPos(0, 0, -1) + self.teapot.hprInterval(1.5, (360, 360, 360)).loop() + + # put some lighting on the teapot + dlight = DirectionalLight('dlight') + alight = AmbientLight('alight') + dlnp = altRender.attachNewNode(dlight) + alnp = altRender.attachNewNode(alight) + dlight.setColor((0.8, 0.8, 0.5, 1)) + alight.setColor((0.2, 0.2, 0.2, 1)) + dlnp.setHpr(0, -60, 0) + altRender.setLight(dlnp) + altRender.setLight(alnp) + + # Put lighting on the main scene + plight = PointLight('plight') + plnp = render.attachNewNode(plight) + plnp.setPos(0, 0, 10) + render.setLight(plnp) + render.setLight(alnp) + + # 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.setCardSize(1.0, 0.0) + + # Create the tv-men. Each TV-man will display the + # offscreen-texture on his TV screen. + self.tvMen = [] + self.makeTvMan(-5, 30, 1, altBuffer.getTexture(), 0.9) + self.makeTvMan(5, 30, 1, altBuffer.getTexture(), 1.4) + self.makeTvMan(0, 23, -3, altBuffer.getTexture(), 2.0) + self.makeTvMan(-5, 20, -6, altBuffer.getTexture(), 1.1) + self.makeTvMan(5, 18, -5, altBuffer.getTexture(), 1.7) + + self.accept("escape", sys.exit, [0]) + self.accept("arrow_up", self.zoomIn) + self.accept("arrow_down", self.zoomOut) + self.accept("arrow_left", self.moveLeft) + self.accept("arrow_right", self.moveRight) + + def makeTvMan(self, x, y, z, tex, playrate): + man = Actor() + man.loadModel('models/mechman_idle') + man.setPos(x, y, z) + man.reparentTo(render) + faceplate = man.find("**/faceplate") + faceplate.setTexture(tex, 1) + man.setPlayRate(playrate, "mechman_anim") + man.loop("mechman_anim") + self.tvMen.append(man) + + def zoomIn(self): + self.altCam.setY(self.altCam.getY() * 0.9) + + def zoomOut(self): + self.altCam.setY(self.altCam.getY() * 1.2) + + def moveLeft(self): + self.altCam.setX(self.altCam.getX() + 1) + + def moveRight(self): + self.altCam.setX(self.altCam.getX() - 1) + +demo = TeapotOnTVDemo() +demo.run() diff --git a/samples/render-to-texture/models/mechman_idle.egg.pz b/samples/render-to-texture/models/mechman_idle.egg.pz new file mode 100644 index 0000000000..5fa90b3d8e Binary files /dev/null and b/samples/render-to-texture/models/mechman_idle.egg.pz differ diff --git a/samples/render-to-texture/models/mechmanskin.jpg b/samples/render-to-texture/models/mechmanskin.jpg new file mode 100644 index 0000000000..d9160b1065 Binary files /dev/null and b/samples/render-to-texture/models/mechmanskin.jpg differ diff --git a/samples/render-to-texture/models/static.jpg b/samples/render-to-texture/models/static.jpg new file mode 100644 index 0000000000..93739eba8a Binary files /dev/null and b/samples/render-to-texture/models/static.jpg differ diff --git a/samples/roaming-ralph/main.py b/samples/roaming-ralph/main.py new file mode 100755 index 0000000000..0290c3e74b --- /dev/null +++ b/samples/roaming-ralph/main.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python + +# Author: Ryan Myers +# Models: Jeff Styers, Reagan Heller +# +# Last Updated: 2015-03-13 +# +# This tutorial provides an example of creating a character +# and having it walk around on uneven terrain, as well +# as implementing a fully rotatable camera. + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import CollisionTraverser, CollisionNode +from panda3d.core import CollisionHandlerQueue, CollisionRay +from panda3d.core import Filename, AmbientLight, DirectionalLight +from panda3d.core import PandaNode, NodePath, Camera, TextNode +from panda3d.core import CollideMask +from direct.gui.OnscreenText import OnscreenText +from direct.actor.Actor import Actor +import random +import sys +import os +import math + +# 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=.07, + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), shadow=(0, 0, 0, 1)) + + +class RoamingRalphDemo(ShowBase): + def __init__(self): + # Set up the window, camera, etc. + ShowBase.__init__(self) + + # Set the background color to black + self.win.setClearColor((0, 0, 0, 1)) + + # This is used to store which keys are currently pressed. + self.keyMap = { + "left": 0, "right": 0, "forward": 0, "cam-left": 0, "cam-right": 0} + + # Post the instructions + self.title = addTitle( + "Panda3D Tutorial: Roaming Ralph (Walking on Uneven Terrain)") + self.inst1 = addInstructions(0.06, "[ESC]: Quit") + self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left") + self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right") + self.inst4 = addInstructions(0.24, "[Up Arrow]: Run Ralph Forward") + self.inst6 = addInstructions(0.30, "[A]: Rotate Camera Left") + self.inst7 = addInstructions(0.36, "[S]: Rotate Camera Right") + + # Set up the environment + # + # This environment model contains collision meshes. If you look + # in the egg file, you will see the following: + # + # { Polyset keep descend } + # + # This tag causes the following mesh to be converted to a collision + # mesh -- a mesh which is optimized for collision, not rendering. + # It also keeps the original mesh, so there are now two copies --- + # one optimized for rendering, one for collisions. + + self.environ = loader.loadModel("models/world") + self.environ.reparentTo(render) + + # Create the main character, Ralph + + ralphStartPos = self.environ.find("**/start_point").getPos() + self.ralph = Actor("models/ralph", + {"run": "models/ralph-run", + "walk": "models/ralph-walk"}) + self.ralph.reparentTo(render) + self.ralph.setScale(.2) + self.ralph.setPos(ralphStartPos + (0, 0, 0.5)) + + # Create a floater object, which floats 2 units above ralph. We + # use this as a target for the camera to look at. + + self.floater = NodePath(PandaNode("floater")) + self.floater.reparentTo(self.ralph) + self.floater.setZ(2.0) + + # Accept the control keys for movement and rotation + + self.accept("escape", sys.exit) + self.accept("arrow_left", self.setKey, ["left", True]) + self.accept("arrow_right", self.setKey, ["right", True]) + self.accept("arrow_up", self.setKey, ["forward", True]) + self.accept("a", self.setKey, ["cam-left", True]) + self.accept("s", self.setKey, ["cam-right", True]) + self.accept("arrow_left-up", self.setKey, ["left", False]) + self.accept("arrow_right-up", self.setKey, ["right", False]) + self.accept("arrow_up-up", self.setKey, ["forward", False]) + self.accept("a-up", self.setKey, ["cam-left", False]) + self.accept("s-up", self.setKey, ["cam-right", False]) + + taskMgr.add(self.move, "moveTask") + + # Game state variables + self.isMoving = False + + # Set up the camera + self.disableMouse() + self.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2) + + # We will detect the height of the terrain by creating a collision + # ray and casting it downward toward the terrain. One ray will + # start above ralph's head, and the other will start above the camera. + # A ray may hit the terrain, or it may hit a rock or a tree. If it + # hits the terrain, we can detect the height. If it hits anything + # else, we rule that the move is illegal. + self.cTrav = CollisionTraverser() + + self.ralphGroundRay = CollisionRay() + self.ralphGroundRay.setOrigin(0, 0, 9) + self.ralphGroundRay.setDirection(0, 0, -1) + self.ralphGroundCol = CollisionNode('ralphRay') + self.ralphGroundCol.addSolid(self.ralphGroundRay) + self.ralphGroundCol.setFromCollideMask(CollideMask.bit(0)) + self.ralphGroundCol.setIntoCollideMask(CollideMask.allOff()) + self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol) + self.ralphGroundHandler = CollisionHandlerQueue() + self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler) + + self.camGroundRay = CollisionRay() + self.camGroundRay.setOrigin(0, 0, 9) + self.camGroundRay.setDirection(0, 0, -1) + self.camGroundCol = CollisionNode('camRay') + self.camGroundCol.addSolid(self.camGroundRay) + self.camGroundCol.setFromCollideMask(CollideMask.bit(0)) + self.camGroundCol.setIntoCollideMask(CollideMask.allOff()) + self.camGroundColNp = self.camera.attachNewNode(self.camGroundCol) + self.camGroundHandler = CollisionHandlerQueue() + self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler) + + # Uncomment this line to see the collision rays + #self.ralphGroundColNp.show() + #self.camGroundColNp.show() + + # Uncomment this line to show a visual representation of the + # collisions occuring + #self.cTrav.showCollisions(render) + + # Create some lighting + ambientLight = AmbientLight("ambientLight") + ambientLight.setColor((.3, .3, .3, 1)) + directionalLight = DirectionalLight("directionalLight") + directionalLight.setDirection((-5, -5, -5)) + directionalLight.setColor((1, 1, 1, 1)) + directionalLight.setSpecularColor((1, 1, 1, 1)) + render.setLight(render.attachNewNode(ambientLight)) + render.setLight(render.attachNewNode(directionalLight)) + + # Records the state of the arrow keys + def setKey(self, key, value): + self.keyMap[key] = value + + # Accepts arrow keys to move either the player or the menu cursor, + # Also deals with grid checking and collision detection + def move(self, task): + + # Get the time that elapsed since last frame. We multiply this with + # the desired speed in order to find out with which distance to move + # in order to achieve that desired speed. + dt = globalClock.getDt() + + # If the camera-left key is pressed, move camera left. + # If the camera-right key is pressed, move camera right. + + if self.keyMap["cam-left"]: + self.camera.setX(self.camera, -20 * dt) + if self.keyMap["cam-right"]: + self.camera.setX(self.camera, +20 * dt) + + # save ralph's initial position so that we can restore it, + # in case he falls off the map or runs into something. + + startpos = self.ralph.getPos() + + # If a move-key is pressed, move ralph in the specified direction. + + if self.keyMap["left"]: + self.ralph.setH(self.ralph.getH() + 300 * dt) + if self.keyMap["right"]: + self.ralph.setH(self.ralph.getH() - 300 * dt) + if self.keyMap["forward"]: + self.ralph.setY(self.ralph, -25 * dt) + + # If ralph is moving, loop the run animation. + # If he is standing still, stop the animation. + + if self.keyMap["forward"] or self.keyMap["left"] or self.keyMap["right"]: + if self.isMoving is False: + self.ralph.loop("run") + self.isMoving = True + else: + if self.isMoving: + self.ralph.stop() + self.ralph.pose("walk", 5) + self.isMoving = False + + # If the camera is too far from ralph, move it closer. + # If the camera is too close to ralph, move it farther. + + camvec = self.ralph.getPos() - self.camera.getPos() + camvec.setZ(0) + camdist = camvec.length() + camvec.normalize() + if camdist > 10.0: + self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10)) + camdist = 10.0 + if camdist < 5.0: + self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist)) + camdist = 5.0 + + # Normally, we would have to call traverse() to check for collisions. + # However, the class ShowBase that we inherit from has a task to do + # this for us, if we assign a CollisionTraverser to self.cTrav. + #self.cTrav.traverse(render) + + # Adjust ralph's Z coordinate. If ralph's ray hit terrain, + # update his Z. If it hit anything else, or didn't hit anything, put + # him back where he was last frame. + + entries = list(self.ralphGroundHandler.getEntries()) + entries.sort(key=lambda x: x.getSurfacePoint(render).getZ()) + + if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain": + self.ralph.setZ(entries[0].getSurfacePoint(render).getZ()) + else: + self.ralph.setPos(startpos) + + # Keep the camera at one foot above the terrain, + # or two feet above ralph, whichever is greater. + + entries = list(self.camGroundHandler.getEntries()) + entries.sort(key=lambda x: x.getSurfacePoint(render).getZ()) + + if len(entries) > 0 and entries[0].getIntoNode().getName() == "terrain": + self.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0) + if self.camera.getZ() < self.ralph.getZ() + 2.0: + self.camera.setZ(self.ralph.getZ() + 2.0) + + # The camera should look in ralph's direction, + # but it should also try to stay horizontal, so look at + # a floater which hovers above ralph's head. + self.camera.lookAt(self.floater) + + return task.cont + + +demo = RoamingRalphDemo() +demo.run() diff --git a/samples/roaming-ralph/models/ground.jpg b/samples/roaming-ralph/models/ground.jpg new file mode 100644 index 0000000000..d36aae42fd Binary files /dev/null and b/samples/roaming-ralph/models/ground.jpg differ diff --git a/samples/roaming-ralph/models/hedge.jpg b/samples/roaming-ralph/models/hedge.jpg new file mode 100644 index 0000000000..233dcbdc83 Binary files /dev/null and b/samples/roaming-ralph/models/hedge.jpg differ diff --git a/samples/roaming-ralph/models/ralph-run.egg.pz b/samples/roaming-ralph/models/ralph-run.egg.pz new file mode 100644 index 0000000000..e50ff41d24 Binary files /dev/null and b/samples/roaming-ralph/models/ralph-run.egg.pz differ diff --git a/samples/roaming-ralph/models/ralph-walk.egg.pz b/samples/roaming-ralph/models/ralph-walk.egg.pz new file mode 100644 index 0000000000..955352549c Binary files /dev/null and b/samples/roaming-ralph/models/ralph-walk.egg.pz differ diff --git a/samples/roaming-ralph/models/ralph.egg.pz b/samples/roaming-ralph/models/ralph.egg.pz new file mode 100644 index 0000000000..5749c11650 Binary files /dev/null and b/samples/roaming-ralph/models/ralph.egg.pz differ diff --git a/samples/roaming-ralph/models/ralph.jpg b/samples/roaming-ralph/models/ralph.jpg new file mode 100644 index 0000000000..198b4f5424 Binary files /dev/null and b/samples/roaming-ralph/models/ralph.jpg differ diff --git a/samples/roaming-ralph/models/rock03.jpg b/samples/roaming-ralph/models/rock03.jpg new file mode 100644 index 0000000000..8598e6de36 Binary files /dev/null and b/samples/roaming-ralph/models/rock03.jpg differ diff --git a/samples/roaming-ralph/models/tree.jpg b/samples/roaming-ralph/models/tree.jpg new file mode 100644 index 0000000000..fcdf4c73ca Binary files /dev/null and b/samples/roaming-ralph/models/tree.jpg differ diff --git a/samples/roaming-ralph/models/world.egg.pz b/samples/roaming-ralph/models/world.egg.pz new file mode 100644 index 0000000000..9ab9439f74 Binary files /dev/null and b/samples/roaming-ralph/models/world.egg.pz differ diff --git a/samples/shadows/advanced.py b/samples/shadows/advanced.py new file mode 100755 index 0000000000..6e8fbf5eb2 --- /dev/null +++ b/samples/shadows/advanced.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python + +from panda3d.core import * +import sys +import os + +import direct.directbase.DirectStart +from direct.interval.IntervalGlobal import * +from direct.gui.DirectGui import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.actor import Actor +from random import * + +# 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=.07, + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), shadow=(0, 0, 0, 1)) + + +class World(DirectObject): + + def __init__(self): + # Preliminary capabilities check. + + if not base.win.getGsg().getSupportsBasicShaders(): + self.t = addTitle( + "Shadow Demo: Video driver reports that shaders are not supported.") + return + if not base.win.getGsg().getSupportsDepthTexture(): + self.t = addTitle( + "Shadow Demo: Video driver reports that depth textures are not supported.") + return + + # creating the offscreen buffer. + + winprops = WindowProperties.size(512, 512) + props = FrameBufferProperties() + props.setRgbColor(1) + props.setAlphaBits(1) + props.setDepthBits(1) + LBuffer = base.graphicsEngine.makeOutput( + base.pipe, "offscreen buffer", -2, + props, winprops, + GraphicsPipe.BFRefuseWindow, + base.win.getGsg(), base.win) + + if not LBuffer: + self.t = addTitle( + "Shadow Demo: Video driver cannot create an offscreen buffer.") + return + + Ldepthmap = Texture() + LBuffer.addRenderTexture(Ldepthmap, GraphicsOutput.RTMBindOrCopy, + GraphicsOutput.RTPDepthStencil) + if base.win.getGsg().getSupportsShadowFilter(): + Ldepthmap.setMinfilter(Texture.FTShadow) + Ldepthmap.setMagfilter(Texture.FTShadow) + + # Adding a color texture is totally unnecessary, but it helps with + # debugging. + Lcolormap = Texture() + LBuffer.addRenderTexture(Lcolormap, GraphicsOutput.RTMBindOrCopy, + GraphicsOutput.RTPColor) + + self.inst_p = addInstructions(0.06, 'P : stop/start the Panda Rotation') + self.inst_w = addInstructions(0.12, 'W : stop/start the Walk Cycle') + self.inst_t = addInstructions(0.18, 'T : stop/start the Teapot') + self.inst_l = addInstructions(0.24, 'L : move light source far or close') + self.inst_v = addInstructions(0.30, 'V: View the Depth-Texture results') + self.inst_x = addInstructions(0.36, 'Left/Right Arrow : switch camera angles') + self.inst_a = addInstructions(0.42, 'Something about A/Z and push bias') + + base.setBackgroundColor(0, 0, 0.2, 1) + + base.camLens.setNearFar(1.0, 10000) + base.camLens.setFov(75) + base.disableMouse() + + # Load the scene. + + floorTex = loader.loadTexture('maps/envir-ground.jpg') + cm = CardMaker('') + cm.setFrame(-2, 2, -2, 2) + floor = render.attachNewNode(PandaNode("floor")) + for y in range(12): + for x in range(12): + nn = floor.attachNewNode(cm.generate()) + nn.setP(-90) + nn.setPos((x - 6) * 4, (y - 6) * 4, 0) + floor.setTexture(floorTex) + floor.flattenStrong() + + self.pandaAxis = render.attachNewNode('panda axis') + self.pandaModel = Actor.Actor('panda-model', {'walk': 'panda-walk4'}) + self.pandaModel.reparentTo(self.pandaAxis) + self.pandaModel.setPos(9, 0, 0) + self.pandaModel.setShaderInput("scale", 0.01, 0.01, 0.01, 1.0) + self.pandaWalk = self.pandaModel.actorInterval('walk', playRate=1.8) + self.pandaWalk.loop() + self.pandaMovement = self.pandaAxis.hprInterval( + 20.0, LPoint3(-360, 0, 0), startHpr=LPoint3(0, 0, 0)) + self.pandaMovement.loop() + + self.teapot = loader.loadModel('teapot') + self.teapot.reparentTo(render) + self.teapot.setPos(0, -20, 10) + self.teapot.setShaderInput("texDisable", 1, 1, 1, 1) + self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360)) + self.teapotMovement.loop() + + self.accept('escape', sys.exit) + + self.accept("arrow_left", self.incrementCameraPosition, [-1]) + self.accept("arrow_right", self.incrementCameraPosition, [1]) + self.accept("p", self.toggleInterval, [self.pandaMovement]) + self.accept("P", self.toggleInterval, [self.pandaMovement]) + self.accept("t", self.toggleInterval, [self.teapotMovement]) + self.accept("T", self.toggleInterval, [self.teapotMovement]) + self.accept("w", self.toggleInterval, [self.pandaWalk]) + self.accept("W", self.toggleInterval, [self.pandaWalk]) + self.accept("v", base.bufferViewer.toggleEnable) + self.accept("V", base.bufferViewer.toggleEnable) + self.accept("l", self.incrementLightPosition, [1]) + self.accept("L", self.incrementLightPosition, [1]) + self.accept("o", base.oobe) + self.accept('a', self.adjustPushBias, [1.1]) + self.accept('A', self.adjustPushBias, [1.1]) + self.accept('z', self.adjustPushBias, [0.9]) + self.accept('Z', self.adjustPushBias, [0.9]) + + self.LCam = base.makeCamera(LBuffer) + self.LCam.node().setScene(render) + self.LCam.node().getLens().setFov(40) + self.LCam.node().getLens().setNearFar(10, 100) + + # default values + self.pushBias = 0.04 + self.ambient = 0.2 + self.cameraSelection = 0 + self.lightSelection = 0 + + # setting up shader + render.setShaderInput('light', self.LCam) + render.setShaderInput('Ldepthmap', Ldepthmap) + render.setShaderInput('ambient', self.ambient, 0, 0, 1.0) + render.setShaderInput('texDisable', 0, 0, 0, 0) + render.setShaderInput('scale', 1, 1, 1, 1) + + # Put a shader on the Light camera. + lci = NodePath(PandaNode("Light Camera Initializer")) + lci.setShader(loader.loadShader('caster.sha')) + self.LCam.node().setInitialState(lci.getState()) + + # Put a shader on the Main camera. + # Some video cards have special hardware for shadow maps. + # If the card has that, use it. If not, use a different + # shader that does not require hardware support. + + mci = NodePath(PandaNode("Main Camera Initializer")) + if base.win.getGsg().getSupportsShadowFilter(): + mci.setShader(loader.loadShader('shadow.sha')) + else: + mci.setShader(loader.loadShader('shadow-nosupport.sha')) + base.cam.node().setInitialState(mci.getState()) + + self.incrementCameraPosition(0) + self.incrementLightPosition(0) + self.adjustPushBias(1.0) + + def toggleInterval(self, ival): + if ival.isPlaying(): + ival.pause() + else: + ival.resume() + + def incrementCameraPosition(self, n): + self.cameraSelection = (self.cameraSelection + n) % 6 + if (self.cameraSelection == 0): + base.cam.reparentTo(render) + base.cam.setPos(30, -45, 26) + base.cam.lookAt(0, 0, 0) + self.LCam.node().hideFrustum() + if (self.cameraSelection == 1): + base.cam.reparentTo(self.pandaModel) + base.cam.setPos(7, -3, 9) + base.cam.lookAt(0, 0, 0) + self.LCam.node().hideFrustum() + if (self.cameraSelection == 2): + base.cam.reparentTo(self.pandaModel) + base.cam.setPos(-7, -3, 9) + base.cam.lookAt(0, 0, 0) + self.LCam.node().hideFrustum() + if (self.cameraSelection == 3): + base.cam.reparentTo(render) + base.cam.setPos(7, -23, 12) + base.cam.lookAt(self.teapot) + self.LCam.node().hideFrustum() + if (self.cameraSelection == 4): + base.cam.reparentTo(render) + base.cam.setPos(-7, -23, 12) + base.cam.lookAt(self.teapot) + self.LCam.node().hideFrustum() + if (self.cameraSelection == 5): + base.cam.reparentTo(render) + base.cam.setPos(1000, 0, 195) + base.cam.lookAt(0, 0, 0) + self.LCam.node().showFrustum() + + def incrementLightPosition(self, n): + self.lightSelection = (self.lightSelection + n) % 2 + if (self.lightSelection == 0): + self.LCam.setPos(0, -40, 25) + self.LCam.lookAt(0, -10, 0) + self.LCam.node().getLens().setNearFar(10, 100) + if (self.lightSelection == 1): + self.LCam.setPos(0, -600, 200) + self.LCam.lookAt(0, -10, 0) + self.LCam.node().getLens().setNearFar(10, 1000) + + def shaderSupported(self): + return base.win.getGsg().getSupportsBasicShaders() and \ + base.win.getGsg().getSupportsDepthTexture() and \ + base.win.getGsg().getSupportsShadowFilter() + + def adjustPushBias(self, inc): + self.pushBias *= inc + self.inst_a.setText( + 'A/Z: Increase/Decrease the Push-Bias [%F]' % self.pushBias) + render.setShaderInput('push', self.pushBias) + +w = World() +base.run() diff --git a/samples/shadows/basic.py b/samples/shadows/basic.py new file mode 100755 index 0000000000..d6ec8671f5 --- /dev/null +++ b/samples/shadows/basic.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +from panda3d.core import * +import sys +import os + +import direct.directbase.DirectStart +from direct.interval.IntervalGlobal import * +from direct.gui.DirectGui import OnscreenText +from direct.showbase.DirectObject import DirectObject +from direct.actor import Actor +from random import * + +# 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=.07, + parent=base.a2dBottomRight, align=TextNode.ARight, + pos=(-0.1, 0.09), shadow=(0, 0, 0, 1)) + + +class World(DirectObject): + + def __init__(self): + # Preliminary capabilities check. + + if not base.win.getGsg().getSupportsBasicShaders(): + self.t = addTitle( + "Shadow Demo: Video driver reports that shaders are not supported.") + return + if not base.win.getGsg().getSupportsDepthTexture(): + self.t = addTitle( + "Shadow Demo: Video driver reports that depth textures are not supported.") + return + + self.inst_p = addInstructions(0.06, 'P : stop/start the Panda Rotation') + self.inst_w = addInstructions(0.12, 'W : stop/start the Walk Cycle') + self.inst_t = addInstructions(0.18, 'T : stop/start the Teapot') + self.inst_l = addInstructions(0.24, 'L : move light source far or close') + self.inst_v = addInstructions(0.30, 'V: View the Depth-Texture results') + self.inst_x = addInstructions(0.36, 'Left/Right Arrow : switch camera angles') + + base.setBackgroundColor(0, 0, 0.2, 1) + + base.camLens.setNearFar(1.0, 10000) + base.camLens.setFov(75) + base.disableMouse() + + # Load the scene. + floorTex = loader.loadTexture('maps/envir-ground.jpg') + + cm = CardMaker('') + cm.setFrame(-2, 2, -2, 2) + floor = render.attachNewNode(PandaNode("floor")) + for y in range(12): + for x in range(12): + nn = floor.attachNewNode(cm.generate()) + nn.setP(-90) + nn.setPos((x - 6) * 4, (y - 6) * 4, 0) + floor.setTexture(floorTex) + floor.flattenStrong() + + self.pandaAxis = render.attachNewNode('panda axis') + self.pandaModel = Actor.Actor('panda-model', {'walk': 'panda-walk4'}) + self.pandaModel.reparentTo(self.pandaAxis) + self.pandaModel.setPos(9, 0, 0) + self.pandaModel.setScale(0.01) + self.pandaWalk = self.pandaModel.actorInterval('walk', playRate=1.8) + self.pandaWalk.loop() + self.pandaMovement = self.pandaAxis.hprInterval( + 20.0, LPoint3(-360, 0, 0), startHpr=LPoint3(0, 0, 0)) + self.pandaMovement.loop() + + self.teapot = loader.loadModel('teapot') + self.teapot.reparentTo(render) + self.teapot.setPos(0, -20, 10) + self.teapot.setShaderInput("texDisable", 1, 1, 1, 1) + self.teapotMovement = self.teapot.hprInterval(50, LPoint3(0, 360, 360)) + self.teapotMovement.loop() + + self.accept('escape', sys.exit) + + self.accept("arrow_left", self.incrementCameraPosition, [-1]) + self.accept("arrow_right", self.incrementCameraPosition, [1]) + self.accept("p", self.toggleInterval, [self.pandaMovement]) + self.accept("P", self.toggleInterval, [self.pandaMovement]) + self.accept("t", self.toggleInterval, [self.teapotMovement]) + self.accept("T", self.toggleInterval, [self.teapotMovement]) + self.accept("w", self.toggleInterval, [self.pandaWalk]) + self.accept("W", self.toggleInterval, [self.pandaWalk]) + self.accept("v", base.bufferViewer.toggleEnable) + self.accept("V", base.bufferViewer.toggleEnable) + self.accept("l", self.incrementLightPosition, [1]) + self.accept("L", self.incrementLightPosition, [1]) + self.accept("o", base.oobe) + + self.light = render.attachNewNode(Spotlight("Spot")) + self.light.node().setScene(render) + self.light.node().setShadowCaster(True) + self.light.node().showFrustum() + self.light.node().getLens().setFov(40) + self.light.node().getLens().setNearFar(10, 100) + render.setLight(self.light) + + self.alight = render.attachNewNode(AmbientLight("Ambient")) + self.alight.node().setColor(LVector4(0.2, 0.2, 0.2, 1)) + render.setLight(self.alight) + + # Important! Enable the shader generator. + render.setShaderAuto() + + # default values + self.cameraSelection = 0 + self.lightSelection = 0 + + self.incrementCameraPosition(0) + self.incrementLightPosition(0) + + def toggleInterval(self, ival): + if ival.isPlaying(): + ival.pause() + else: + ival.resume() + + def incrementCameraPosition(self, n): + self.cameraSelection = (self.cameraSelection + n) % 6 + if (self.cameraSelection == 0): + base.cam.reparentTo(render) + base.cam.setPos(30, -45, 26) + base.cam.lookAt(0, 0, 0) + self.light.node().hideFrustum() + if (self.cameraSelection == 1): + base.cam.reparentTo(self.pandaModel) + base.cam.setPos(7, -3, 9) + base.cam.lookAt(0, 0, 0) + self.light.node().hideFrustum() + if (self.cameraSelection == 2): + base.cam.reparentTo(self.pandaModel) + base.cam.setPos(-7, -3, 9) + base.cam.lookAt(0, 0, 0) + self.light.node().hideFrustum() + if (self.cameraSelection == 3): + base.cam.reparentTo(render) + base.cam.setPos(7, -23, 12) + base.cam.lookAt(self.teapot) + self.light.node().hideFrustum() + if (self.cameraSelection == 4): + base.cam.reparentTo(render) + base.cam.setPos(-7, -23, 12) + base.cam.lookAt(self.teapot) + self.light.node().hideFrustum() + if (self.cameraSelection == 5): + base.cam.reparentTo(render) + base.cam.setPos(1000, 0, 195) + base.cam.lookAt(0, 0, 0) + self.light.node().showFrustum() + + def incrementLightPosition(self, n): + self.lightSelection = (self.lightSelection + n) % 2 + if (self.lightSelection == 0): + self.light.setPos(0, -40, 25) + self.light.lookAt(0, -10, 0) + self.light.node().getLens().setNearFar(10, 100) + if (self.lightSelection == 1): + self.light.setPos(0, -600, 200) + self.light.lookAt(0, -10, 0) + self.light.node().getLens().setNearFar(10, 1000) + + def shaderSupported(self): + return base.win.getGsg().getSupportsBasicShaders() and \ + base.win.getGsg().getSupportsDepthTexture() and \ + base.win.getGsg().getSupportsShadowFilter() + +w = World() +base.run() diff --git a/samples/shadows/caster.sha b/samples/shadows/caster.sha new file mode 100644 index 0000000000..ea463d3bd2 --- /dev/null +++ b/samples/shadows/caster.sha @@ -0,0 +1,20 @@ +//Cg + +void vshader(float4 vtx_position : POSITION, + uniform float4x4 mat_modelproj, + uniform float4 k_scale, + out float4 l_position : POSITION, + out float4 l_pos : TEXCOORD0 + ) +{ + float4 position = vtx_position * k_scale; + l_pos = mul(mat_modelproj, position); + l_position = l_pos; +} + +void fshader(in float4 l_pos : TEXCOORD0, + out float4 o_color : COLOR) +{ + float z = (l_pos.z / l_pos.w) * 0.5 + 0.5; + o_color = float4(z, z, z, 1); +} diff --git a/samples/shadows/shadow-nosupport.sha b/samples/shadows/shadow-nosupport.sha new file mode 100644 index 0000000000..a486c91891 --- /dev/null +++ b/samples/shadows/shadow-nosupport.sha @@ -0,0 +1,59 @@ +//Cg + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + float3 vtx_normal : NORMAL, + + uniform float4x4 trans_model_to_clip_of_light, + uniform float4x4 mat_modelproj, + uniform float4 mspos_light, + uniform float4 k_ambient, + uniform float4 k_scale, + uniform float k_push, + + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0, + out float4 l_shadowcoord : TEXCOORD1, + out float l_smooth : TEXCOORD2, + out float4 l_lightclip : TEXCOORD3) +{ + float4 position = vtx_position * k_scale; + + // vertex position + l_position = mul(mat_modelproj, position); + + // Pass through texture coordinate for main texture. + l_texcoord0 = vtx_texcoord0; + + // Calculate the surface lighting factor. + l_smooth = saturate(dot(vtx_normal, normalize(mspos_light.xyz - position.xyz))); + + // Calculate light-space clip position. + float4 pushed = position + float4(vtx_normal * k_push, 0); + l_lightclip = mul(trans_model_to_clip_of_light, pushed); + + // Calculate shadow-map texture coordinates. + l_shadowcoord = l_lightclip * float4(0.5, 0.5, 0.5, 1.0) + + l_lightclip.w * float4(0.5, 0.5, 0.5, 0.0); +} + + +void fshader(in float2 l_texcoord0 : TEXCOORD0, + in float4 l_shadowcoord : TEXCOORD1, + in float l_smooth : TEXCOORD2, + in float4 l_lightclip : TEXCOORD3, + uniform sampler2D tex_0 : TEXUNIT0, + uniform sampler2D k_Ldepthmap : TEXUNIT1, + uniform float4 k_ambient, + uniform float4 k_texDisable, + out float4 o_color : COLOR) +{ + float3 circleoffs = float3(l_lightclip.xy / l_lightclip.w, 0); + float falloff = saturate(1.0 - dot(circleoffs, circleoffs)); + float4 baseColor = saturate(tex2D(tex_0, l_texcoord0) + k_texDisable); + float4 proj = l_shadowcoord / l_shadowcoord.w; + float mapval = f1tex2D(k_Ldepthmap, proj.xy); + float shade = (mapval > proj.z); + o_color = baseColor * (falloff * shade * l_smooth + k_ambient.x); +} + diff --git a/samples/shadows/shadow.sha b/samples/shadows/shadow.sha new file mode 100644 index 0000000000..b9ada780f5 --- /dev/null +++ b/samples/shadows/shadow.sha @@ -0,0 +1,56 @@ +//Cg + +void vshader(float4 vtx_position : POSITION, + float2 vtx_texcoord0 : TEXCOORD0, + float3 vtx_normal : NORMAL, + + uniform float4x4 trans_model_to_clip_of_light, + uniform float4x4 mat_modelproj, + uniform float4 mspos_light, + uniform float4 k_ambient, + uniform float4 k_scale, + uniform float k_push, + + out float4 l_position : POSITION, + out float2 l_texcoord0 : TEXCOORD0, + out float4 l_shadowcoord : TEXCOORD1, + out float l_smooth : TEXCOORD2, + out float4 l_lightclip : TEXCOORD3) +{ + float4 position = vtx_position * k_scale; + + // vertex position + l_position = mul(mat_modelproj, position); + + // Pass through texture coordinate for main texture. + l_texcoord0 = vtx_texcoord0; + + // Calculate the surface lighting factor. + l_smooth = saturate(dot(vtx_normal, normalize(mspos_light.xyz - position.xyz))); + + // Calculate light-space clip position. + float4 pushed = position + float4(vtx_normal * k_push, 0); + l_lightclip = mul(trans_model_to_clip_of_light, pushed); + + // Calculate shadow-map texture coordinates. + l_shadowcoord = l_lightclip * float4(0.5, 0.5, 0.5, 1.0) + + l_lightclip.w * float4(0.5, 0.5, 0.5, 0.0); +} + + +void fshader(in float2 l_texcoord0 : TEXCOORD0, + in float4 l_shadowcoord : TEXCOORD1, + in float l_smooth : TEXCOORD2, + in float4 l_lightclip : TEXCOORD3, + uniform sampler2D tex_0 : TEXUNIT0, + uniform sampler2DShadow k_Ldepthmap : TEXUNIT1, + uniform float4 k_ambient, + uniform float4 k_texDisable, + out float4 o_color : COLOR) +{ + float3 circleoffs = float3(l_lightclip.xy / l_lightclip.w, 0); + float falloff = saturate(1.0 - dot(circleoffs, circleoffs)); + float4 baseColor = saturate(tex2D(tex_0, l_texcoord0) + k_texDisable); + float shade = shadow2DProj(k_Ldepthmap, l_shadowcoord).r; + o_color = baseColor * (falloff * shade * l_smooth + k_ambient.x); +} diff --git a/samples/solar-system/models/deimos_1k_tex.jpg b/samples/solar-system/models/deimos_1k_tex.jpg new file mode 100644 index 0000000000..9ab0fbee80 Binary files /dev/null and b/samples/solar-system/models/deimos_1k_tex.jpg differ diff --git a/samples/solar-system/models/earth_1k_tex.jpg b/samples/solar-system/models/earth_1k_tex.jpg new file mode 100644 index 0000000000..97bc0662f8 Binary files /dev/null and b/samples/solar-system/models/earth_1k_tex.jpg differ diff --git a/samples/solar-system/models/mars_1k_tex.jpg b/samples/solar-system/models/mars_1k_tex.jpg new file mode 100644 index 0000000000..d78e1e7a05 Binary files /dev/null and b/samples/solar-system/models/mars_1k_tex.jpg differ diff --git a/samples/solar-system/models/mercury_1k_tex.jpg b/samples/solar-system/models/mercury_1k_tex.jpg new file mode 100644 index 0000000000..b971efd382 Binary files /dev/null and b/samples/solar-system/models/mercury_1k_tex.jpg differ diff --git a/samples/solar-system/models/moon_1k_tex.jpg b/samples/solar-system/models/moon_1k_tex.jpg new file mode 100644 index 0000000000..2a0b0d0234 Binary files /dev/null and b/samples/solar-system/models/moon_1k_tex.jpg differ diff --git a/samples/solar-system/models/phobos_1k_tex.jpg b/samples/solar-system/models/phobos_1k_tex.jpg new file mode 100644 index 0000000000..516cbec6d8 Binary files /dev/null and b/samples/solar-system/models/phobos_1k_tex.jpg differ diff --git a/samples/solar-system/models/planet_sphere.egg.pz b/samples/solar-system/models/planet_sphere.egg.pz new file mode 100644 index 0000000000..bc83ac21c1 Binary files /dev/null and b/samples/solar-system/models/planet_sphere.egg.pz differ diff --git a/samples/solar-system/models/solar_sky_sphere.egg.pz b/samples/solar-system/models/solar_sky_sphere.egg.pz new file mode 100644 index 0000000000..3ce49155e8 Binary files /dev/null and b/samples/solar-system/models/solar_sky_sphere.egg.pz differ diff --git a/samples/solar-system/models/stars_1k_tex.jpg b/samples/solar-system/models/stars_1k_tex.jpg new file mode 100644 index 0000000000..07735f1fb1 Binary files /dev/null and b/samples/solar-system/models/stars_1k_tex.jpg differ diff --git a/samples/solar-system/models/sun_1k_tex.jpg b/samples/solar-system/models/sun_1k_tex.jpg new file mode 100644 index 0000000000..42ffcd1339 Binary files /dev/null and b/samples/solar-system/models/sun_1k_tex.jpg differ diff --git a/samples/solar-system/models/venus_1k_tex.jpg b/samples/solar-system/models/venus_1k_tex.jpg new file mode 100644 index 0000000000..865af1a785 Binary files /dev/null and b/samples/solar-system/models/venus_1k_tex.jpg differ diff --git a/samples/solar-system/step1_blank_window.py b/samples/solar-system/step1_blank_window.py new file mode 100755 index 0000000000..a702a382af --- /dev/null +++ b/samples/solar-system/step1_blank_window.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial is intended as a initial panda scripting lesson going over +# display initialization, loading models, placing objects, and the scene graph. +# +# Step 1: ShowBase contains the main Panda3D modules. Importing it +# initializes Panda and creates the window. The run() command causes the +# real-time simulation to begin + +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +base.run() diff --git a/samples/solar-system/step2_basic_setup.py b/samples/solar-system/step2_basic_setup.py new file mode 100755 index 0000000000..0588491b6d --- /dev/null +++ b/samples/solar-system/step2_basic_setup.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial is intended as a initial panda scripting lesson going over +# display initialization, loading models, placing objects, and the scene graph. +# +# Step 2: After initializing panda, we define a class called World. We put +# all of our code in a class to provide a convenient way to keep track of +# all of the variables our project will use, and in later tutorials to handle +# keyboard input. +# The code contained in the __init__ method is executed when we instantiate +# the class (at the end of this file). Inside __init__ we will first change +# the background color of the window. We then disable the mouse-based camera +# control and set the camera position. + +# Initialize Panda and create a window +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +from panda3d.core import * # Contains most of Panda's modules +from direct.gui.DirectGui import * # Imports Gui objects we use for putting +# text on the screen +import sys + + +class World(object): # Our main class + def __init__(self): # The initialization method caused when a + # world object is created + + # Create some text overlayed on our screen. + # We will use similar commands in all of our tutorials to create titles and + # instruction guides. + self.title = OnscreenText( + text="Panda3D: Tutorial 1 - Solar System", + parent=base.a2dBottomRight, align=TextNode.A_right, + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07) + + # Make the background color black (R=0, G=0, B=0) + # instead of the default grey + base.setBackgroundColor(0, 0, 0) + + # By default, the mouse controls the camera. Often, we disable that so that + # the camera can be placed manually (if we don't do this, our placement + # commands will be overridden by the mouse control) + base.disableMouse() + + # Set the camera position (x, y, z) + camera.setPos(0, 0, 45) + + # Set the camera orientation (heading, pitch, roll) in degrees + camera.setHpr(0, -90, 0) +# end class world + +# Now that our class is defined, we create an instance of it. +# Doing so calls the __init__ method set up above +w = World() +# As usual - run() must be called before anything can be shown on screen +base.run() diff --git a/samples/solar-system/step3_load_model.py b/samples/solar-system/step3_load_model.py new file mode 100755 index 0000000000..d60be02c98 --- /dev/null +++ b/samples/solar-system/step3_load_model.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial is intended as a initial panda scripting lesson going over +# display initialization, loading models, placing objects, and the scene graph. +# +# Step 3: In this step, we create a function called loadPlanets, which will +# eventually be used to load all of the planets in our simulation. For now +# we will load just the sun and and the sky-sphere we use to create the +# star-field. + +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +from panda3d.core import NodePath, TextNode +from direct.gui.DirectGui import * +import sys + + +class World(object): + def __init__(self): + # This is the initialization we had before + self.title = OnscreenText( # Create the title + text="Panda3D: Tutorial 1 - Solar System", + parent=base.a2dBottomRight, align=TextNode.A_right, + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07) + + base.setBackgroundColor(0, 0, 0) # Set the background to black + base.disableMouse() # disable mouse control of the camera + camera.setPos(0, 0, 45) # Set the camera position (X, Y, Z) + camera.setHpr(0, -90, 0) # Set the camera orientation + #(heading, pitch, roll) in degrees + + # We will now define a variable to help keep a consistent scale in + # our model. As we progress, we will continue to add variables here as we + # need them + + # The value of this variable scales the size of the planets. True scale size + # would be 1 + self.sizescale = 0.6 + + # Now that we have finished basic initialization, we call loadPlanets which + # will handle actually getting our objects in the world + self.loadPlanets() + + def loadPlanets(self): + # Here, inside our class, is where we are creating the loadPlanets function + # For now we are just loading the star-field and sun. In the next step we + # will load all of the planets + + # Loading objects in Panda is done via the command loader.loadModel, which + # takes one argument, the path to the model file. Models in Panda come in + # two types, .egg (which is readable in a text editor), and .bam (which is + # not readable but makes smaller files). When you load a file you leave the + # extension off so that it can choose the right version + + # Load model returns a NodePath, which you can think of as an object + # containing your model + + # Here we load the sky model. For all the planets we will use the same + # sphere model and simply change textures. However, even though the sky is + # a sphere, it is different from the planet model because its polygons + #(which are always one-sided in Panda) face inside the sphere instead of + # outside (this is known as a model with reversed normals). Because of + # that it has to be a separate model. + self.sky = loader.loadModel("models/solar_sky_sphere") + + # After the object is loaded, it must be placed in the scene. We do this by + # changing the parent of self.sky to render, which is a special NodePath. + # Each frame, Panda starts with render and renders everything attached to + # it. + self.sky.reparentTo(render) + + # You can set the position, orientation, and scale on a NodePath the same + # way that you set those properties on the camera. In fact, the camera is + # just another special NodePath + self.sky.setScale(40) + + # Very often, the egg file will know what textures are needed and load them + # automatically. But sometimes we want to set our textures manually, (for + # instance we want to put different textures on the same planet model) + # Loading textures works the same way as loading models, but instead of + # calling loader.loadModel, we call loader.loadTexture + self.sky_tex = loader.loadTexture("models/stars_1k_tex.jpg") + + # Finally, the following line sets our new sky texture on our sky model. + # The second argument must be one or the command will be ignored. + self.sky.setTexture(self.sky_tex, 1) + + # Now we load the sun. + self.sun = loader.loadModel("models/planet_sphere") + # Now we repeat our other steps + self.sun.reparentTo(render) + self.sun_tex = loader.loadTexture("models/sun_1k_tex.jpg") + self.sun.setTexture(self.sun_tex, 1) + # The sun is really much bigger than + self.sun.setScale(2 * self.sizescale) + # this, but to be able to see the + # planets we're making it smaller + + # end loadPlanets() + +# end class world + +# instantiate the class +w = World() +base.run() diff --git a/samples/solar-system/step4_load_system.py b/samples/solar-system/step4_load_system.py new file mode 100755 index 0000000000..e2e530a868 --- /dev/null +++ b/samples/solar-system/step4_load_system.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial is intended as a initial panda scripting lesson going over +# display initialization, loading models, placing objects, and the scene graph. +# +# Step 4: In this step, we will load the rest of the planets up to Mars. +# In addition to loading them, we will organize how the planets are grouped +# hierarchically in the scene. This will help us rotate them in the next step +# to give a rough simulation of the solar system. You can see them move by +# running step_5_complete_solar_system.py. + +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +from panda3d.core import NodePath, TextNode +from direct.gui.DirectGui import * +import sys + + +class World(object): + + def __init__(self): + # This is the initialization we had before + self.title = OnscreenText( # Create the title + text="Panda3D: Tutorial 1 - Solar System", + parent=base.a2dBottomRight, align=TextNode.A_right, + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07) + + base.setBackgroundColor(0, 0, 0) # Set the background to black + base.disableMouse() # disable mouse control of the camera + camera.setPos(0, 0, 45) # Set the camera position (X, Y, Z) + camera.setHpr(0, -90, 0) # Set the camera orientation + #(heading, pitch, roll) in degrees + + # This section has our variables. This time we are adding a variable to + # control the relative size of the orbits. + self.sizescale = 0.6 # relative size of planets + self.orbitscale = 10 # relative size of orbits + + self.loadPlanets() # Load our models and make them render + + def loadPlanets(self): + # Here is where we load all of the planets, and place them. + # The first thing we do is create a dummy node for each planet. A dummy + # node is simply a node path that does not have any geometry attached to it. + # This is done by .attachNewNode('name_of_new_node') + + # We do this because positioning the planets around a circular orbit could + # be done with a lot of messy sine and cosine operations. Instead, we define + # our planets to be a given distance from a dummy node, and when we turn the + # dummy, the planets will move along with it, kind of like turning the + # center of a disc and having an object at its edge move. Most attributes, + # like position, orientation, scale, texture, color, etc., are inherited + # this way. Panda deals with the fact that the objects are not attached + # directly to render (they are attached through other NodePaths to render), + # and makes sure the attributes inherit. + + # This system of attaching NodePaths to each other is called the Scene + # Graph + self.orbit_root_mercury = render.attachNewNode('orbit_root_mercury') + self.orbit_root_venus = render.attachNewNode('orbit_root_venus') + self.orbit_root_mars = render.attachNewNode('orbit_root_mars') + self.orbit_root_earth = render.attachNewNode('orbit_root_earth') + + # orbit_root_moon is like all the other orbit_root dummy nodes except that + # it will be parented to orbit_root_earth so that the moon will orbit the + # earth instead of the sun. So, the moon will first inherit + # orbit_root_moon's position and then orbit_root_earth's. There is no hard + # limit on how many objects can inherit from each other. + self.orbit_root_moon = ( + self.orbit_root_earth.attachNewNode('orbit_root_moon')) + + ############################################################### + + # These are the same steps used to load the sky model that we used in the + # last step + # Load the model for the sky + self.sky = loader.loadModel("models/solar_sky_sphere") + # Load the texture for the sky. + self.sky_tex = loader.loadTexture("models/stars_1k_tex.jpg") + # Set the sky texture to the sky model + self.sky.setTexture(self.sky_tex, 1) + # Parent the sky model to the render node so that the sky is rendered + self.sky.reparentTo(render) + # Scale the size of the sky. + self.sky.setScale(40) + + # These are the same steps we used to load the sun in the last step. + # Again, we use loader.loadModel since we're using planet_sphere more + # than once. + self.sun = loader.loadModel("models/planet_sphere") + self.sun_tex = loader.loadTexture("models/sun_1k_tex.jpg") + self.sun.setTexture(self.sun_tex, 1) + self.sun.reparentTo(render) + self.sun.setScale(2 * self.sizescale) + + # Now we load the planets, which we load using the same steps we used to + # load the sun. The only difference is that the models are not parented + # directly to render for the reasons described above. + # The values used for scale are the ratio of the planet's radius to Earth's + # radius, multiplied by our global scale variable. In the same way, the + # values used for orbit are the ratio of the planet's orbit to Earth's + # orbit, multiplied by our global orbit scale variable + + # Load mercury + self.mercury = loader.loadModel("models/planet_sphere") + self.mercury_tex = loader.loadTexture("models/mercury_1k_tex.jpg") + self.mercury.setTexture(self.mercury_tex, 1) + self.mercury.reparentTo(self.orbit_root_mercury) + # Set the position of mercury. By default, all nodes are pre assigned the + # position (0, 0, 0) when they are first loaded. We didn't reposition the + # sun and sky because they are centered in the solar system. Mercury, + # however, needs to be offset so we use .setPos to offset the + # position of mercury in the X direction with respect to its orbit radius. + # We will do this for the rest of the planets. + self.mercury.setPos(0.38 * self.orbitscale, 0, 0) + self.mercury.setScale(0.385 * self.sizescale) + + # Load Venus + self.venus = loader.loadModel("models/planet_sphere") + self.venus_tex = loader.loadTexture("models/venus_1k_tex.jpg") + self.venus.setTexture(self.venus_tex, 1) + self.venus.reparentTo(self.orbit_root_venus) + self.venus.setPos(0.72 * self.orbitscale, 0, 0) + self.venus.setScale(0.923 * self.sizescale) + + # Load Mars + self.mars = loader.loadModel("models/planet_sphere") + self.mars_tex = loader.loadTexture("models/mars_1k_tex.jpg") + self.mars.setTexture(self.mars_tex, 1) + self.mars.reparentTo(self.orbit_root_mars) + self.mars.setPos(1.52 * self.orbitscale, 0, 0) + self.mars.setScale(0.515 * self.sizescale) + + # Load Earth + self.earth = loader.loadModel("models/planet_sphere") + self.earth_tex = loader.loadTexture("models/earth_1k_tex.jpg") + self.earth.setTexture(self.earth_tex, 1) + self.earth.reparentTo(self.orbit_root_earth) + self.earth.setScale(self.sizescale) + self.earth.setPos(self.orbitscale, 0, 0) + + # The center of the moon's orbit is exactly the same distance away from + # The sun as the Earth's distance from the sun + self.orbit_root_moon.setPos(self.orbitscale, 0, 0) + + # Load the moon + self.moon = loader.loadModel("models/planet_sphere") + self.moon_tex = loader.loadTexture("models/moon_1k_tex.jpg") + self.moon.setTexture(self.moon_tex, 1) + self.moon.reparentTo(self.orbit_root_moon) + self.moon.setScale(0.1 * self.sizescale) + self.moon.setPos(0.1 * self.orbitscale, 0, 0) + # end loadPlanets() + +# end class world + +w = World() +base.run() diff --git a/samples/solar-system/step5_complete_solar_system.py b/samples/solar-system/step5_complete_solar_system.py new file mode 100755 index 0000000000..3e2a0e2bd1 --- /dev/null +++ b/samples/solar-system/step5_complete_solar_system.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial is intended as a initial panda scripting lesson going over +# display initialization, loading models, placing objects, and the scene graph. +# +# Step 5: Here we put the finishing touches on our solar system model by +# making the planets move. The actual code for doing the movement is covered +# in the next tutorial, but watching it move really shows what inheritance on +# the scene graph is all about. + +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +from direct.gui.DirectGui import * +from panda3d.core import TextNode +import sys + + +class World(object): + + def __init__(self): + # This is the initialization we had before + self.title = OnscreenText( # Create the title + text="Panda3D: Tutorial 1 - Solar System", + parent=base.a2dBottomRight, align=TextNode.A_right, + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07) + + base.setBackgroundColor(0, 0, 0) # Set the background to black + base.disableMouse() # disable mouse control of the camera + camera.setPos(0, 0, 45) # Set the camera position (X, Y, Z) + camera.setHpr(0, -90, 0) # Set the camera orientation + #(heading, pitch, roll) in degrees + + # Here again is where we put our global variables. Added this time are + # variables to control the relative speeds of spinning and orbits in the + # simulation + # Number of seconds a full rotation of Earth around the sun should take + self.yearscale = 60 + # Number of seconds a day rotation of Earth should take. + # It is scaled from its correct value for easier visability + self.dayscale = self.yearscale / 365.0 * 5 + self.orbitscale = 10 # Orbit scale + self.sizescale = 0.6 # Planet size scale + + self.loadPlanets() # Load and position the models + + # Finally, we call the rotatePlanets function which puts the planets, + # sun, and moon into motion. + self.rotatePlanets() + + def loadPlanets(self): + # This is the same function that we completed in the previous step + # It is unchanged in this version + + # Create the dummy nodes + self.orbit_root_mercury = render.attachNewNode('orbit_root_mercury') + self.orbit_root_venus = render.attachNewNode('orbit_root_venus') + self.orbit_root_mars = render.attachNewNode('orbit_root_mars') + self.orbit_root_earth = render.attachNewNode('orbit_root_earth') + + # The moon orbits Earth, not the sun + self.orbit_root_moon = ( + self.orbit_root_earth.attachNewNode('orbit_root_moon')) + + ############################################################### + + # Load the sky + self.sky = loader.loadModel("models/solar_sky_sphere") + self.sky_tex = loader.loadTexture("models/stars_1k_tex.jpg") + self.sky.setTexture(self.sky_tex, 1) + self.sky.reparentTo(render) + self.sky.setScale(40) + + # Load the Sun + self.sun = loader.loadModel("models/planet_sphere") + self.sun_tex = loader.loadTexture("models/sun_1k_tex.jpg") + self.sun.setTexture(self.sun_tex, 1) + self.sun.reparentTo(render) + self.sun.setScale(2 * self.sizescale) + + # Load mercury + self.mercury = loader.loadModel("models/planet_sphere") + self.mercury_tex = loader.loadTexture("models/mercury_1k_tex.jpg") + self.mercury.setTexture(self.mercury_tex, 1) + self.mercury.reparentTo(self.orbit_root_mercury) + self.mercury.setPos(0.38 * self.orbitscale, 0, 0) + self.mercury.setScale(0.385 * self.sizescale) + + # Load Venus + self.venus = loader.loadModel("models/planet_sphere") + self.venus_tex = loader.loadTexture("models/venus_1k_tex.jpg") + self.venus.setTexture(self.venus_tex, 1) + self.venus.reparentTo(self.orbit_root_venus) + self.venus.setPos(0.72 * self.orbitscale, 0, 0) + self.venus.setScale(0.923 * self.sizescale) + + # Load Mars + self.mars = loader.loadModel("models/planet_sphere") + self.mars_tex = loader.loadTexture("models/mars_1k_tex.jpg") + self.mars.setTexture(self.mars_tex, 1) + self.mars.reparentTo(self.orbit_root_mars) + self.mars.setPos(1.52 * self.orbitscale, 0, 0) + self.mars.setScale(0.515 * self.sizescale) + + # Load Earth + self.earth = loader.loadModel("models/planet_sphere") + self.earth_tex = loader.loadTexture("models/earth_1k_tex.jpg") + self.earth.setTexture(self.earth_tex, 1) + self.earth.reparentTo(self.orbit_root_earth) + self.earth.setScale(self.sizescale) + self.earth.setPos(self.orbitscale, 0, 0) + + # Offest the moon dummy node so that it is positioned properly + self.orbit_root_moon.setPos(self.orbitscale, 0, 0) + + # Load the moon + self.moon = loader.loadModel("models/planet_sphere") + self.moon_tex = loader.loadTexture("models/moon_1k_tex.jpg") + self.moon.setTexture(self.moon_tex, 1) + self.moon.reparentTo(self.orbit_root_moon) + self.moon.setScale(0.1 * self.sizescale) + self.moon.setPos(0.1 * self.orbitscale, 0, 0) + # end loadPlanets() + + def rotatePlanets(self): + # rotatePlanets creates intervals to actually use the hierarchy we created + # to turn the sun, planets, and moon to give a rough representation of the + # solar system. The next lesson will go into more depth on intervals. + self.day_period_sun = self.sun.hprInterval(20, (360, 0, 0)) + + self.orbit_period_mercury = self.orbit_root_mercury.hprInterval( + (0.241 * self.yearscale), (360, 0, 0)) + self.day_period_mercury = self.mercury.hprInterval( + (59 * self.dayscale), (360, 0, 0)) + + self.orbit_period_venus = self.orbit_root_venus.hprInterval( + (0.615 * self.yearscale), (360, 0, 0)) + self.day_period_venus = self.venus.hprInterval( + (243 * self.dayscale), (360, 0, 0)) + + self.orbit_period_earth = self.orbit_root_earth.hprInterval( + self.yearscale, (360, 0, 0)) + self.day_period_earth = self.earth.hprInterval( + self.dayscale, (360, 0, 0)) + + self.orbit_period_moon = self.orbit_root_moon.hprInterval( + (.0749 * self.yearscale), (360, 0, 0)) + self.day_period_moon = self.moon.hprInterval( + (.0749 * self.yearscale), (360, 0, 0)) + + self.orbit_period_mars = self.orbit_root_mars.hprInterval( + (1.881 * self.yearscale), (360, 0, 0)) + self.day_period_mars = self.mars.hprInterval( + (1.03 * self.dayscale), (360, 0, 0)) + + self.day_period_sun.loop() + self.orbit_period_mercury.loop() + self.day_period_mercury.loop() + self.orbit_period_venus.loop() + self.day_period_venus.loop() + self.orbit_period_earth.loop() + self.day_period_earth.loop() + self.orbit_period_moon.loop() + self.day_period_moon.loop() + self.orbit_period_mars.loop() + self.day_period_mars.loop() + # end RotatePlanets() +# end class world + +w = World() +base.run() diff --git a/samples/solar-system/step6_controllable_system.py b/samples/solar-system/step6_controllable_system.py new file mode 100755 index 0000000000..8758fb0509 --- /dev/null +++ b/samples/solar-system/step6_controllable_system.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python + +# Author: Shao Zhang and Phil Saltzman +# Last Updated: 2015-03-13 +# +# This tutorial will cover events and how they can be used in Panda +# Specifically, this lesson will use events to capture keyboard presses and +# mouse clicks to trigger actions in the world. It will also use events +# to count the number of orbits the Earth makes around the sun. This +# tutorial uses the same base code from the solar system tutorial. + +from direct.showbase.ShowBase import ShowBase +base = ShowBase() + +from panda3d.core import TextNode +from direct.interval.IntervalGlobal import * +from direct.gui.DirectGui import * +from direct.showbase.DirectObject import DirectObject +import sys + +# We start this tutorial with the standard class. However, the class is a +# subclass of an object called DirectObject. This gives the class the ability +# to listen for and respond to events. From now on the main class in every +# tutorial will be a subclass of DirectObject + + +class World(DirectObject): + # 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, pos=(0.06, -.06 * (i + 0.5)), fg=(1, 1, 1, 1), + parent=base.a2dTopLeft,align=TextNode.ALeft, scale=.05) + + def __init__(self): + + # The standard camera position and background initialization + base.setBackgroundColor(0, 0, 0) + base.disableMouse() + camera.setPos(0, 0, 45) + camera.setHpr(0, -90, 0) + + # The global variables we used to control the speed and size of objects + self.yearscale = 60 + self.dayscale = self.yearscale / 365.0 * 5 + self.orbitscale = 10 + self.sizescale = 0.6 + + self.loadPlanets() # Load, texture, and position the planets + self.rotatePlanets() # Set up the motion to start them moving + + # The standard title text that's in every tutorial + # Things to note: + #-fg represents the forground color of the text in (r,g,b,a) format + #-pos represents the position of the text on the screen. + # The coordinate system is a x-y based wih 0,0 as the center of the + # screen + #-align sets the alingment of the text relative to the pos argument. + # Default is center align. + #-scale set the scale of the text + #-mayChange argument lets us change the text later in the program. + # By default mayChange is set to 0. Trying to change text when + # mayChange is set to 0 will cause the program to crash. + self.title = OnscreenText( + text="Panda3D: Tutorial 3 - Events", + parent=base.a2dBottomRight, align=TextNode.A_right, + style=1, fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.07) + + self.mouse1EventText = self.genLabelText( + "Mouse Button 1: Toggle entire Solar System [RUNNING]", 1) + self.skeyEventText = self.genLabelText("[S]: Toggle Sun [RUNNING]", 2) + self.ykeyEventText = self.genLabelText("[Y]: Toggle Mercury [RUNNING]", 3) + self.vkeyEventText = self.genLabelText("[V]: Toggle Venus [RUNNING]", 4) + self.ekeyEventText = self.genLabelText("[E]: Toggle Earth [RUNNING]", 5) + self.mkeyEventText = self.genLabelText("[M]: Toggle Mars [RUNNING]", 6) + self.yearCounterText = self.genLabelText("0 Earth years completed", 7) + + self.yearCounter = 0 # year counter for earth years + self.simRunning = True # boolean to keep track of the + # state of the global simulation + + # Events + # Each self.accept statement creates an event handler object that will call + # the specified function when that event occurs. + # Certain events like "mouse1", "a", "b", "c" ... "z", "1", "2", "3"..."0" + # are references to keyboard keys and mouse buttons. You can also define + # your own events to be used within your program. In this tutorial, the + # event "newYear" is not tied to a physical input device, but rather + # is sent by the function that rotates the Earth whenever a revolution + # completes to tell the counter to update + # Exit the program when escape is pressed + self.accept("escape", sys.exit) + self.accept("mouse1", self.handleMouseClick) + self.accept("e", self.handleEarth) + self.accept("s", # message name + self.togglePlanet, # function to call + ["Sun", # arguments to be passed to togglePlanet + # See togglePlanet's definition below for + # an explanation of what they are + self.day_period_sun, + None, + self.skeyEventText]) + # Repeat the structure above for the other planets + self.accept("y", self.togglePlanet, + ["Mercury", self.day_period_mercury, + self.orbit_period_mercury, self.ykeyEventText]) + self.accept("v", self.togglePlanet, + ["Venus", self.day_period_venus, + self.orbit_period_venus, self.vkeyEventText]) + self.accept("m", self.togglePlanet, + ["Mars", self.day_period_mars, + self.orbit_period_mars, self.mkeyEventText]) + self.accept("newYear", self.incYear) + # end __init__ + + def handleMouseClick(self): + # When the mouse is clicked, if the simulation is running pause all the + # planets and sun, otherwise resume it + if self.simRunning: + print "Pausing Simulation" + # changing the text to reflect the change from "RUNNING" to + # "PAUSED" + self.mouse1EventText.setText( + "Mouse Button 1: Toggle entire Solar System [PAUSED]") + # For each planet, check if it is moving and if so, pause it + # Sun + if self.day_period_sun.isPlaying(): + self.togglePlanet("Sun", self.day_period_sun, None, + self.skeyEventText) + if self.day_period_mercury.isPlaying(): + self.togglePlanet("Mercury", self.day_period_mercury, + self.orbit_period_mercury, self.ykeyEventText) + # Venus + if self.day_period_venus.isPlaying(): + self.togglePlanet("Venus", self.day_period_venus, + self.orbit_period_venus, self.vkeyEventText) + #Earth and moon + if self.day_period_earth.isPlaying(): + self.togglePlanet("Earth", self.day_period_earth, + self.orbit_period_earth, self.ekeyEventText) + self.togglePlanet("Moon", self.day_period_moon, + self.orbit_period_moon) + # Mars + if self.day_period_mars.isPlaying(): + self.togglePlanet("Mars", self.day_period_mars, + self.orbit_period_mars, self.mkeyEventText) + else: + #"The simulation is paused, so resume it + print "Resuming Simulation" + self.mouse1EventText.setText( + "Mouse Button 1: Toggle entire Solar System [RUNNING]") + # the not operator does the reverse of the previous code + if not self.day_period_sun.isPlaying(): + self.togglePlanet("Sun", self.day_period_sun, None, + self.skeyEventText) + if not self.day_period_mercury.isPlaying(): + self.togglePlanet("Mercury", self.day_period_mercury, + self.orbit_period_mercury, self.ykeyEventText) + if not self.day_period_venus.isPlaying(): + self.togglePlanet("Venus", self.day_period_venus, + self.orbit_period_venus, self.vkeyEventText) + if not self.day_period_earth.isPlaying(): + self.togglePlanet("Earth", self.day_period_earth, + self.orbit_period_earth, self.ekeyEventText) + self.togglePlanet("Moon", self.day_period_moon, + self.orbit_period_moon) + if not self.day_period_mars.isPlaying(): + self.togglePlanet("Mars", self.day_period_mars, + self.orbit_period_mars, self.mkeyEventText) + # toggle self.simRunning + self.simRunning = not self.simRunning + # end handleMouseClick + + # The togglePlanet function will toggle the intervals that are given to it + # between paused and playing. + # Planet is the name to print + # Day is the interval that spins the planet + # Orbit is the interval that moves around the orbit + # Text is the OnscreenText object that needs to be updated + def togglePlanet(self, planet, day, orbit=None, text=None): + if day.isPlaying(): + print "Pausing " + planet + state = " [PAUSED]" + else: + print "Resuming " + planet + state = " [RUNNING]" + + # Update the onscreen text if it is given as an argument + if text: + old = text.getText() + # strip out the last segment of text after the last white space + # and append the string stored in 'state' + text.setText(old[0:old.rfind(' ')] + state) + + # toggle the day interval + self.toggleInterval(day) + # if there is an orbit interval, toggle it + if orbit: + self.toggleInterval(orbit) + # end togglePlanet + + # toggleInterval does exactly as its name implies + # It takes an interval as an argument. Then it checks to see if it is playing. + # If it is, it pauses it, otherwise it resumes it. + def toggleInterval(self, interval): + if interval.isPlaying(): + interval.pause() + else: + interval.resume() + # end toggleInterval + + # Earth needs a special buffer function because the moon is tied to it + # When the "e" key is pressed, togglePlanet is called on both the earth and + # the moon. + def handleEarth(self): + self.togglePlanet("Earth", self.day_period_earth, + self.orbit_period_earth, self.ekeyEventText) + self.togglePlanet("Moon", self.day_period_moon, + self.orbit_period_moon) + # end handleEarth + + # the function incYear increments the variable yearCounter and then updates + # the OnscreenText 'yearCounterText' every time the message "newYear" is + # sent + def incYear(self): + self.yearCounter += 1 + self.yearCounterText.setText( + str(self.yearCounter) + " Earth years completed") + # end incYear + + +######################################################################### +# Except for the one commented line below, this is all as it was before # +# Scroll down to the next comment to see an example of sending messages # +######################################################################### + + def loadPlanets(self): + self.orbit_root_mercury = render.attachNewNode('orbit_root_mercury') + self.orbit_root_venus = render.attachNewNode('orbit_root_venus') + self.orbit_root_mars = render.attachNewNode('orbit_root_mars') + self.orbit_root_earth = render.attachNewNode('orbit_root_earth') + + self.orbit_root_moon = ( + self.orbit_root_earth.attachNewNode('orbit_root_moon')) + + self.sky = loader.loadModel("models/solar_sky_sphere") + + self.sky_tex = loader.loadTexture("models/stars_1k_tex.jpg") + self.sky.setTexture(self.sky_tex, 1) + self.sky.reparentTo(render) + self.sky.setScale(40) + + self.sun = loader.loadModel("models/planet_sphere") + self.sun_tex = loader.loadTexture("models/sun_1k_tex.jpg") + self.sun.setTexture(self.sun_tex, 1) + self.sun.reparentTo(render) + self.sun.setScale(2 * self.sizescale) + + self.mercury = loader.loadModel("models/planet_sphere") + self.mercury_tex = loader.loadTexture("models/mercury_1k_tex.jpg") + self.mercury.setTexture(self.mercury_tex, 1) + self.mercury.reparentTo(self.orbit_root_mercury) + self.mercury.setPos(0.38 * self.orbitscale, 0, 0) + self.mercury.setScale(0.385 * self.sizescale) + + self.venus = loader.loadModel("models/planet_sphere") + self.venus_tex = loader.loadTexture("models/venus_1k_tex.jpg") + self.venus.setTexture(self.venus_tex, 1) + self.venus.reparentTo(self.orbit_root_venus) + self.venus.setPos(0.72 * self.orbitscale, 0, 0) + self.venus.setScale(0.923 * self.sizescale) + + self.mars = loader.loadModel("models/planet_sphere") + self.mars_tex = loader.loadTexture("models/mars_1k_tex.jpg") + self.mars.setTexture(self.mars_tex, 1) + self.mars.reparentTo(self.orbit_root_mars) + self.mars.setPos(1.52 * self.orbitscale, 0, 0) + self.mars.setScale(0.515 * self.sizescale) + + self.earth = loader.loadModel("models/planet_sphere") + self.earth_tex = loader.loadTexture("models/earth_1k_tex.jpg") + self.earth.setTexture(self.earth_tex, 1) + self.earth.reparentTo(self.orbit_root_earth) + self.earth.setScale(self.sizescale) + self.earth.setPos(self.orbitscale, 0, 0) + + self.orbit_root_moon.setPos(self.orbitscale, 0, 0) + + self.moon = loader.loadModel("models/planet_sphere") + self.moon_tex = loader.loadTexture("models/moon_1k_tex.jpg") + self.moon.setTexture(self.moon_tex, 1) + self.moon.reparentTo(self.orbit_root_moon) + self.moon.setScale(0.1 * self.sizescale) + self.moon.setPos(0.1 * self.orbitscale, 0, 0) + + def rotatePlanets(self): + self.day_period_sun = self.sun.hprInterval(20, (360, 0, 0)) + + self.orbit_period_mercury = self.orbit_root_mercury.hprInterval( + (0.241 * self.yearscale), (360, 0, 0)) + self.day_period_mercury = self.mercury.hprInterval( + (59 * self.dayscale), (360, 0, 0)) + + self.orbit_period_venus = self.orbit_root_venus.hprInterval( + (0.615 * self.yearscale), (360, 0, 0)) + self.day_period_venus = self.venus.hprInterval( + (243 * self.dayscale), (360, 0, 0)) + + # Here the earth interval has been changed to rotate like the rest of the + # planets and send a message before it starts turning again. To send a + # message, the call is simply messenger.send("message"). The "newYear" + # message is picked up by the accept("newYear"...) statement earlier, and + # calls the incYear function as a result + self.orbit_period_earth = Sequence( + self.orbit_root_earth.hprInterval( + self.yearscale, (360, 0, 0)), + Func(messenger.send, "newYear")) + self.day_period_earth = self.earth.hprInterval( + self.dayscale, (360, 0, 0)) + + self.orbit_period_moon = self.orbit_root_moon.hprInterval( + (.0749 * self.yearscale), (360, 0, 0)) + self.day_period_moon = self.moon.hprInterval( + (.0749 * self.yearscale), (360, 0, 0)) + + self.orbit_period_mars = self.orbit_root_mars.hprInterval( + (1.881 * self.yearscale), (360, 0, 0)) + self.day_period_mars = self.mars.hprInterval( + (1.03 * self.dayscale), (360, 0, 0)) + + self.day_period_sun.loop() + self.orbit_period_mercury.loop() + self.day_period_mercury.loop() + self.orbit_period_venus.loop() + self.day_period_venus.loop() + self.orbit_period_earth.loop() + self.day_period_earth.loop() + self.orbit_period_moon.loop() + self.day_period_moon.loop() + self.orbit_period_mars.loop() + self.day_period_mars.loop() + # end RotatePlanets() + +# end class world + +w = World() +base.run()