| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- #!/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()
|