historical/toontown-classic.git/panda/samples/carousel/main.py
2024-01-16 11:20:27 -06:00

196 lines
9.3 KiB
Python

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