main.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. #!/usr/bin/env python
  2. # Author: Shao Zhang, Phil Saltzman, and Eddie Canaan
  3. # Last Updated: 2015-03-13
  4. #
  5. # This tutorial will demonstrate some uses for intervals in Panda
  6. # to move objects in your panda world.
  7. # Intervals are tools that change a value of something, like position,
  8. # rotation or anything else, linearly, over a set period of time. They can be
  9. # also be combined to work in sequence or in Parallel
  10. #
  11. # In this lesson, we will simulate a carousel in motion using intervals.
  12. # The carousel will spin using an hprInterval while 4 pandas will represent
  13. # the horses on a traditional carousel. The 4 pandas will rotate with the
  14. # carousel and also move up and down on their poles using a LerpFunc interval.
  15. # Finally there will also be lights on the outer edge of the carousel that
  16. # will turn on and off by switching their texture with intervals in Sequence
  17. # and Parallel
  18. from direct.showbase.ShowBase import ShowBase
  19. from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
  20. from panda3d.core import NodePath
  21. from panda3d.core import LVector3
  22. from direct.interval.IntervalGlobal import * # Needed to use Intervals
  23. from direct.gui.DirectGui import *
  24. # Importing math constants and functions
  25. from math import pi, sin
  26. class CarouselDemo(ShowBase):
  27. def __init__(self):
  28. # Initialize the ShowBase class from which we inherit, which will
  29. # create a window and set up everything we need for rendering into it.
  30. ShowBase.__init__(self)
  31. # This creates the on screen title that is in every tutorial
  32. self.title = OnscreenText(text="Panda3D: Tutorial - Carousel",
  33. parent=base.a2dBottomCenter,
  34. fg=(1, 1, 1, 1), shadow=(0, 0, 0, .5),
  35. pos=(0, .1), scale=.1)
  36. base.disableMouse() # Allow manual positioning of the camera
  37. camera.setPosHpr(0, -8, 2.5, 0, -9, 0) # Set the cameras' position
  38. # and orientation
  39. self.loadModels() # Load and position our models
  40. self.setupLights() # Add some basic lighting
  41. self.startCarousel() # Create the needed intervals and put the
  42. # carousel into motion
  43. def loadModels(self):
  44. # Load the carousel base
  45. self.carousel = loader.loadModel("models/carousel_base")
  46. self.carousel.reparentTo(render) # Attach it to render
  47. # Load the modeled lights that are on the outer rim of the carousel
  48. # (not Panda lights)
  49. # There are 2 groups of lights. At any given time, one group will have
  50. # the "on" texture and the other will have the "off" texture.
  51. self.lights1 = loader.loadModel("models/carousel_lights")
  52. self.lights1.reparentTo(self.carousel)
  53. # Load the 2nd set of lights
  54. self.lights2 = loader.loadModel("models/carousel_lights")
  55. # We need to rotate the 2nd so it doesn't overlap with the 1st set.
  56. self.lights2.setH(36)
  57. self.lights2.reparentTo(self.carousel)
  58. # Load the textures for the lights. One texture is for the "on" state,
  59. # the other is for the "off" state.
  60. self.lightOffTex = loader.loadTexture("models/carousel_lights_off.jpg")
  61. self.lightOnTex = loader.loadTexture("models/carousel_lights_on.jpg")
  62. # Create an list (self.pandas) with filled with 4 dummy nodes attached
  63. # to the carousel.
  64. # This uses a python concept called "Array Comprehensions." Check the
  65. # Python manual for more information on how they work
  66. self.pandas = [self.carousel.attachNewNode("panda" + str(i))
  67. for i in range(4)]
  68. self.models = [loader.loadModel("models/carousel_panda")
  69. for i in range(4)]
  70. self.moves = [0] * 4
  71. for i in range(4):
  72. # set the position and orientation of the ith panda node we just created
  73. # The Z value of the position will be the base height of the pandas.
  74. # The headings are multiplied by i to put each panda in its own position
  75. # around the carousel
  76. self.pandas[i].setPosHpr(0, 0, 1.3, i * 90, 0, 0)
  77. # Load the actual panda model, and parent it to its dummy node
  78. self.models[i].reparentTo(self.pandas[i])
  79. # Set the distance from the center. This distance is based on the way the
  80. # carousel was modeled in Maya
  81. self.models[i].setY(.85)
  82. # Load the environment (Sky sphere and ground plane)
  83. self.env = loader.loadModel("models/env")
  84. self.env.reparentTo(render)
  85. self.env.setScale(7)
  86. # Panda Lighting
  87. def setupLights(self):
  88. # Create some lights and add them to the scene. By setting the lights on
  89. # render they affect the entire scene
  90. # Check out the lighting tutorial for more information on lights
  91. ambientLight = AmbientLight("ambientLight")
  92. ambientLight.setColor((.4, .4, .35, 1))
  93. directionalLight = DirectionalLight("directionalLight")
  94. directionalLight.setDirection(LVector3(0, 8, -2.5))
  95. directionalLight.setColor((0.9, 0.8, 0.9, 1))
  96. render.setLight(render.attachNewNode(directionalLight))
  97. render.setLight(render.attachNewNode(ambientLight))
  98. # Explicitly set the environment to not be lit
  99. self.env.setLightOff()
  100. def startCarousel(self):
  101. # Here's where we actually create the intervals to move the carousel
  102. # The first type of interval we use is one created directly from a NodePath
  103. # This interval tells the NodePath to vary its orientation (hpr) from its
  104. # current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from
  105. # NodePaths also exist for position, scale, color, and shear
  106. self.carouselSpin = self.carousel.hprInterval(20, LVector3(360, 0, 0))
  107. # Once an interval is created, we need to tell it to actually move.
  108. # start() will cause an interval to play once. loop() will tell an interval
  109. # to repeat once it finished. To keep the carousel turning, we use
  110. # loop()
  111. self.carouselSpin.loop()
  112. # The next type of interval we use is called a LerpFunc interval. It is
  113. # called that becuase it linearly interpolates (aka Lerp) values passed to
  114. # a function over a given amount of time.
  115. # In this specific case, horses on a carousel don't move contantly up,
  116. # suddenly stop, and then contantly move down again. Instead, they start
  117. # slowly, get fast in the middle, and slow down at the top. This motion is
  118. # close to a sine wave. This LerpFunc calls the function oscillatePanda
  119. # (which we will create below), which changes the height of the panda based
  120. # on the sin of the value passed in. In this way we achieve non-linear
  121. # motion by linearly changing the input to a function
  122. for i in range(4):
  123. self.moves[i] = LerpFunc(
  124. self.oscillatePanda, # function to call
  125. duration=3, # 3 second duration
  126. fromData=0, # starting value (in radians)
  127. toData=2 * pi, # ending value (2pi radians = 360 degrees)
  128. # Additional information to pass to
  129. # self.oscialtePanda
  130. extraArgs=[self.models[i], pi * (i % 2)]
  131. )
  132. # again, we want these to play continuously so we start them with
  133. # loop()
  134. self.moves[i].loop()
  135. # Finally, we combine Sequence, Parallel, Func, and Wait intervals,
  136. # to schedule texture swapping on the lights to simulate the lights turning
  137. # on and off.
  138. # Sequence intervals play other intervals in a sequence. In other words,
  139. # it waits for the current interval to finish before playing the next
  140. # one.
  141. # Parallel intervals play a group of intervals at the same time
  142. # Wait intervals simply do nothing for a given amount of time
  143. # Func intervals simply make a single function call. This is helpful because
  144. # it allows us to schedule functions to be called in a larger sequence. They
  145. # take virtually no time so they don't cause a Sequence to wait.
  146. self.lightBlink = Sequence(
  147. # For the first step in our sequence we will set the on texture on one
  148. # light and set the off texture on the other light at the same time
  149. Parallel(
  150. Func(self.lights1.setTexture, self.lightOnTex, 1),
  151. Func(self.lights2.setTexture, self.lightOffTex, 1)),
  152. Wait(1), # Then we will wait 1 second
  153. # Then we will switch the textures at the same time
  154. Parallel(
  155. Func(self.lights1.setTexture, self.lightOffTex, 1),
  156. Func(self.lights2.setTexture, self.lightOnTex, 1)),
  157. Wait(1) # Then we will wait another second
  158. )
  159. self.lightBlink.loop() # Loop this sequence continuously
  160. def oscillatePanda(self, rad, panda, offset):
  161. # This is the oscillation function mentioned earlier. It takes in a
  162. # degree value, a NodePath to set the height on, and an offset. The
  163. # offset is there so that the different pandas can move opposite to
  164. # each other. The .2 is the amplitude, so the height of the panda will
  165. # vary from -.2 to .2
  166. panda.setZ(sin(rad + offset) * .2)
  167. demo = CarouselDemo()
  168. demo.run()