Panda3D Manual

Panda3D Manual: Main Page

Table of Contents

  1. Introduction to Panda
    1. Installing Panda in Windows
    2. Installing Panda in Linux
    3. General Preparation
    4. How to use a Python Editor
    5. Using Command Prompt to Run your Program
    6. A Panda "Hello World" using Python
      1. Starting Panda3D
      2. Loading the Grassy Scenery
      3. Controlling the Camera
      4. Loading and Animating the Panda Model
      5. Using Intervals to move the Panda
    7. A Panda "Hello World" using C++
      1. Starting Panda3D (C++)
      2. Loading the Grassy Scenery (C++)
      3. Controlling the Camera (C++)
      4. Loading and Animating the Panda Model (C++)
      5. Using Intervals to move the Panda (C++)
    8. User Contributed Tutorials and Examples
    9. Video Tutorials
      1. User Contributed Video Tutorials
      2. Disney Video Lectures
  2. Programming with Panda
    1. The Scene Graph
      1. Scene Graph Manipulations
      2. Common State Changes
      3. Manipulating a Piece of a Model
      4. Searching the Scene Graph
      5. Instancing
    2. The Configuration File
      1. Configuring Panda
      2. List of All Config Variables
      3. Accessing Config Vars in a Program
    3. Models and Actors
      1. Loading Models
      2. Loading Actors and Animations
      3. Actor Animations
      4. Multi-Part Actors
      5. Attaching an Object to a Joint
      6. Controlling a Joint Procedurally
    4. Render Attributes
      1. List of All Attributes
      2. Lighting
      3. Materials
      4. Depth Test and Depth Write
      5. Fog
      6. Alpha Testing
      7. Color Write Masks
      8. Antialiasing
      9. Clip Planes
      10. Tinting and Recoloring
      11. Backface Culling and Frontface Culling
      12. Light Ramps
      13. Auxiliary Bitplane Control
      14. Stencil Test/Write Attribute
    5. Texturing
      1. Simple Texturing
      2. Choosing a Texture Size
      3. Texture Wrap Modes
      4. Texture Filter Types
      5. Simple Texture Replacement
      6. Multitexture Introduction
      7. Texture Modes
      8. Texture Order
      9. Texture Combine Modes
      10. Texture Transforms
      11. Multiple Texture Coordinate Sets
      12. Automatic Texture Coordinates
      13. Projected Textures
      14. Simple Environment Mapping
      15. 3-D Textures
      16. Cube Maps
      17. Environment Mapping with Cube Maps
      18. Automatic Texture Animation
      19. Playing MPG and AVI files
      20. Transparency and Blending
      21. Texture Management
      22. Texture Compression
    6. Pixel and Vertex Shaders
      1. Shader Basics
      2. List of Possible Shader Inputs
      3. Shaders and Coordinate Spaces
      4. Known Shader Bugs and Limitations
      5. The Shader Generator
      6. Cg Shader Tutorial
        1. Cg Tutorial Part 1
        2. Cg Tutorial Part 2
    7. Camera Control
      1. The Default Camera Driver
      2. Lenses and Field of View
      3. Orthographic Lenses
    8. Sound
      1. Loading and Playing Sounds and Music
      2. Manipulating Sounds
      3. DSP Effects
      4. 3DAudio
      5. Multi-Channel
    9. Intervals
      1. Lerp Intervals
      2. Function Intervals
      3. Actor Intervals
      4. Sound Intervals
      5. Motion Path and Particle Intervals
      6. Sequences and Parallels
      7. Position, Rotation and Scale Intervals
      8. Projectile Intervals
    10. Tasks and Event Handling
      1. Tasks
      2. Event Handlers
      3. Main Loop
    11. Text and Image Rendering
      1. Text Fonts
      2. Text Node
      3. OnscreenText
      4. OnscreenImage
      5. Embedded Text Properties
    12. DirectGUI
      1. DirectButton
      2. DirectCheckButton
      3. DirectRadioButton
      4. DirectDialog
      5. DirectEntry
      6. DirectFrame
      7. DirectLabel
      8. DirectOptionMenu
      9. DirectScrolledList
      10. DirectWaitBar
      11. DirectSlider
      12. DirectScrollBar
      13. DirectScrolledFrame
    13. Render Effects
      1. Compass Effects
      2. Billboard Effects
    14. Finite State Machines
      1. FSM Introduction
      2. Simple FSM Usage
      3. FSM with input
      4. Advanced FSM Tidbits
    15. Terrain
      1. The Heightfield Tesselator
      2. Geometrical MipMapping
    16. Advanced operations with Panda's internal structures
      1. How Panda3D Stores Vertices and Geometry
        1. GeomVertexData
        2. GeomVertexFormat
        3. GeomPrimitive
        4. Geom
        5. GeomNode
        6. BoundingVolume
      2. Procedurally Generating 3D Models
        1. Defining your own GeomVertexFormat
        2. Pre-defined vertex formats
        3. Creating and filling a GeomVertexData
        4. Creating the GeomPrimitive objects
        5. Putting your new geometry in the scene graph
      3. Other Vertex and Model Manipulation
        1. Reading existing geometry data
        2. Modifying existing geometry data
        3. More about GeomVertexReader, GeomVertexWriter, and GeomVertexRewriter
        4. Creating New Textures from Scratch
        5. Writing 3D Models out to Disk
    17. Render-to-Texture and Image Postprocessing
      1. Common Image Filters
      2. Generalized Image Filters
      3. Dynamic Cube Maps
      4. Low-Level Render to Texture
    18. Panda Rendering Process
      1. Introducing Graphics Classes
      2. The Graphics Pipe
      3. Creating Windows and Buffers
      4. Display Regions
      5. Clearing Display Regions
      6. The 2D Display Region
          Deprecated pages
        1. The Graphics Engine
        2. The GraphicsOutput class
        3. Graphics Buffers and Windows
        4. Multi-Pass Rendering
        5. How to Control Render Order
    19. Panda Utility Functions
    20. Particle Effects
      1. Using the Particle Panel
      2. Loading Particle Systems
      3. Particle Effect Basic Parameters
      4. Particle Factories
      5. Particle Emitters
      6. Particle Renderers
    21. Collision Detection
      1. Collision Solids
      2. Collision Handlers
      3. Collision Entries
      4. Collision Traversers
      5. Collision Bitmasks
      6. Rapidly-Moving Objects
      7. Pusher Example
      8. Event Example
      9. Bitmask Example
      10. Clicking on 3D Objects
    22. Hardware support
      1. Keyboard Support
      2. Mouse Support
      3. Joystick Support
      4. VR Helmets and Trackers
    23. Math Engine
      1. Matrix Representation
    24. Physics
      1. Panda's Built-in Physics Engine
        1. Enabling physics on a node
        2. Applying physics to a node
        3. Types of forces
        4. Notes and caveats
      2. Using ODE with Panda3D
        1. Worlds, Bodies and Masses
        2. Simulating the Physics World
        3. Attaching Bodies using Joints
        4. Collision Detection with ODE
    25. Motion Paths
    26. Timing
      1. The Global Clock
    27. Networking
      1. Datagram Protocol
        1. Client-Server Connection
        2. Transmitting Data
      1. Downloading a File
    28. Multifiles
      1. Creating Multifiles
      2. Patching
      3. Loading resources from nonstandard sources
    29. File Reading
    30. Threading
  3. Sample Programs in the Distribution
    1. Sample Programs: Asteroids
    2. Sample Programs: Ball in Maze
    3. Sample Programs: Boxing Robots
    4. Sample Programs: Carousel
    5. Sample Programs: Cartoon Shader
    6. Sample Programs: Chessboard
    7. Sample Programs: Disco Lights
    8. Sample Programs: Fireflies
    9. Sample Programs: Fractal Plants
    10. Sample Programs: Glow Filter
    11. Sample Programs: Infinite Tunnel
    12. Sample Programs: Looking and Gripping
    13. Sample Programs: Media Player
    14. Sample Programs: Motion Trails
    15. Sample Programs: Music Box
    16. Sample Programs: Normal Mapping
    17. Sample Programs: Particles
    18. Sample Programs: Procedural Cube
    19. Sample Programs: Roaming Ralph
    20. Sample Programs: Shadows
    21. Sample Programs: Solar System
    22. Sample Programs: Teapot on TV
    23. Sample Programs: Texture Swapping
  4. DirectStart (Content of)
  5. Debugging
    1. Log Messages
    2. The Python Debugger
    3. Running Panda under the C++ Debugger
  6. Performance Tuning
    1. Basic Performance Diagnostics
    2. Measuring Performance with PStats
    3. The Rigid Body Combiner
    4. Performance Issue: Too Many Meshes
    5. Performance Issue: Too Many State Changes
    6. Performance Issue: Too Many Text Updates
    7. Performance Issue: Too Many Shader Instructions
    8. Performance Issue: Excessive Fill
    9. Performance Issue: Memory Full
    10. Performance Issue: Python Calculation
    11. Performance Issue: Failure to Garbage Collect
    12. Performance Issue: Collision System Misuse
    13. Performance Issue: Motherboard Integrated Video
    14. Performance Issue: Too Many Polygons
    15. Performance Issue: Miscellaneous
  7. Using C++
    1. How to compile a C++ Panda3D program
      1. How to compile a C++ Panda3D program on Linux
    2. The Window Framework
    3. Texturing in C++
    4. Reference Counting
  8. Panda Tools
    1. The Scene Graph Browser
      1. Enhanced Mouse Navigation
    2. Interrogate
    3. Python Editors
      1. SPE
    4. Pipeline Tips
    5. Model Export
      1. Converting from 3D Studio Max
      2. Converting from Maya
      3. Converting from Blender
      4. Converting from SoftImage
      5. Converting from Milkshape 3D
      6. Converting from GMax
      7. Converting from other Formats
      8. Converting Egg to Bam
      9. Parsing and Generating Egg Files
      10. Egg Syntax
    6. Previewing 3D Models in Pview
    7. Building an installer using packpanda
  9. Building Panda from Source
    1. Troubleshooting ppremake on Windows
    2. Troubleshooting ppremake on Linux
    3. Troubleshooting makepanda on Windows
    4. Troubleshooting makepanda on Linux
    5. Tutorial: Compiling the Panda 3D Source on Windows
  • List of Panda Executables
  • Getting Started on OSX
  • More Panda Resources
  • Project Hosting
  • The IRC Channel
  • FAQ
  • Class Reference


Panda3D Manual: Introduction to Panda
<<prev top next>>

Panda3D Basics

Panda3D is a 3D engine: a library of subroutines for 3D rendering and game development. The library is C++ with a set of Python bindings. Game development with Panda3D usually consists of writing a Python or C++ program that controls the Panda3D library.

Panda was created for commercial game development, and its primary users are still commercial game developers. Because of this, Panda3D needs to emphasize four areas: power, speed, completeness, and error tolerance. Everyone knows what power and speed are. But completeness and error tolerance deserve some extra commentary.

Completeness means that Panda3D contains tons of unexciting but essential tools: scene graph browsing, performance monitoring, animation optimizers, and so forth. These things may not be sexy, and as a result, open-source engines often don't have them. But when you're serious about getting work done, and not just playing, these tools need to be there.

Error tolerance is about the fact that all game developers create bugs. When you do, you want your engine to give you a clear error message and help you find the mistake. Too many engines will just crash if you pass the wrong value to a function. Panda3D almost never crashes, and much code is dedicated to the problem of tracking and isolating errors.

Finally, to come back to power and speed: the best way to gauge Panda3D's capabilities is to take a look at the Sample Programs. These are short programs that demonstrate a sampling of Panda3D's capabilities. The screenshots have frame-rates in the upper-right corner, taken on a Radeon X700.

Panda3D was developed by Disney for their massively multiplayer online game, Toontown. It was released as free software in 2002. Panda3D is now developed jointly by Disney and Carnegie Mellon University's Entertainment Technology Center.

You can read more about Panda3D's Features.

Panda3D is not a Beginner's Tool or a Toy

To successfully use Panda3D, you must be a skilled programmer. If you do not know what an "API" is, or if you don't know what a "tree" is, you will probably find Panda3D overwhelming. This is no point-and-click game-maker: this is a tool for professionals. While It is important to point that out so you have accurate expectations, it's also relevant to be aware that Panda3d is one of the easiest and most powerful engines you will ever use, and we welcome your participation.

If you are just getting started with programming, we suggest that your best option is to start with a class on programming. Alternately, you could try teaching yourself using a training tool like Alice, also from CMU.

Some people have seen screenshots of childrens' games written in Panda3D, and concluded that Panda3D is graphically limited. Not so. Developers of childrens' games often choose not to use shaders or other advanced graphics, because children often own older hand-me-down computers. But Panda3D supports the full range of what modern engines should: it provides convenient support for normal mapping, gloss mapping, HDR, cartoon shading and inking, bloom, and a number of other things. It also allows you to write your own shaders, making it capable of anything. The one big shortcoming, at this time, is that dynamic shadows are not handled automatically: to use dynamic shadows, you have to write your own shaders. This will change soon.

People sometimes have the mistaken impression that Panda3D is written in python, which would make it very slow. But Panda3D is not written in python - it's written in C++. The python is just for scripting, developers usually write the performance-intensive bits in C++. To see what kind of framerate a small Panda3D program typically gets, take a look at the screenshots of the Sample Programs. Those were taken using a Radeon x700. Of course, only a sample program can run at 400fps like that, but but for a real game, 60fps is quite attainable. One caveat, though: to get that kind of performance, you need to understand 3D cards and 3D performance optimization. It doesn't happen automatically. Panda3D includes profiling tools you need to hit 60fps.

Panda3D's Software License

Since version 1.5.3, Panda3D has been released under the so-called "Modified BSD license," which is a free software license with very few restrictions on usage. In versions 1.5.2 and before, it used a proprietary license which was very similar in intention to the BSD and MIT licenses, though there was some disagreement about the freeness of two of the clauses. The old license can still be accessed here.

Although the engine itself is completely free, it comes with various third-party libraries that are not free software. Some of them (like FMOD) even restrict you from using it in commercial games, unless you have a licensed copy of FMOD. Because of this reason, Panda3D makes it easy to disable or remove these restricted thirdparty libraries, and most of the time it offers an alternative. For example, instead of FMOD it also comes with OpenAL which you can use instead.

You can read Panda3D's License.

Who is Working on Panda3D

There are a number of developers in the commercial and open-source community. Currently, the two most active members of the development community are Disney and the Entertainment Technology Center at Carnegie Mellon. Because both organizations have specific goals, Panda3D must necessarily serve both:

  • Disney's primary interest in Panda3D is commercial. Panda3D is being used in the development of a number of Disney games and amusement-park exhibits. To serve Disney's needs, Panda3D must be a fully-featured engine, capable of all the performance and quality one expects in any 'A-grade' commercial title.
  • The Entertainment Technology Center's primary goal is education. To serve the Entertainment Technology Center's needs, Panda3D must be well-suited for use in student projects. Since students have a unique talent for causing crashes, bulletproof reliability is needed. Since projects only last one semester, the learning curve must be very short, and prototyping must be very rapid.

As it turns out, the two sets of goals are complementary. The rapid development and high reliability needed by the Entertainment Technology Center are also highly advantageous in a game-development studio, since they lower development time and costs. The good visual quality and full feature set needed by Disney to make a professional-quality game also turn out to be useful in a university setting: with a broad range of features at their disposal, students can explore their creativity more fully than they could with a more limited engine.

The most supported language is Python. Though you can use C++ too, the documentation is mostly aimed at Python use.

The Introductory Chapter

This introductory chapter of the manual is designed to walk you through some of the basics of using Panda3D. This chapter is structured as a tutorial, not as a reference work.

<<prev top next>>


Panda3D Manual: Installing Panda in Windows

If you have already installed panda previously, you should uninstall it before installing a new version. Once your machine is clean of all previous versions of panda, you should download the windows installer from the download page. Run the installer, and follow the prompts:

After installing panda, you should run the sample programs to verify that the installation is good. Under Windows, the easiest way to run a sample program is to find it in the Start Menu:

But you can also run a sample program manually, using a command prompt. It is a good idea to do so, since this is how you will run your own programs, at least at first.

Open a command prompt, then change directory to the location where you installed panda. Then, change to the samples subdirectory. There, you will find a large number of samples:

Change directory into one of the sample program directories, then use python to run the sample program in question:

If the test programs don't run, then usually, you need to update your video drivers. If you want to know exactly why a program didn't run, you may need to run it using the command prompt - this will enable you to see the error messages.

What to do if you see the Error Message:
display(error): The application requested harware acceleration, but your OpenGL
display(error): driver, GDI Generic, only supports software rendering.
display(error): You need to install a hardware-accelerated OpenGL driver, or,
display(error): if you actually *want* to use a software renderer, then
display(error): alter the hardware/software configuration in your Config.prc file.
display(error): Window wouldn't open; abandoning window.

This error is fairly self-explanatory: it means your video drivers are inadequate. Obtain better drivers.

What to do if you see the Error Message:
python is not a recognised internal command

This error message means that the command prompt was not able to locate python. Normally, during the panda installation process, the PATH environment variable is configured to indicate the location of python. However, some PCs have unusual security configurations that prevent the installer from modifying the PATH. This can result in the error message shown above.

To solve this problem, it may be necessary to modify the PATH manually. Click on the "My Computer" icon on your desktop and select Properties. Choose the tab "Advanced". Click the Button "Environnment Variables":

If you have administrator access to the PC, select the PATH line in the list under System Variables, otherwise, select the PATH line in the list of personal environment Variables.

Now double click the PATH (or Path) line. a small window opens containing a series of directories separated by semicolons. Add Panda3D's bin and python directories to the end of the PATH.

It may be necessary to log off and log back in to obtain the updated settings.

What to do if you see the Error Message:
Warning: DirectNotify: category 'Interval' already exists

This error is of no consequence. Ignore it.


Panda3D Manual: Installing Panda in Linux

The Installation Process - Linux

The easiest way to install panda is to use the RPM or DEB packages. This is only possible if your version of Linux is one of the provided versions of Linux. If not, you will need to compile from source. If there is an installer available, download and install the RPM or DEB appropriate to your version of Linux.

After installing panda, you should run the sample programs to verify that the installation is good. To do so, you need to change directory to the panda samples directory, select a sample program, change directory to that sample, and run the sample using Python:

$ cd /usr/share/panda3d/samples
$ ls
Asteroids
Ball-in-Maze
Boxing-Robots
Carousel
Cartoon-Shader
Chessboard
Disco-Lights
Fireflies
Fractal-Plants
Glow-Filter
GUI
Infinite-Tunnel
Looking-and-Gripping
Media-Player
Motion-Trails
Music-Box
Normal-Mapping
Particles
Procedural-Cube
Roaming-Ralph
Shadows
Solar-System
Teapot-on-TV
Texture-Swapping
$ cd Boxing-Robots
$ python Tut-Boxing-Robots.py

Using an Unsupported Linux Distribution or an Unsupported Python

Python packages need to be compiled for a particular variant of python. For example, a package that works with python 2.4 will not work with python 2.5. A package that works with 32-bit python will not work with 64-bit python. A package that works with UCS2 python will not work with UCS4 python. And so forth. In short, a python package must be carefully aligned, feature-for-feature, with one particular python interpreter. That package will not work with any other python interpreter.

Fortunately for you, our prepackaged copies of Panda3D are already carefully matched. For example, our Panda3D for Ubuntu Gutsy Gibbon is already perfectly matched to the python interpreter that comes with Ubuntu Gutsy Gibbon. So normally, you don't need to worry about this at all.

If your Linux Distribution is not listed, you will need to build your own copy of Panda3D. The build process will automatically create a copy of Panda3D which perfectly matches your Linux Distribution's python interpreter. This is easy to do, but it does require a time-consuming compile. On the other hand, trying to use an RPM or a DEB from some other Distribution is very unlikely to work, because of this need for an exact feature-for-feature match between the python package (Panda3D) and the python interpreter.

If you are using a copy of python other than the one that came with the Linux Distribution, you have a bigger problem. Panda3D's build-scripts automatcially build Panda3D for the system's native python interpreter, not for some other python interpreter. To get Panda3D to build for some other python interpreter, you will have to edit the build scripts.

What to do if you see the Error Message:

If you see this error:

display(error): The application requested harware acceleration, but your OpenGL
display(error): driver, GDI Generic, only supports software rendering.
display(error): You need to install a hardware-accelerated OpenGL driver, or,
display(error): if you actually *want* to use a software renderer, then
display(error): alter the hardware/software configuration in your Config.prc file.
display(error): Window wouldn't open; abandoning window.

This error is fairly self-explanatory: it means your video drivers are inadequate. Obtain better drivers.

What to do if you see the Error Message:
ImportError: No module named direct.directbase.DirectStart

This error means it couldn't find the python modules -- please make sure you are running the correct version of Python (probably python 2.5, that depends on the Panda3D version) and that the panda3d.pth is located inside the python site-packages directory.

What to do if you see the Error Message:
Warning: DirectNotify: category 'Interval' already exists

This error is of no consequence. Ignore it.

What to do if you see the Error Message:
ImportError: /usr/lib/panda3d/libpandaexpress.so: undefined symbol: PyUnicodeUCS4_AsWideChar

This could mean that your version of python is compiled with the flag Py_UNICODE_SIZE set to 2. Please find a python version compiled with Py_UNICODE_SIZE set to 4 (which is usually the default). See this forum topic for a more detailed explanation about this problem.


Panda3D Manual: General Preparation

Learning Python

Since Panda3D is a library, and not a point-n-click game maker, it is needed to learn Python or C++ before you will be able to use it. Since this engine's main goal is to support Python, it would be a good idea to familiarize yourself with Python before continuing.

Python is an interpreted, interactive, object-oriented language comparable to Java or Perl. It is available on several platforms, including UNIX, Windows, OS/2, and Mac. Python also has a large number of modules outside of the standard Python installation, and additional modules can be created in C or C++. Because it is late-binding and requires minimal memory management, it is an ideal language for rapid prototyping.

The Copy of Python that comes with Panda

It is not necessary to install python, because the windows installer for Panda3D includes a copy. This is a completely normal copy of python, identical to what you would have if you installed python using the standard python installer. Panda's built-in copy of python is automatically added to the PATH environment variable. This enables you to type "python" at the command prompt, and it will run the python that comes with panda.

What if I already have a copy of Python?

If you already have a copy of python, and you wish to use that instead of the one provided with panda, it is easy to do so. Simply create a "panda.pth" file inside your copy of python, containing the path of the panda directory. This will enable your copy of python to find the panda libraries.

For this to work, the version of python that you use must match the version of python included with panda. The panda libraries are compiled for that particular version, and will not work with any other.

Of course, if you do use your own copy of python, you may wish to delete panda's copy of python, or at least, remove it from the PATH environment variable. Otherwise, you will have two copies of python, which can lead to confusion.

Python Programming Resources

There are a lot of other resources available for programming in Python. Here is a list of some of the best:

Links from the official python website:

  • Official Website - http://www.python.org
  • Current Python Documentation
  • Python Documentation This is the version still being used in Panda3D.
  • Python Tutorial. Written by Guido Van Rossum, the author of Python.

Here are some other good links for learning python:

  • Programming in Python
    • Byte of Python
    • Python for Non-Programmers
    • Dive Into Python
    • Python 101
  • Miscellaneous python documentation
    • Python Examples and Sample Code
    • The Standard Python Library
    • Python Book List

Learning C++

It is possible to write Panda3D programs using C++. However, since most of the documentation uses Python, it may be better to learn Panda3D using python first, and then switch to C++ later. If you do switch, the function calls are the same.

C++ is an object-oriented high-level multi-purpose language. It is actually a copy of the C programming language, but object-oriented, with more functions. Here are a few links to C++ tutorials that might be useful for you:

  • http://www.cplusplus.com/doc/tutorial/
  • http://www.intap.net/~drw/cpp/

The most recommended compiler on windows is the Microsoft Visual C++ 2005 compiler, on UNIX it is the GNU G++ compiler. For information about compiling your program with the G++ compiler, see this page.


Panda3D Manual: How to use a Python Editor

Creating a Program: General Outline

Panda3D is a library of subroutines; not an application. This means that unlike all the other programs on your computer, you won’t find any program icons or shortcuts to start it. Instead, you must use a programmer's editor to create your program. The basic steps are:

  • Create an empty directory for your program.
  • Obtain a programmer's editor.
  • Start the programmer's editor.
  • Type in a program into the editor.
  • Save the program in your program's directory.
  • Start a command prompt.
  • Change directory to your program's directory.
  • Run the program using 'python.exe'

These steps will be explained in more detail below. The detailed descriptions are for windows users and cover only the PyPE editor. Linux users, you're on your own.

Possible programmer's editors are:

Windows:

  • PyPE
  • PSPad
  • Geany
  • Crimson Editor
  • Notepad++
  • Stani's Python Editor
  • Eclipse in combination with PyDev

Linux:

  • Geany
  • Stani's Python Editor
  • Eclipse in combination with PyDev
  • Eric Python IDE
  • Kate (KDE only)
  • Scribes or gedit in combination with IPython

The next part of this page will only explain usage with PyPE.

Creating a Directory for your Program

So, you now need to create a place where you can save all your scripts. Panda3D can run a script from anywhere on your computer, but it looks for models and other assets in the folder that it is run from. If those assets aren’t present, then you’ll get an error message when you try to run it. So, I believe that the easiest thing for the beginner to do, is to make a new folder in the main Panda3D directory itself (which already contains all the models and other assets that you’ll need).

To do this, click your computers ‘Start’ button, then ‘My Computer’.

Then double-click ‘Local Disk (C)’ (if the contents of this drive are hidden, just click ‘Show the contents of this folder’). Now find the Panda3D folder and double-click on it to open it.

On the top menu bar click ‘File > New > Folder’. This will create a new folder in the Panda3D directory, backspace out the name and type a new name for it (I called mine mystuff).

Obtaining a Programmer's Editor

One of python’s strong points, is that you don’t need a compiler to write your scripts, however, you do need a good editor. Do not try to use Windows Notepad or Wordpad: they are not designed for this. Instead, you will need a programmer's editor. A well-known python editor is PyPE (which is completely free and makes writing python scripts a whole lot easier). You can download it from this location:

http://sourceforge.net/projects/pype/

If you're using Windows, download the PyPE-2.8-win-ansi.zip (as of writing this was the latest version). You don't need to install this program, you just download and unzip it somewhere, then open the unzipped folder and double-click on the PyPE icon to run it (or right-click on the icon and send it to the desktop as a shortcut, then simply run it by double-clicking the icon on your desktop).

Typing your Program into the Editor

Open PyPE, then click ‘File > New’ on the top menu bar to open a new work environment. Now type the following code (or just copy and paste it):

import direct.directbase.DirectStart
run()

Notice that PyPE automatically highlights certain words and numbers the lines for you. This is a very nice feature which makes finding errors much easier.

You’ve just written your first Panda3D script. It’s not much, but those few lines of code tell Panda3D to start. But before you can run this script, you must save it. So click ‘File > Save As’ on the top menu bar and a new window should open.

At the top of this new window there is a ‘Save in:’ text box, which is pointing to ‘PyPE-2.8-win-ansi’, you DON’T want to save your script there, so click the little down arrow beside the text box, then scroll down the list and click ‘Local Disk (C)’, then double-click on the Panda3D folder to open it. Find the ‘mystuff’ folder that we created earlier and double-click on it to open it.

Next, type a name for your script in the ‘File name:’ text box. I called mine myscript.py (make sure you put .py on the end of the name). Then click the ‘Save’ button.

You can now close PyPE.


Panda3D Manual: Using Command Prompt to Run your Program

Using Command Prompt to Run your Program

You can run your script by using your computer’s ‘Command Prompt’. You access this by clicking ‘Start > All Programs > Accessories > Command Prompt’. When it opens, it should look something like this:

At the moment it’s pointing to its default directory, which in my case is ‘Documents and Settings’ (it doesn’t matter if yours is different). We need to change the directory to the one where we saved our script. To do this, we type cd. This stands for ‘change directory’. So type the following text behind the > symbol.

cd C:\Panda3D-1.4.0\mystuff\

Instead of version 1.4.0, you should type the version number of Panda that you have downloaded and installed. Please note that this folder name is case sensitive and must match exactly (other than the version number, of course). Then press the ‘Enter’ key on your keyboard. You should now have the following on the Command Prompt:

This means that it’s now pointing to the right directory. To run your script, and start Panda3D, type the following text behind the > symbol:
python myscript.py

Press the ‘Enter’ key on your keyboard. If all is well, Panda3D will start and you should see the main rendering window appear.

This is a empty program, it won't do anything. The next step is to add additional commands to the program, as described in one of the following tutorials.


Panda3D Manual: A Panda "Hello World" using Python

This tutorial is called "A Panda Hello World." It is a typical example of a simple Panda3D program. Walking through this tutorial will enable you to obtain some limited familiarity with the Panda3D API, without having to learn the entire thing.

The program that we are going to create will load up a small scene containing some grass and a panda. The panda will be animated to walk back and forth over the grass.

Note: This tutorial is intended for Python usage only. For the C++ equivalent of this tutorial, click here.


Panda3D Manual: Starting Panda3D

To start Panda3D, create a text file and save it with the .py extension. PYPE, SPE and IDLE are Python-specific text-editors, but any text editor will work. Enter the following text into your Python file:

import direct.directbase.DirectStart
run()

DirectStart loads most of the other Panda3D modules, and causes the 3D window to appear. The run subroutine contains the Panda3D main loop. It renders a frame, handles the background tasks, and then repeats. It does not normally return, so it only needs to be called once and must be the last line in your script. In this particular example, there will be nothing to render, so you should expect a window containing an empty grey area.

To run your program, type this at the command prompt:

python filename.py

If Panda3D has been installed properly, a gray window titled Panda appear. There is nothing we can do with this window, but that will change shortly.


Panda3D Manual: Loading the Grassy Scenery

With Panda3D running properly, it is now possible to load some grassy scenery. Update your code as follows:

import direct.directbase.DirectStart

#Load the first environment model
environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)
#Run the tutorial
run()

The command loader.loadModel() loads the specified file, in this case the environment.egg file in the models folder. The return value is a 'NodePath', effectively a pointer to the model. Note that Panda Filename Syntax uses the forward-slash, even under Windows.

Panda3D contains a data structure called the scene graph. The scene graph is a tree containing all objects that need to be rendered. At the root of the tree is an object named render. Nothing is rendered until it is first installed into the scene graph.

To install the grassy scenery model into the scene graph, we use the method reparentTo. This sets the parent of the model, thereby giving it a place in the scene graph. Doing so makes the model visible.

Finally, we adjust the position and scale of the model. In this particular case, the environment model is a little too large and somewhat offset for our purposes. The setScale and setPos rescale and recenter the model.

Go ahead and run the program. You should see this:

The rock and tree appear to be hovering. The camera is slightly below ground, and backface culling is making the ground invisible to us. If we reposition the camera, the terrain will look better.


Panda3D Manual: Controlling the Camera

By default, Panda3D runs a task that enables you to move the camera using the mouse. The keys to do this are:

  • Left Button: pan left and right
  • Right Button: move forwards and backwards
  • Middle Button: rotate around the origin of the application
  • Right and Middle Buttons: roll the point of view around the view axis

Go ahead and try this camera control system. The problem with this camera control system is that it is sometimes awkward, it is not always easy to get the camera pointed in the direction we want.

Instead, we are going to write a task that controls the camera's position explicitly. A task is nothing but a subroutine that gets called every frame. Update your code as follows:

import direct.directbase.DirectStart
from direct.task import Task
from direct.actor import Actor
import math

#Load the first environment model
environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

#Task to move the camera
def SpinCameraTask(task):
  angledegrees = task.time * 6.0
  angleradians = angledegrees * (math.pi / 180.0)
  base.camera.setPos(20*math.sin(angleradians),-20.0*math.cos(angleradians),3)
  base.camera.setHpr(angledegrees, 0, 0)
  return Task.cont

taskMgr.add(SpinCameraTask, "SpinCameraTask")

run()

The function taskMgr.add tells panda's task manager to call the subroutine SpinCameraTask every frame. This is a subroutine that we have written to control the camera. As long as the subroutine SpinCameraTask returns the constant Task.cont, the task manager will continue to call it every frame.

The subroutine SpinCameraTask() calculates the desired position of the camera based on how much time has elapsed. The camera rotates 6 degrees every second. The first two lines compute the desired orientation of the camera, first in degrees, then in radians. The setPos call actually sets the position of the camera. The setHpr call actually sets the orientation.

The camera should no longer be underground, and furthermore, the camera should now be rotating about the clearing:


Panda3D Manual: Loading and Animating the Panda Model

Now that the scenery is in place, we will now load an actor. Update your code to look like this:

import direct.directbase.DirectStart
from direct.task import Task
from direct.actor import Actor
import math

#Load the first environment model
environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

#Task to move the camera
def SpinCameraTask(task):
  angledegrees = task.time * 6.0
  angleradians = angledegrees * (math.pi / 180.0)
  base.camera.setPos(20*math.sin(angleradians),-20.0*math.cos(angleradians),3)
  base.camera.setHpr(angledegrees, 0, 0)
  return Task.cont

taskMgr.add(SpinCameraTask, "SpinCameraTask")

#Load the panda actor, and loop its animation
pandaActor = Actor.Actor("models/panda-model",{"walk":"models/panda-walk4"})
pandaActor.setScale(0.005,0.005,0.005)
pandaActor.reparentTo(render)
pandaActor.loop("walk")

run()

The Actor class is for animated models. Note that we use loadModel for static models, and Actor only when they are animated. The two constructor arguments for the Actor class are the name of the file containing the model, and a Python dictionary containing the names of the files containing the animations.

The command loop("walk") causes the walk animation to begin looping. The result is a panda walking in place, as if on a treadmill:


Panda3D Manual: Using Intervals to move the Panda

The next step is to cause the panda to actually move back and forth. Add the following lines of code:

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task import Task
from direct.actor import Actor
from direct.interval.IntervalGlobal import *
import math

#Load the first environment model
environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

#Task to move the camera
def SpinCameraTask(task):
  angledegrees = task.time * 6.0
  angleradians = angledegrees * (math.pi / 180.0)
  base.camera.setPos(20*math.sin(angleradians),-20.0*math.cos(angleradians),3)
  base.camera.setHpr(angledegrees, 0, 0)
  return Task.cont

taskMgr.add(SpinCameraTask, "SpinCameraTask")

#Load the panda actor, and loop its animation
pandaActor = Actor.Actor("models/panda-model",{"walk":"models/panda-walk4"})
pandaActor.setScale(0.005,0.005,0.005)
pandaActor.reparentTo(render)
pandaActor.loop("walk")

#Create the four lerp intervals needed to walk back and forth
pandaPosInterval1= pandaActor.posInterval(13,Point3(0,-10,0), startPos=Point3(0,10,0))
pandaPosInterval2= pandaActor.posInterval(13,Point3(0,10,0), startPos=Point3(0,-10,0))
pandaHprInterval1= pandaActor.hprInterval(3,Point3(180,0,0), startHpr=Point3(0,0,0))
pandaHprInterval2= pandaActor.hprInterval(3,Point3(0,0,0), startHpr=Point3(180,0,0))

#Create and play the sequence that coordinates the intervals
pandaPace = Sequence(pandaPosInterval1, pandaHprInterval1,
  pandaPosInterval2, pandaHprInterval2, name = "pandaPace")
pandaPace.loop()

run()

Intervals are tasks that change a property from one value to another over a specified period of time. Starting an interval effectively starts a background process that modifies the property over the specified period of time. For example, consider the pandaPosInterval1 above. When that interval is started, it will gradually adjust the position of the panda from (0,10,0) to (0,-10,0) over a period of 13 seconds. Similarly, when the pandaHprInterval1 is started, the orientation of the panda will rotate 180 degrees over a period of 3 seconds.

Sequences are tasks that execute one interval after another. The pandaPace sequence above causes the panda to move in a straight line, then turn, then in the opposite straight line, then to turn again. The code pandaPace.loop() causes the Sequence to be started in looping mode.

The result of all this is to cause the panda to pace back and forth from one tree to the other.


Panda3D Manual: A Panda "Hello World" using C++

Recommendation: Use Python

Caution: we do not recommend writing Panda3D programs in 100% C++. Instead, we suggest writing your programs in mostly-python, with a little C++ here and there. The reasons we suggest this are:

First, because we've tried it very successfully. Both Disney and Carnegie-Mellon have been creating applications in python with excellent results. All the commercial Disney games that use Panda3D are written in mostly-python. It is very easy to attain excellent performance using mostly-python, and the development is a lot quicker and easier than using C++.

Second, there are parts of Panda3D that are written in python. These are not the speed-critical parts. For instance, the scene graph browser is written in python. The heads-up-display code is in python. If you reject python entirely, you lose access to a lot of important functionality.

Third, the entire Panda3D manual is in python. Also, all the sample programs are in python. It is much harder to learn Panda3D if you try to translate the manual from python to C++ at the same time as you're trying to learn Panda3D.

So, You're Determined to Use C++

Ah well, we tried to talk you out of it, but you're determined...

This tutorial is called "A Panda Hello World." It is a typical example of a simple Panda3D program. Walking through this tutorial will enable you to obtain some limited familiarity with the Panda3D API, without having to learn the entire thing.

The program that we are going to create will load up a small scene containing some grass and a panda. The panda will be animated to walk back and forth over the grass.

Note: This tutorial is intended for C++ usage only. For the Python equivalent of this tutorial, click here.


Panda3D Manual: Starting Panda3D (C++)
<<prev top next>>

To start Panda3D, create a text file and save it with a .cxx extension. PSPad is a good text-editor with C++ highlighting, but any text editor will work. Enter the following text into your C++ file:

#include "pandaFramework.h"
#include "pandaSystem.h"

PandaFramework framework;

int main(int argc, char *argv[]) {
    //open a new window framework
  framework.open_framework(argc, argv);
    //set the window title to My Panda3D Window
  framework.set_window_title("My Panda3D Window");
    //open the window
  WindowFramework *window = framework.open_window();

  //here is room for your own code

    //do the main loop, equal to run() in python
  framework.main_loop();
    //close the window framework
  framework.close_framework();
  return (0);
}

For information about the Window Framework to open a window, click here.

pandaFramework.h and pandaSystem.h load most of the Panda3D modules. The main_loop() subroutine contains the Panda3D main loop. It renders a frame, handles the background tasks, and then repeats. It does not normally return, so it only needs to be called once and must be the last line in your script. In this particular example, there will be nothing to render, so you should expect a window containing an empty grey area.

To run your program, follow the compile steps explained on this page.

If Panda3D has been installed properly, a gray window titled My Panda3D Window will appear. There is nothing we can do with this window, but that will change shortly.


Panda3D Manual: Loading the Grassy Scenery (C++)
<<prev top next>>

With Panda3D running properly, it is now possible to load some grassy scenery. Update your code as follows:

#include "pandaFramework.h"
#include "pandaSystem.h"

PandaFramework framework;

int main(int argc, char *argv[]) {
    //load the window and stuff
  framework.open_framework(argc, argv);
  framework.set_window_title("My Panda3D Window");
  WindowFramework *window = framework.open_window();

    //load the environment model
  NodePath environ = window->load_model(framework.get_models(),"models/environment");
  environ.reparent_to(window->get_render());
  environ.set_scale(0.25,0.25,0.25);
  environ.set_pos(-8,42,0);

    //do the rest
  framework.main_loop();
  framework.close_framework();
  return (0);
}

The command window->load_model(framework.get_models(),path) loads the specified file, in this case the environment.egg file in the models folder. The return value is a 'NodePath', effectively a pointer to the model. Note that Panda Filename Syntax uses the forward-slash, even under Windows.

Panda3D contains a data structure called the scene graph. The scene graph is a tree containing all objects that need to be rendered. At the root of the tree is an object named render. You can get the NodePath of render by calling window->get_render(). Nothing is rendered until it is first installed into the scene graph.

To install the grassy scenery model into the scene graph, we use the method reparent_to. This sets the parent of the model, thereby giving it a place in the scene graph. Doing so makes the model visible.

Finally, we adjust the position and scale of the model. In this particular case, the environment model is a little too large and somewhat offset for our purposes. The set_scale and set_pos rescale and recenter the model.

Go ahead and run the program. You should see this:

The rock and tree appear to be hovering. The camera is slightly below ground, and backface culling is making the ground invisible to us. If we reposition the camera, the terrain will look better.


Panda3D Manual: Controlling the Camera (C++)
<<prev top next>>

Panda3D comes with a function that enables you to move the camera using the mouse. To use this, insert:

window->setup_trackball();

just before your call to the main loop. The keys to navigate using this function are:

  • Left Button: pan left and right
  • Right Button: move forwards and backwards
  • Middle Button: rotate around the origin of the application
  • Right and Middle Buttons: roll the point of view around the view axis

Go ahead and try this camera control system. The problem with this camera control system is that it is sometimes awkward, it is not always easy to get the camera pointed in the direction we want.

Instead, we want to have our camera position changed every frame. Because there is no basic implementation for tasks in C++ (yet), we have to write the main loop by ourselves. We can do this by making an infinite loop, in which the camera position gets updated and the function framework.do_frame gets called. All this function does is rendering the current frame. It returns false if the window is terminated, and the main loop should stop. Update your code as follows:

#include "pandaFramework.h"
#include "pandaSystem.h"

PandaFramework framework;

int main(int argc, char *argv[]) {
    //open a new window framework
  framework.open_framework(argc, argv);
    //set the window title to My Panda3D Window
  framework.set_window_title("My Panda3D Window");
    //open the window
  WindowFramework *window = framework.open_window();
  NodePath cam = window->get_camera_group(); //get the camera and store it
    //load the environment model
  NodePath environ = window->load_model(framework.get_models(),"models/environment");
  environ.reparent_to(window->get_render());
  environ.set_scale(0.25,0.25,0.25);
  environ.set_pos(-8,42,0);
    //do the main loop:
  ClockObject* clock; //create a clock object for time measurement
  clock=ClockObject::get_global_clock();
  Thread *current_thread = Thread::get_current_thread();
  while(framework.do_frame(current_thread)) {
    double time = clock->get_real_time(); //get the time in seconds
    double angledegrees = time * 6.0; //the angle of the camera in degrees
    double angleradians = angledegrees * (3.14 / 180.0); //in radians
    cam.set_pos(20*sin(angleradians),-20.0*cos(angleradians),3); //set the position
    cam.set_hpr(angledegrees, 0, 0); //set the hpr
  }
  //close the window framework
  framework.close_framework();
  return (0);
}

Note: It is not possible to update the camera's position while the trackball is enabled. To have this code work, you must not call the setup_trackball() function.

The code in our home-made main loop calculates the desired position of the camera based on how much time has elapsed. To get this time, we use Panda3D's global ClockObject. (It is sometimes convenient to put these calculations in a separate function, but since we only have one task now, it's not much of a problem.) The camera rotates 6 degrees every second. The first two lines compute the desired orientation of the camera, first in degrees, then in radians. The set_pos call actually sets the position of the camera. The set_hpr call actually sets the orientation.

The camera should no longer be underground, and furthermore, the camera should now be rotating about the clearing:


Panda3D Manual: Loading and Animating the Panda Model (C++)
<<prev top next>>

Now that the scenery is in place, we will now load an animated panda. Update your code to look like this:

#include "pandaFramework.h"
#include "pandaSystem.h"

PandaFramework framework;

int main(int argc, char *argv[]) {
    //open a new window framework and set title
  framework.open_framework(argc, argv);
  framework.set_window_title("My Panda3D Window");

    //open the window
  WindowFramework *window = framework.open_window();
  NodePath cam = window->get_camera_group(); //get the camera and store it

    //load the environment model
  NodePath environ = window->load_model(framework.get_models(),"models/environment");
  environ.reparent_to(window->get_render());
  environ.set_scale(0.25,0.25,0.25);
  environ.set_pos(-8,42,0);

    //load our panda
  NodePath pandaActor = window->load_model(framework.get_models(),"panda-model");
  pandaActor.set_scale(0.005,0.005,0.005);
  pandaActor.reparent_to(window->get_render());

    //load the walk animation
  window->load_model(pandaActor,"panda-walk4");
  window->loop_animations(0);

    //do the main loop:
  ClockObject* clock; //create a clock object for time measurement
  clock=ClockObject::get_global_clock();
  Thread *current_thread = Thread::get_current_thread();
  while(framework.do_frame(current_thread)) {
    double time = clock->get_real_time(); //get the time in seconds
    double angledegrees = time * 6.0; //the angle of the camera in degrees
    double angleradians = angledegrees * (3.14 / 180.0); //in radians
    cam.set_pos(20*sin(angleradians),-20.0*cos(angleradians),3); //set the position
    cam.set_hpr(angledegrees, 0, 0); //set the hpr
  }

    //close the window framework
  framework.close_framework();
  return (0);
}

Different from Python, you have to load the animation file as a normal model and reparent it to render just as you would do with a normal model.

The command loop_animations(0) causes all animations to begin looping, since the walk animation is the only one at the moment. What that 0 is doing there is not necessary to know right now. The result is a panda walking in place, as if on a treadmill:


Panda3D Manual: Using Intervals to move the Panda (C++)
<<prev top next>>

The next step is to cause the panda to actually move back and forth. Add the following lines of code:

#include "pandaFramework.h"
#include "pandaSystem.h"

#include "cIntervalManager.h"
#include "cLerpNodePathInterval.h"
#include "cMetaInterval.h"

PandaFramework framework;

int main(int argc, char *argv[]) {
    //open a new window framework and set title
  framework.open_framework(argc, argv);
  framework.set_window_title("My Panda3D Window");

    //open the window
  WindowFramework *window = framework.open_window();
  NodePath cam = window->get_camera_group(); //get the camera and store it

    //load the environment model
  NodePath environ = window->load_model(framework.get_models(),"models/environment");
  environ.reparent_to(window->get_render());
  environ.set_scale(0.25,0.25,0.25);
  environ.set_pos(-8,42,0);

    //load our panda
  NodePath pandaActor = window->load_model(framework.get_models(),"panda-model");
  pandaActor.set_scale(0.005,0.005,0.005);
  pandaActor.reparent_to(window->get_render());

    //load the walk animation
  window->load_model(pandaActor,"panda-walk4");
  window->loop_animations(0);

    //create the lerp intervals needed to walk back and forth
  PT(CLerpNodePathInterval) pandaPosInterval1;
  pandaPosInterval1 = new CLerpNodePathInterval("pandaPosInterval1",13.0,CLerpInterval::BT_no_blend,
                              true,false,pandaActor,window->get_render());
  pandaPosInterval1->set_start_pos(LPoint3f(0,10,0));
  pandaPosInterval1->set_end_pos(LPoint3f(0,-10,0));
  PT(CLerpNodePathInterval) pandaPosInterval2;
  pandaPosInterval2 = new CLerpNodePathInterval("pandaPosInterval2",13.0,CLerpInterval::BT_no_blend,
                              true,false,pandaActor,window->get_render());
  pandaPosInterval2->set_start_pos(LPoint3f(0,-10,0));
  pandaPosInterval2->set_end_pos(LPoint3f(0,10,0));
  PT(CLerpNodePathInterval) pandaHprInterval1;
  pandaHprInterval1 = new CLerpNodePathInterval("pandaHprInterval1",3.0,CLerpInterval::BT_no_blend,
                              true,false,pandaActor,window->get_render());
  pandaHprInterval1->set_start_hpr(LPoint3f(0,0,0));
  pandaHprInterval1->set_end_hpr(LPoint3f(180,0,0));
  PT(CLerpNodePathInterval) pandaHprInterval2;
  pandaHprInterval2 = new CLerpNodePathInterval("pandaHprInterval2",3.0,CLerpInterval::BT_no_blend,
                              true,false,pandaActor,window->get_render());
  pandaHprInterval2->set_start_hpr(LPoint3f(180,0,0));
  pandaHprInterval2->set_end_hpr(LPoint3f(0,0,0));

    //create and play the sequence that coordinates the intervals
  PT(CMetaInterval) pandaPace;
  pandaPace = new CMetaInterval("pandaPace");
  pandaPace->add_c_interval(pandaPosInterval1,0,CMetaInterval::RS_previous_end);
  pandaPace->add_c_interval(pandaHprInterval1,0,CMetaInterval::RS_previous_end);
  pandaPace->add_c_interval(pandaPosInterval2,0,CMetaInterval::RS_previous_end);
  pandaPace->add_c_interval(pandaHprInterval2,0,CMetaInterval::RS_previous_end);
  pandaPace->loop();

    //do the main loop:
  ClockObject* clock (ClockObject::get_global_clock()); //get the global clock object
  Thread *current_thread = Thread::get_current_thread();
  while(framework.do_frame(current_thread)) {
    CIntervalManager::get_global_ptr()->step(); //step the interval manager
    double time = clock->get_real_time(); //get the time in seconds
    double angledegrees = time * 6.0; //the angle of the camera in degrees
    double angleradians = angledegrees * (3.14 / 180.0); //in radians
    cam.set_pos(20*sin(angleradians),-20.0*cos(angleradians),3); //set the position
    cam.set_hpr(angledegrees, 0, 0); //set the hpr
  }

    //close the window framework
  framework.close_framework();
  return (0);
}

Intervals are tasks that change a property from one value to another over a specified period of time. Starting an interval effectively starts a background process that modifies the property over the specified period of time. For example, consider the pandaPosInterval1 above. When that interval is started, it will gradually adjust the position of the panda from (0,10,0) to (0,-10,0) over a period of 13 seconds. Similarly, when the pandaHprInterval1 is started, the orientation of the panda will rotate 180 degrees over a period of 3 seconds.

MetaIntervals are tasks that execute one interval after another. The pandaPace sequence above causes the panda to move in a straight line, then turn, then in the opposite straight line, then to turn again. The code pandaPace->loop() causes the MetaInterval to be started in looping mode.

The result of all this is to cause the panda to pace back and forth from one tree to the other.


Panda3D Manual: User Contributed Tutorials and Examples

Official Sample Programs

The Sample Programs in the Distribution are intended to show you how to use Panda3D to achieve a large number of different goals. They should be your first stop when looking for ideas on how to achieve a given task.

Unofficial Sample Programs

The following are some of the examples contributed by the community. Note that since these tutorials are owned by private individuals not affiliated with Carnegie-Mellon, we cannot vouch that these tutorials are valid with every version of Panda3D.

lettier (IPKnightly):

  • Bouncing Box
  • Room Walking
  • PyGame Sound
  • Ping! (3D Pong)
  • ECAPS (Space Fighter)
  • FPS Calculation
  • Boids
  • SpaceFlight (3D Lunar Lander)

Note: not all examples will work with latest Panda3D version. Please send a request for an update to contact @ lettierishere.com.

TipToe::

  • Edge Screen Tracking

Networking client server examples follow.

Yellow and bigfoot29:

  • Panda3D network example

bigfoot29:

  • small Chatserver/client


Panda3D Manual: Video Tutorials

The following sections contain links to video tutorials about Panda3D.


Panda3D Manual: User Contributed Video Tutorials

This is a list of video tutorials contributed by Panda3D users.

Downloading Panda 3D and Opening a Panda 3D Program - 3.5 MB - Quicktime Format

Downloading and Installing the Blender DirectX Exporter - 2.5 MB - Quicktime Format


Panda3D Manual: Disney Video Lectures

David Rose, from Walt Disney VR Studio, occasionally holds classes based on the fundamentals and working of Panda3D. Below are a few of these lectures.

  • Characters Part I Recorded October 8, 2003. Eggs, Characters and Actors, but audio quality is very poor.
  • Characters Part II Recorded October 22, 2003. This is a recap of character animation in Panda3D.
  • PStats Recorded February 24, 2004. Demonstrating the use of Panda's built-in performance analysis tool.
  • Threading Recorded March 26, 2008. The current state-of-the-art in multiprogramming in Panda.
  • Windows, Buffers, and Cameras Recorded April 9, 2008. Fundamentals of managing windows and offscreen buffers, and connecting them up to cameras for rendering.
  • Scene Graph Recorded April 2, 2008. Fundamentals of Panda's scene graph, tricks you can do with it, and how it is implemented.
  • DistributedObjects Recorded April 16, 2008. How to use Panda's DistributedObject system, for the Python developer.
  • DistributedObjects and the OTP Server Recorded April 23, 2008. Panda's DistributedObject system, and how it relates to the VR Studio's OTP Server.
  • The OTP Server Recorded April 30, 2008. What's inside the VR Studio's OTP Server (not part of Panda).
  • The PRC System Recorded May 7, 2008. All about Panda's run-time configuration system.
  • Collisions Recorded May 14, 2008. How to use Panda's built-in collision system, and how it works.
  • egg-palettize Recorded May 21, 2008. How to use egg-palettize, and why you'd want to.
  • interrogate Recorded June 4, 2008. How interrogate works to generate Panda's Python/C++ interface.
  • ODE in Panda Recorded June 18, 2008. How Panda's built-in ODE integration is intended to be used.
  • Inside an Actor Recorded July 2, 2008. How the Actor class is implemented, and how to use it.


Panda3D Manual: Programming with Panda

The programming with panda section of the manual attempts to enumerate all the various functionality that Panda3D provides.

This manual does not attempt to be complete in the sense of listing every class, function, and method provided by Panda3D. If that's what you're looking for, go to the Reference Section instead.

The purpose of this manual is to:

1. List all of Panda3D's broad capabilities,

2. Help help you understand what purpose these serve,

3. Give you a general understanding of how to build games in Panda3D.

In time, though, you will need to supplement the knowledge provided by this manual by studying the Reference Section.


Panda3D Manual: The Scene Graph

The Scene Graph: a Tree of Nodes

Many simple 3D engines maintain a list of 3D models to render every frame. In these simple engines, one must allocate a 3D model (or load it from disk), and then insert it into the list of models to render. The model is not "visible" to the renderer until it is inserted into the list.

Panda3D is slightly more sophisticated. Instead of maintaining a list of objects to render, it maintains a tree of objects to render. An object is not visible to the renderer until it is inserted into the tree.

The tree consists of objects of class PandaNode. This is actually a superclass for a number of other classes: ModelNode, GeomNode, LightNode, and so forth. Throughout this manual, it is common for us to refer to objects of these classes as simply nodes. The root of the tree is a node called render. (Note: there may be additional roots for specialized purposes, like render2d for 2D objects)

Panda3D's "tree of things to render" is named the scene graph.

What you Need to Know about the Hierarchical Scene Graph

Here are the most important things you need to know about the hierarchical arrangement of the scene graph:

  1. You control where objects go in the tree. When you insert an object into the tree, you specify where to insert it. You can move branches of the tree around. You can make the tree as deep or as shallow as you like.

  2. Positions of objects are specified relative to their parent in the tree. For example, if you have a 3D model of a hat, you might want to specify that it always stays five units above a 3D model of a certain person's head. Insert the hat as a child of the head, and set the position of the hat to (0,0,5).

  3. When models are arranged in a tree, any rendering attributes you assign to a node will propagate to its children. For example, if you specify that a given node should be rendered with depth fog, then its children will also be rendered with depth fog, unless you explicitly override at the child level.

  4. Panda3D generates bounding boxes for each node in the tree. A good organizational hierarchy can speed frustum and occlusion culling. If the bounding box of an entire branch is outside the frustum, there is no need to examine the children.

Beginners usually choose to make their tree completely flat--everything is inserted immediately beneath the root. This is actually a very good initial design. Eventually, you will find a reason to want to add a little more depth to the hierarchy. But it is wise not to get complicated until you have a clear, specific reason to do so.

NodePaths

There is a helper class called NodePath which is a very small object containing a pointer to a node, plus some administrative information. For now, you can ignore the administrative information; it will be explained in a later section of the manual. It is the intent of the panda designers that you should think of a NodePath as a handle to a node. Any function that creates a node returns a NodePath that refers to the newly-created node.

A NodePath isn't exactly a pointer to a node; it's a "handle" to a node. Conceptually, this is almost a distinction without a difference. However, there are certain API functions that expect you to pass in a NodePath, and there are other API functions that expect you to pass in a node pointer. Because of this, although there is little conceptual difference between them, you still need to know that both exist.

You can convert a NodePath into a "regular" pointer at any time by calling nodePath.node(). However, there is no unambiguous way to convert back. That's important: sometimes you need a NodePath, sometimes you need a node pointer. Because of this, it is recommended that you store NodePaths, not node pointers. When you pass parameters, you should probably pass NodePaths, not node pointers. The callee can always convert the NodePath to a node pointer if it needs to.

NodePath-Methods and Node-Methods

There are many methods that you can invoke on nodepaths, which are appropriate for nodes of any type. Specialized node types, like LODNodes and Cameras (for instance), provide additional methods that are available only for nodes of that type, which you must invoke on the node itself. Here are some assorted examples:

# NODEPATH METHODS:
myNodePath.setPos(x,y,z)
myNodePath.setColor(banana)

# LODNODE METHODS:
myNodePath.node().addSwitch(1000, 100)
myNodePath.node().setCenter(Point3(0, 5, 0))

# CAMERA NODE METHODS:
myNodePath.node().setLens(PerspectiveLens())
myNodePath.node().getCameraMask()

Always remember: when you invoke a method of NodePath, you are actually performing an operation on the node to which it points.

In the example above, we call node-methods by first converting the nodepath into a node, and then immediately calling the node-method. This is the recommended style.


Panda3D Manual: Scene Graph Manipulations

The default scene graphs

By default, there are two different scene graphs created automatically when you start up Panda3D. These graphs are referred to by their top nodes: render and render2d.

You use render most often; this is the top of the ordinary 3-D scene. In order to put an object in the world, you will need to parent it to render (or to some node that is in turn parented to render).

You will use render2d to render 2-D GUI elements, such as text or buttons, that you want to display onscreen; for instance, a heads-up display. Anything parented to render2d will be rendered on top of the 3-D scene, as if it were painted on the screen glass.

The coordinate system of render2d is set up to match that of the mouse inputs: the lower-left corner of the screen is (-1, 0, -1), and the upper-right corner is (1, 0, 1). Since this is a square coordinate system, but the screen is usually non-square, objects parented directly to render2d may appear squashed. For this reason, Panda3D also defines a child of render2d, called aspect2d, which has a scale applied to it to correct the non-square aspect ratio of render2d. Most often, you will parent GUI elements to aspect2d rather than render2d.

Specifically, the coordinate system of aspect2d is by default scaled such that x ranges over [-ratio,ratio], and y ranges over [-1,1] where ratio is screen_size_x/screen_size_y (in the normal case of a window wider than it is tall).

Finally, you may see references to one other top-level node called hidden. This is simply an ordinary node that has no rendering properties set up for it, so that things parented to hidden will not be rendered. Older Panda3D code needed to use hidden to remove a node from the render scene graph. However, this is no longer necessary, and its use is not recommended for new programs; the best way to remove a node from render is to call nodePath.detachNode().

Loading models

You can load up a model with a filename path, in the Panda Filename Syntax, to the model's egg or bam file. In many examples, the filename extension is omitted; in this case, Panda will look for a file with either the .egg or .bam extension.

myNodePath = loader.loadModel('my/path/to/models/myModel.egg')

The first time you call loadModel() for a particular model, that model is read and saved in a table in memory; on each subsequent call, the model is simply copied from the table, instead of reading the file.

The above call is appropriate for loading static models; for animated models, see Loading Actors and Animations.

Reparenting nodes and models

One of the most fundamental scene graph manipulations is changing a node's parent. You need to do this at least once after you load a model, to put it under render for viewing:

myModel.reparentTo(render)

And to remove it again:

myModel.detachNode()

To completely remove a nodePath from the scene graph and memory call removeNode, this has the effect of emptying the node and releasing the memory taken up by the node. Use it only when you have no further use for the node:

myNodePath.removeNode()

As you become more comfortable with scene graph operations, you may find yourself taking more and more advantage of a deeply nested scene graph, and you may start to parent your models to other nodes than just render. Sometimes it is convenient to create an empty node for this purpose, for instance, to group several models together:

dummyNode = render.attachNewNode("Dummy Node Name")
myModel.reparentTo(dummyNode)
myOtherModel.reparentTo(dummyNode)

Since a node inherits its position information from its parent node, when you reparent a node in the scene graph you might inadvertently change its position in the world. If you need to avoid this, you can use a special variant on reparentTo():

myModel.wrtReparentTo(newParent)

The "wrt" prefix stands for "with respect to". This special method works like reparentTo(), except that it automatically recomputes the local transform on myModel to compensate for the change in transform under the new parent, so that the node ends up in the same position relative to the world.

Note that the computation required to perform wrtReparentTo() is a floating-point matrix computation and is therefore inherently imprecise. This means that if you use wrtReparentTo() repeatedly, thousands of times on the same node, it may eventually accumulate enough numerical inaccuracies to introduce a slight scale on the object (for instance, a scale of 1, 1, 0.99999); if left unchecked, this scale could eventually become noticeable.

Beginners tend to overuse this method; you should not use wrtReparentTo() unless there is a real reason to use it.


Panda3D Manual: Common State Changes

Summary

This page lists some of the most common changes you can make to a 3D model. This page is really only a quick cheat-sheet summary: the detailed documentation for these operations comes later in the manual.

State Change Cheat Sheet

Two of the most common changes are position and orientation.

myNodePath.setPos(X,Y,Z)

By default in Panda3D, the X axis points to the right, the Y axis is forward, and Z is up. An object's rotation is usually described using Euler angles called Heading, Pitch, and Roll (sometimes called Yaw, Pitch, and Roll in other packages)--these specify angle rotations in degrees. (If you are more comfortable using quaternions, the setQuat() method can be used to specify the rotation as a quaternion.)

You can change an object's size, either uniformly, or with a different value of x, y, and z.

myNodePath.setScale(uniform)

Sometimes it is convenient to adjust a single component individually:

myNodePath.setX(X)
myNodePath.setY(Y)
myNodePath.setZ(Z)
myNodePath.setH(H)
myNodePath.setP(P)
myNodePath.setR(R)
myNodePath.setSx(SX)
myNodePath.setSy(SY)
myNodePath.setSz(SZ)

Or all at the same time:

myNodePath.setPosHprScale(X,Y,Z,H,P,R,SX,SY,SZ)

You can also query the current transform information for any of the above:

myNodePath.getPos()
myNodePath.getX()
myNodePath.getY()
myNodePath.getZ()

Also, by using the functions setTag() and getTag() you can store your own information in key value pairs. For example:

myNodePath.setTag('Key', 'Value')

As a more advanced feature, you may also set or query the position (or any of the above transform properties) of a particular NodePath with respect to another one. To do this, specify the relative NodePath as the first parameter:

myNodePath.setPos(otherNodePath, X, Y, Z)
myNodePath.getPos(otherNodePath)

Putting a NodePath as the first parameter to any of the transform setters or getters makes it a relative operation. The above setPos() means to set myNodePath to the position (X, Y, Z), relative to otherNodePath--that is, the position myNodePath would be in if it were a child of otherNodePath and its position were set to (X, Y, Z). The getPos() call returns the position myNodePath would have if it were a child of otherNodePath.

It is also important to note that you can use the NodePath in its own relative sets and gets. This maybe helpful in situations where you are concerned with distances. For example:

# move myNodePath 3 units forward in the x
myNodePath.setPos(myNodePath, 3, 0, 0)

These relative sets and gets are a very powerful feature of Panda's scene graph, but they can also be confusing; don't worry if it doesn't make sense right now.

The lookAt() method rotates an model to face another object; that is, it rotates the first object so that its +Y axis points toward the second object. Note that a particular model might or might not have been generated with the +Y axis forward, so this doesn't necessarily make a model "look at" the given object.

myNodePath.lookAt(otherObject)

Color changes are another common alteration. Values for color are floating point numbers from 0 to 1, 0 being black, 1 being white.

myNodePath.setColor(R,G,B,A)

If models have textures, they may not be distinguishable or even visible at certain color settings. Setting the color to white may restore the visibility of the texture, but it is better to simply clear the current color settings.

myNodePath.clearColor()

Note the fourth component of color is alpha. This is usually used to indicate transparency, and it is usually 1.0 to indicate the object is not transparent. If you set the alpha to a value between 0 and 1, you can fade the object to invisible. However, in order for the alpha value to be respected, you must first enable transparency:

myNodePath.setTransparency(TransparencyAttrib.MAlpha)

The parameter to setTransparency() is usually TransparencyAttrib.MAlpha, which is ordinary transparency. You can also explicitly turn transparency off with TransparencyAttrib.MNone. (Other transparency modes are possible, but that is a more advanced topic. Some older code may pass just 0 or 1 for this parameter, but it is better to name the mode.) If you don't explicitly enable transparency first, the alpha component of color may be ignored. Be sure you don't enable transparency unnecessarily, since it does enable a more expensive rendering mode.

Setting an object's color completely replaces any color on the vertices. However, if you have created a model with per-vertex color, you might prefer to modulate the object's color without losing the per-vertex color. For this there is the setColorScale() variant, which multiples the indicated color values by the object's existing color:

myNodePath.setColorScale(R,G,B,A)

One use of setColorScale() is to apply it at the top of the scene graph (e.g. render) to darken the entire scene uniformly, for instance to implement a fade-to-black effect.

Since alpha is so important, there is also a method for scaling it without affecting the other color components:

myNodePath.setAlphaScale(SA)

To temporarily prevent an object from being drawn on all cameras, use hide() and show():

myNodePath.hide()
myNodePath.show()

If you want to hide an object for one camera but not another, you can use the hide and show commands in conjunction with the camera.setCameraMask() function:

camera1.node().setCameraMask(BitMask32.bit(0))
camera2.node().setCameraMask(BitMask32.bit(1))
myNodePath.show(BitMask32.bit(1)) #will only be shown on camera2...

Please note that using hide/show without an argument will mess up any hide/shows with the argument (show(bit) will not undo a hide()...) To hide an object from all cameras instead use nodepath.hide(BitMask32.allOn()). To set the camera mask for the default camera use base.cam, not base.camera. Please see the camera section for information on how to set up multiple cameras.

Any object that is parented to the object that is hidden will also be hidden.

If you have trouble to place, scale or rotate your nodes you can use the place() function to bring up a small GUI which will help you. You need to have TkInter installed to use it.

myNodePath.place()


Panda3D Manual: Manipulating a Piece of a Model

Every model, when loaded, becomes a ModelNode in the scene graph. Beneath the ModelNode are one or more GeomNodes containing the actual polygons. If you want to manipulate a piece of a model, for instance, if you want to change the texture of just part of a model, you need a pointer to the relevant geomnode.

In order to obtain such a pointer, you must first ensure that the relevant geometry is in a GeomNode of its own (and not merged with all the other geometry). In other words, you must ensure that panda's optimization mechanisms do not cause the geometry to be merged with the geometry of the rest of the model. While normally this optimization is a good thing, if you want to change textures on a specific part of the model (for example, just a character's face) you will need this geometry to be separate.

There are two different ways that you should do this, according to the type of model it is.

Animated (skeleton animation) models

If your model is animated via keyframe animation in a package such as 3DSMax or Maya--that is, the sort of model you expect to load in via the Actor interface--then Panda will be aggressive in combining all of the geometry into as few nodes as possible. In order to mark particular geometry to be kept separate, you should use the egg-optchar program.

The name "optchar" is short for "optimize character", since the egg-optchar program is designed to optimize an animated character for runtime performance by removing unused and unneeded joints. However, in addition to this optimization, it also allows you to label a section of a model for later manipulation. Once you have labeled a piece of geometry, Panda's optimization mechanisms will not fold it in to the rest of the model.

Your first step is to note the name of the object in your modeling program. For example, suppose you want to control the texture of a model's head, and suppose (hypothetically) the head is labeled "Sphere01" in your modeling program. Use egg-optchar to tell panda that "Sphere01" deserves to be kept separate and labeled:

egg-optchar -d outputDir -flag Sphere01=theHead modelFile.egg anim1.egg anim2.egg

Note that you must always supply the model file(s) and all of its animation files to egg-optchar at the same time. This is so it can examine all of the joints and determine which joints are actually animated; and it can remove joints by operating on all the files at once. The output of egg-optchar is written into the directory named by the "-d" parameter.

The "-flag" switch will ensure that panda does not rearrange the geometry for the named polyset, folding it into the model as a whole. It also assigns the polyset a meaningful name. Once you have labeled the relevant piece of geometry, you can obtain a pointer to it using the find method:

myModelsHead = myModel.find("**/theHead")

With this nodepath, you can manipulate the head separately from the rest of the model. For example, you can move the piece using setPos, or change its texture using setTexture, or for that matter, do anything that you would do to any other scene graph node.

Unanimated (environment) models

Other kinds of models, those that do not contain any skeleton or animations, are not optimized as aggressively by the Panda loader, on the assumption that the model's hierarchy was structured the way it is intentionally, to maximize culling (see Pipeline Tips). Thus, only certain nodes are combined with others, so it's quite likely that an object that you modeled as a separate node in your modeling package will still be available under the same name when you load it in Panda. But Panda doesn't promise that it will never collapse together nodes that it thinks need to be combined for optimization purposes, unless you tell it not to.

In the case of an unanimated model, the way to protect a particular node is to insert the <Model> flag into the egg file within the particular group. The way to do this depends on your modeling package (and this documentation still needs to be written).


Panda3D Manual: Searching the Scene Graph

It is often useful to get a handle to a particular node deep within the scene graph, especially to get a sub-part of a model that was loaded from a single file. There are a number of methods dedicated to finding entrenched nodes and returning the NodePaths.

First, and most useful, is the ls() command:

myNodePath.ls()

This simply lists all of the children of the indicated NodePath, along with all of their children, and so on until the entire subgraph is printed out. It also lists the transforms and Render Attributes that are on each node. This is an especially useful command for when you're running interactively with Python; it's a good way to check that the scene graph is what you think it should be.

The two methods find() and findAllMatches() will return a NodePath and a NodePathCollection respectively. These methods require a path string as an argument. Searches can based on name or type. In its simplest form this path consists of a series of node names separated by slashes, like a directory pathname. When creating the string each component may optionally consist of one of the following special names, instead of a node name.

* | Matches exactly one node of any name
** | Matches any sequence of zero or more nodes
+typename | Matches any node that is or derives from the given type
-typename | Matches any node that is the given type exactly
=tag | Matches any node that has the indicated tag
=tag=value | Matches any node whose tag matches the indicated value

Standard filename globbing characters, such as *, ?, and [a-z] are also usable. Also the @@ special character before a node name indicates that this particular node is a stashed node. Normally, stashed nodes are not returned. @@*, by extension, means any stashed node.

The argument may also be followed with control flags. To use a control flag, add a semicolon after the argument, followed by at least one of the special flags with no extra spaces or punctuation.

-h | Do not return hidden nodes
+h | Return hidden nodes
-s | Do not return stashed nodes unless explicitly referenced with @@
+s | Return stashed nodes even without any explicit @@ characters
-i | Node name comparisons are not case insensitive: case must match exactly
+i | Node name comparisons are case insensitive: case is not important. This affects matches against the node name only; node type and tag strings are always case sensitive

The default flags are +h-s-i.

The find() method searches for a single node that matches the path string given. If there are multiple matches, the method returns the shortest match. If it finds no match, it will return an empty NodePath. On the other hand, findAllMatches() will return all NodePaths found, shortest first.

myNodePath.find("<Path>")
myNodePath.findAllMatches("<Path>")

Some examples:

myNodePath.find("house/door")

This will look for a node named "door", which is a child of a node named "house", which is a child of the starting path.

myNodePath.find("**/red*")

This will look for any node anywhere in the tree (below the starting path) with a name that begins with "red".

In addition there are also the methods getParent() and getChildren(). getParent() returns the NodePath of the parent node. getChildren() returns the children of the current node as a NodePathCollection( use getChildrenAsList() if you want them as a List).

getChildrenAsList Example

for child in myNodePath.getChildrenAsList():
  print child

some examples:

#if you wanted to search up the Scene Graph until you found a certain node
while myNodePath.getParent()!=someAncestor:
  myNodePath=myNodePath.getParent()

For more information and a complete list of NodePath functions please see the API reference.


Panda3D Manual: Instancing

In the musical "A Chorus Line," the most well-known scene is when about 50 identical-looking young women line up left-to-right across the stage, and they all kick-left-kick-right in unison. To implement this in Panda3D, you might do this:

for i in range(50):
  dancer = Actor.Actor("chorus-line-dancer.egg", {"kick":"kick.egg"})
  dancer.loop("kick")
  dancer.setPos(i*5,0,0)
  dancer.reparentTo(render)

Here is the scene graph that we just created:

This works fine, but it is a little expensive. Animating a model involves a lot of per-vertex matrix calculations. In this case, we're animating 50 copies of the exact same model using 50 copies of the exact same animation. That's a lot of redundant calculation. It would seem that there must be some way avoid calculating the exact same values 50 times. There is: the technique is called instancing.

The idea is this: instead of creating 50 separate dancers, create only one dancer, so that the engine only has to update her animation once. Cause the engine to render her 50 times, by inserting her into the scene graph in 50 different places. Here is how it is done:

dancer = Actor.Actor("chorus-line-dancer.egg", {"kick":"kick.egg"})
dancer.loop("kick")
dancer.setPos(0,0,0)
for i in range(50):
  placeholder = render.attachNewNode("Dancer-Placeholder")
  placeholder.setPos(i*5,0,0)
  dancer.instanceTo(placeholder)

Here is a diagram of the scene graph we just created:

It's not a tree any more, it is a directed acyclic graph. But the renderer still traverses the graph using a recursive tree-traversal algorithm. As a result, it ends up traversing the dancer node 50 times. Here is a diagram of the depth-first traversal that the renderer takes through the graph. Note that this is not a diagram of the scene graph - it's a diagram of the renderer's path through the scene graph:

In other words, the renderer visits the dancer actor 50 times. It doesn't even notice that it's visiting the same actor 50 times, rather than visiting 50 different actors. It's all the same to the renderer.

There are 50 placeholder nodes, lined up across the stage. These are called dummy nodes. They don't contain any polygons, they're little tiny objects used mainly for organization. In this case, I'm using each placeholder as a platform on which a dancer can stand.

The position of the dancer is (0,0,0). But that's relative to the position of the parent. When the renderer is traversing placeholder 1's subtree, the dancer's position is treated as relative to placeholder 1. When the renderer is traversing placeholder 2's subtree, the dancer's position is treated as relative to placeholder 2. So although the position of the dancer is fixed at (0,0,0), it appears in multiple locations in the scene (on top of each placeholder).

In this way, it is possible to render a model multiple times without storing and animating it multiple times.

Advanced Instancing

Now, let's go a step further:

dancer = Actor.Actor("chorus-line-dancer.egg", {"kick":"kick.egg"})
dancer.loop("kick")
dancer.setPos(0,0,0)
chorusline = NodePath('chorusline')
for i in range(50):
  placeholder = chorusline.attachNewNode("Dancer-Placeholder")
  placeholder.setPos(i*5,0,0)
  dancer.instanceTo(placeholder)

This is the exact same code as before, except that instead of putting the 50 placeholders beneath render, I put them beneath a dummy node called chorusline. So my line of dancers is not part of the scene graph yet. Now, I can do this:

for i in range(3):
  placeholder = render.attachNewNode("Line-Placeholder")
  placeholder.setPos(0,i*10,0)
  chorusline.instanceTo(placeholder)

Here is the scene graph I just created:

But when the renderer traverses it using a recursive tree-traversal algorithm, it will see 3 major subtrees (rooted at a line-placeholder), and each subtree will contain 50 placeholders and 50 dancers, for a grand total of 150 apparent dancers.

Instancing: an Important Caveat

Instancing saves panda quite a bit of CPU time when animating the model. But that doesn't change the fact that the renderer still needs to render the model 150 times. If the dancer is a 1000 polygon model, that's still 150,000 polygons.

Note that each instance has its own bounding box, each is occlusion-culled and frustum-culled separately.

The NodePath: a Pointer to a Node plus a Unique Instance ID

If I had a pointer to the chorus-line dancer model, and I tried to ask the question "where is the dancer," there would be no well-defined answer. The dancer is not in one place, she is in 150 places. Because of this, the data type pointer to node does not have a method that retrieves the net transform.

This is very inconvenient. Being able to ask "where is this object located" is fundamental. There are other incredibly useful queries that you cannot perform because of instancing. For example, you cannot fetch the parent of a node. You cannot determine its global color, or any other global attribute. All of these queries are ill-defined, because a single node can have many positions, many colors, many parents. Yet these queries are essential. It was therefore necessary for the panda3d designers to come up with some way to perform these queries, even though a node can be in multiple locations at the same time.

The solution is based on the following observation: if I had a pointer to the chorus line-dancer model, and I also had a unique identifier that distinguishes one of the 150 instances from all the others, then I could meaningfully ask for the net transform of that particular instance of the node.

Earlier, it was noted that a NodePath contains a pointer to a node, plus some administrative information. The purpose of that administrative information is to uniquely identify one of the instances. There is no method Node::getNetTransform, but there is a method NodePath::getNetTransform. Now you know why.

To understand how NodePath got its name, think about what is necessary to uniquely identify an instance. Each of the 150 dancers in the graph above corresponds to a single path through the scene graph. For every possible path from root to dancer, there exists one dancer-instance in the scene. In other words, to uniquely identify an instance, you need a list of nodes that starts at the leaf and goes up to the root.

The administrative information in a nodepath is a list of nodes. You can fetch any node in the list, using the NodePath::node(i) method. The first one, node(0), is the node to which the NodePath points.


Panda3D Manual: The Configuration File

This section will explain about the configuration files that Panda3D uses.


Panda3D Manual: Configuring Panda

In the etc subdirectory, you will find a configuration file Config.prc. This controls several of Panda's configuration options - does it use OpenGL or DirectX, how much debugging output does it print, and so forth. The following table lists several of the most commonly-used variables.

For a full documentation about Panda3D's configuration system, click here to view the original documentation file.

To know about accessing config variables from within your code, please see Accessing Config Vars in a Program.

To get a more complete list of variables, see the list of all config variables.

Variable | Values | Default | Details
load-display | pandagl pandadx8 | pandagl | Specifies which graphics GSG to use for rendering (OpenGL or DirectX 8)
win-size | Number of pixels | 640 480 | Specifies the size of the Panda3D window
win-origin | Pixel offsets | 50 50 | Specifies the offset of the Panda3D window
window-title | Window title | Panda | Specifies the title of the Panda3D window
fullscreen | #t #f | #f | Enables full-screen mode (true or false)
undecorated | #t #f | #f | Removes border from window (true or false)
cursor-hidden | #t #f | #f | Hides mouse cursor (true or false)
show-frame-rate-meter | #t #f | #f | Shows the fps in the upper right corner of the screen (true or false)
notify-level-[package] | fatal error warning info debug spam | info | Sets notification levels for various Panda3D packages to control the amount of information printed during execution (fatal being least, spam being most)
model-path | Path string | see config file | Adds specified path to the list of paths searched when loading a model
audio-library-name | openal_audio fmod_audio miles_audio null | fmod_audio | Loads the appropriate audio drivers. Miles is a propriertary audio, so only select that option if you currently have it.
want-directtools | #t #f | #t line commented out | Enables directtools, a suite of interactive object/camera manipulation tools
want-tk | #t #f | #t line commented out | Enables support for using Tkinter/PMW (Python’s wrappers around Tk)


Panda3D Manual: List of All Config Variables

The following is a complete list of config variables as of Aug, 2007. It was generated by typing this into Python:

ConfigVariableManager.getGlobalPtr().listVariables()

Bear in mind that this list includes configuration parameters that control functionality that isn't fully implemented yet. Ie, not all of these config variables are as useful as they sound.

You can use this command yourself in case this list gets out of date.

allow-collider-multiple bool
default value = 0
Set this true to enable the use of a DoubleBitMask or QuadBitMask to manage many colliders added to a single traverser in one pass. If this is false, a one-word BitMask is always used instead, which is faster per pass, but may require more passes.

allow-incomplete-render bool
default value = 0
When this is true, the frame may be rendered even if some of the geometry in the scene has been paged out. The nonresident geometry will be rendered as soon as it can be paged back in, which may be several frames in the future. When this is false, geometry is always paged in when needed, holding up the frame render if necessary.

allow-nonpipeline-threads bool
default value = 0
This variable should only be set true for debugging or development purposes. When true, the threading-model variable may specify a threaded pipeline mode, even if pipelining is not compiled in. This will certainly result in erroneous behavior, and quite likely will cause a crash. Do not set this unless you know what you are doing.

allow-portal-cull bool
default value = 0
Set this true to enable portal clipping. This will enable the renderer to cull more objects that are clipped if not in the current list of portals. This is still somewhat experimental.

allow-unrelated-wrt bool
default value = 1
Set this true to allow unrelated NodePaths (that is, nodes which have no common ancestor) to be adjusted relative to each other. If true, these will be treated as if they had a common node above their top nodes.

alpha-bits int
default value = 0
The minimum number of alpha buffer bits requested.

alpha-scale-via-texture bool
default value = 1
When this is true, Panda will try to implement ColorScaleAttribs that affect alpha by creating an additional Texture layer over the geometry with a uniform alpha scale applied everywhere, if there is at least one available Texture slot available on the multitexture pipeline. Set this false to avoid this trickery, so that texturing is only enabled when the application specifically enables it. See also color-scale-via-lighting.

anim-blend-type enum
default value = normalized_linear
The default blend type to use for blending animations between frames, or between multiple animations. See interpolate-frames, and also PartBundle::set_anim_blend_flag() and PartBundle::set_frame_blend_flag().

assert-abort bool
default value = 0
Set this true to trigger a core dump and/or stack trace when the first assertion fails

asynchronous-clients bool
default value = 1

audio-active bool
default value = 1

audio-cache-limit int
default value = 15
The number of sounds in the cache.

audio-dls-file filename
default value =
Specifies a DLS file that defines an instrument set to load for MIDI file playback. If this is not specified, the sound interface will try to use the system default DLS file, if one is available; the likely success of this depends on the operating system.

audio-library-name string
default value = fmod_audio

audio-output-bits int
default value = 16

audio-output-channels int
default value = 2

audio-output-rate int
default value = 22050

audio-play-midi bool
default value = 1

audio-play-mp3 bool
default value = 1

audio-play-wave bool
default value = 1

audio-software-midi bool
default value = 1

audio-volume double
default value = 1

auto-break-cycles bool
default value = 1
Set this true to automatically detect and break reference-count cycles in the TransformState and RenderState caches. When this is false, you must explicitly call TransformState.clear_cache() from time to time to prevent gradual memory bloat.

auto-flip bool
default value = 0
This indicates the initial setting of the auto-flip flag. Set it true to cause render_frame() to flip all the windows before it returns (in single-threaded mode only), or false to wait until an explicit call to flip_frame() or the next render_frame(). Setting it true gets more immediate response time, while setting it false can get a better frame rate as more is achieved in parallel with the graphics card.

auto-generate-mipmaps bool
default value = 0
Set this true to use the hardware to generate mipmaps automatically in all cases, if supported. Set it false to generate mipmaps in software when possible. This is false by default because some drivers (Intel) seem to do a poor job of generating mipmaps when needed.

average-frame-rate-interval double
default value = 1
See ClockObject::set_average_frame_rate_interval().

back-buffers int
default value = 1
The default number of back buffers requested.

background-color double
default value = 0.41 0.41 0.41
Specifies the rgb(a) value of the default background color for a new window or offscreen buffer.

bam-endian enum
default value = littleendian
The default endianness to use for writing major numeric data tables to bam files. This does not affect all numbers written to bam files, only those for which the individual object was designed to support this flag. The default is "native"; you may set it to "littleendian" or "bigendian" to target a particular platform.

bam-texture-mode enum
default value = relative
Set this to specify how textures should be written into Bam files.See the panda source or documentation for available options.

bmp-bpp int
default value = 0
This controls how many bits per pixel are written out for BMP files. If this is zero, the default, the number of bits per pixel is based on the image.

bounds-type enum
default value = sphere
Specify the type of bounding volume that is created automatically by Panda to enclose geometry. Use 'sphere' or 'box', or use 'best' to let Panda decide which is most appropriate.

cache-report bool
default value = 0

cache-report-interval double
default value = 5

clip-plane-cull bool
default value = 1
This is normally true; set it false to disable culling of objects that are completely behind one or more clip planes (primarily useful for debugging).

clock-degrade-factor double
default value = 1
In degrade clock mode, returns the ratio by which the performance is degraded. A value of 2.0 causes the clock to be slowed down by a factor of two (reducing performance to 1/2 what would be otherwise). See ClockObject::set_degrade_factor().

clock-frame-rate double
default value = 1
In non-real-time clock mode, sets the number of frames per second that we should appear to be running. In forced mode or limited mode, sets our target frame rate. In normal mode, this has no effect. See ClockObject::set_frame_rate().

clock-mode enum
default value = normal
Specifies the mode of the global clock. The default mode, normal, is a real-time clock; other modes allow non-real-time special effects like simulated reduced frame rate. See ClockObject::set_mode().

collect-tcp bool
default value = 0
Set this true to enable accumulation of several small consecutive TCP datagrams into one large datagram before sending it, to reduce overhead from the TCP/IP protocol. See Connection::set_collect_tcp() or SocketStream::set_collect_tcp().

collect-tcp-interval double
default value = 0.2

color-bits int
default value = 0
The minimum number of color buffer bits requested.

color-scale-via-lighting bool
default value = 1
When this is true, Panda will try to implement ColorAttribs and ColorScaleAttribs using the lighting interface, by creating a default material and/or an ambient light if necessary, even if lighting is ostensibly disabled. This avoids the need to munge the vertex data to change each vertex's color. Set this false to avoid this trickery, so that lighting is only enabled when the application specifically enables it. See also alpha-scale-via-texture.

compose-componentwise bool
default value = 1
Set this true to perform componentwise compose and invert operations when possible. If this is false, the compositions are always computed by matrix.

compress-chan-quality int
default value = 95
The quality level is an integer number that generally ranges between 0 and 100, where smaller numbers indicate greater compression at the cost of quality, and larger numbers indicate higher quality but less compression. Generally, 95 is the highest useful value; values between 95 and 100 produce substantially larger, but not substantially better, output files. This is akin to the JPEG compression level.

compress-channels bool
default value = 0
Set this true to enable lossy compression of animation channels when writing to the bam file. This serves to reduce the size of the bam file only; it does reduce the memory footprint of the channels when the bam file is loaded.

compressed-textures bool
default value = 0
Set this to true to compress textures as they are loaded into texture memory, if the driver supports this. Specifically, this changes the meaning of set_compression(Texture::CM_default) to Texture::CM_on.

connect-triangle-strips bool
default value = 1
Set this true to send a batch of triangle strips to the graphics card as one long triangle strip, connected by degenerate triangles, or false to send them as separate triangle strips with no degenerate triangles. On PC hardware, using one long triangle strip may help performance by reducing the number of separate graphics calls that have to be made.

coordinate-system enum
default value = zup_right
The default coordinate system to use throughout Panda for rendering, user input, and matrix operations, unless specified otherwise.

copy-texture-inverted bool
default value = 0
Set this true to indicate that the GSG in use will invert textures when it performs a framebuffer-to-texture copy operation, or false to indicate that it does the right thing. If this is not set, the default behavior is determined by the GSG's internal logic.

cull-bin list
gui-popup 60 unsorted (from /d/panda3d-a/built/etc/Confauto.prc)
Creates a new cull bin by name, with the specified properties. This is a string in three tokens, separated by whitespace: 'bin_name sort type'.

cursor-filename filename
default value =

cursor-hidden bool
default value = 0

dc-multiple-inheritance bool
default value = 1
Set this true to support multiple inheritance in the dc file. If this is false, the old way, multiple inheritance is not supported, but field numbers will be numbered sequentially, which may be required to support old code that assumed this.

dc-sort-inheritance-by-file bool
default value = 1
This is a temporary hack. This should be true if you are using version 1.42 of the otp_server.exe binary, which sorted inherited fields based on the order of the classes within the DC file, rather than based on the order in which the references are made within the class.

dc-virtual-inheritance bool
default value = 1
Set this true to support proper virtual inheritance in the dc file, so that diamond-of-death type constructs can be used. This also enables shadowing (overloading) of inherited method names from a base class.

decompressor-step-time double
default value = 0.1
Specifies the maximum amount of time that should be consumed by a single call to Decompressor::run().

default-converge double
default value = 25
The default convergence distance for stereo cameras.

default-far double
default value = 1000
The default far clipping distance for all cameras.

default-fov double
default value = 30
The default field of view in degrees for all cameras. This is defined as a min_fov; that is, it is the field-of-view for the smallest of the X and Y sizes of the window, which is usually the vertical field of view (windows are usually wider than they are tall). For a 4x3 window, 30 degrees vertical is roughly 40 degrees horizontal.

default-iod double
default value = 0.2
The default interocular distance for stereo cameras.

default-keystone double
default value = 0
The default keystone correction, as an x y pair, for all cameras.

default-lod-type enum
default value = pop
Set this to either 'pop' or 'fade' to determine the type of LODNode that is created by LODNode::make_default_lod().

default-model-extension string
default value =
This specifies the filename extension (with leading dot) that should be assumed if an attempt is made to load a filename that has no extension. This is primarily designed to support legacy code that used the now-deprecated implicit-extension feature of Panda's loader; new code should probably give the correct name for each model file they intend to load.

default-near double
default value = 1
The default near clipping distance for all cameras.

default_max_angular_dt double
default value = 0.0333333

default_max_linear_dt double
default value = 0.0333333

default_noise_force_seed int
default value = 665

default_terminal_velocity double
default value = 400

depth-bits int
default value = 0
The minimum number of depth buffer bits requested.

depth-offset-decals bool
default value = 0
Set this true to allow decals to be implemented via the advanced depth offset feature, if supported, instead of via the traditional (and slower) two-pass approach. This is false by default because it appears that many graphics drivers have issues with their depth offset implementation.

detect-graph-cycles bool
default value = 1
Set this true to attempt to detect cycles in the scene graph (e.g. a node which is its own parent) as soon as they are made. This has no effect in NDEBUG mode.

display-list-animation bool
default value = 0
Set this true to allow the use of OpenGL display lists for rendering animated geometry (when the geometry is animated by the hardware). This is not on by default because there appear to be some driver issues with this on my FireGL T2, but it should be perfectly doable in principle, and might get you a small performance boost.

display-lists bool
default value = 0
Set this true to allow the use of OpenGL display lists for rendering static geometry. On some systems, this can result in a performance improvement over vertex buffers alone; on other systems (particularly low-end systems) it makes little to no difference. On some systems, using display lists can actually reduce performance. This has no effect on DirectX rendering or on dynamic geometry (e.g. soft-skinned animation).

downloader-byte-rate int
default value = 3600
We'd like this to be about 1 second worth of download assuming a 28.8Kb connection (28.8Kb / 8 = 3600 bytes per second).

downloader-disk-write-frequency int
default value = 4
How often we write to disk is determined by this ratio which is relative to the downloader-byte-rate (e.g. if disk-write-ratio is 4, we will write every 4 seconds if the frequency is 0.2)

downloader-frequency double
default value = 0.2
Frequency of download chunk requests in seconds (or fractions of) (Estimated 200 msec round-trip to server).

downloader-timeout int
default value = 15

downloader-timeout-retries int
default value = 5

drive-forward-speed double
default value = 20

drive-horizontal-center double
default value = 0

drive-horizontal-dead-zone double
default value = 0.1

drive-horizontal-ramp-down-time double
default value = 0

drive-horizontal-ramp-up-time double
default value = 0

drive-reverse-speed double
default value = 10

drive-rotate-speed double
default value = 80

drive-vertical-center double
default value = 0

drive-vertical-dead-zone double
default value = 0.1

drive-vertical-ramp-down-time double
default value = 0

drive-vertical-ramp-up-time double
default value = 0

early-random-seed bool
default value = 0
Configure this true to compute the SSL random seed early on in the application (specifically, when the libpandaexpress library is loaded), or false to defer this until it is actually needed (which will be the first time you open an https connection or otherwise use encryption services). You can also call HTTPClient::initialize_ssl() to do this when you are ready. The issue is that on Windows, OpenSSL will attempt to randomize its seed by crawling through the entire heap of allocated memory, which can be extremely large in a Panda application, especially if you have already opened a window and started rendering; and so this can take as much as 30 seconds or more. For this reason it is best to initialize the random seed at startup, when the application is still very small.

egg-accept-errors bool
default value = 1
When this is true, certain kinds of recoverable errors (not syntax errors) in an egg file will be allowed and ignored when an egg file is loaded. When it is false, only perfectly pristine egg files may be loaded.

egg-alpha-mode enum
default value = blend
Specifies the alpha mode to apply when the alpha specification "on" appears in the egg file (or when a primitive is implicitly transparent, because of a <RGBA> that involves a non-unity alpha, or because of a four-channel texture.

egg-combine-geoms bool
default value = 0
Set this true to combine sibling GeomNodes into a single GeomNode, when possible. This usually shouldn't be necessary, since the egg loader does a pretty good job of combining these by itself.

egg-consider-fans bool
default value = 0
Set this true to enable the egg mesher to consider making triangle fans out of triangles that are connected at a common vertex. This may help if your scene involves lots of such connected triangles, but it can also make the overall stripping less effective (by interfering with triangle strips).

egg-coordinate-system enum
default value = default

egg-coplanar-threshold double
default value = 0.01
The numerical threshold below which polygons are considered to be coplanar. Determined empirically.

egg-emulate-bface bool
default value = 0
When this is true, the bface flag applied to a polygon will cause two different polygons to be created, back-to-back. When it is false, a single polygon will be created with the two_sided flag set on it.

egg-flat-shading bool
default value = 0
Set this true to allow the egg loader to create geometry with the ShadeModelAttrib::M_flat attribute set. It will do this only for geometry that has per-polygon normals and/or colors. This allows the egg loader to avoid duplicating vertices when they are shared between connect polygons with different normals or colors, but it prevents the flat-shaded geometry from being combined with any adjacent smooth-shaded geometry (for instance, as the result of a flatten_strong operation). It is false by default, since flat-shaded geometry is rare; but you may wish to set it true if your scene largely or entirely consists of flat-shaded polygons.

egg-flatten bool
default value = 1
This is normally true to flatten out useless nodes after loading an egg file. Set it false if you want to see the complete and true hierarchy as the egg loader created it (although the extra nodes may have a small impact on render performance).

egg-flatten-radius double
default value = 0
This specifies the minimum cull radius in the egg file. Nodes whose bounding volume is smaller than this radius will be flattened tighter than nodes larger than this radius, to reduce the node count even further. The idea is that small objects will not need to have their individual components culled separately, but large environments should. This allows the user to specify what should be considered "small". Set it to 0.0 to disable this feature.

egg-ignore-decals bool
default value = 0

egg-ignore-filters bool
default value = 0

egg-ignore-mipmaps bool
default value = 0

egg-load-classic-nurbs-curves bool
default value = 0
When this is true (and the above is also true), a <NurbsCurve> entry appearing in an egg file will load a ClassicNurbsCurve object instead of the default, a NurbsCurve object. This only makes a difference when the NURBS++ library is available, in which case the default, NurbsCurve, is actually a NurbsPPCurve object.

egg-load-old-curves bool
default value = 1
When this is true, a <NurbsCurve> entry appearing in an egg file will load as a NurbsCurve or ClassicNurbsCurve object (see below). When this is false, it will load a RopeNode instead, which uses the new NurbsCurveEvaluator interface.

egg-max-indices int
default value = 65535
Specifies the maximum number of vertex indices that will be added to any one GeomPrimitive by the egg loader.

egg-max-tfan-angle double
default value = 40
The maximum average angle per triangle to allow in a triangle fan. If triangles are larger than this--that is, more loosely packed--then we figure a triangle strip is likely to do a more effective job than a triangle fan, and the fan maker leaves it alone.

egg-max-vertices int
default value = 65535
Specifies the maximum number of vertices that will be added to any one GeomVertexData by the egg loader.

egg-mesh bool
default value = 1
Set this true to convert triangles and higher-order polygons into triangle strips and triangle fans when an egg file is loaded or converted to bam. Set this false just to triangulate everything into independent triangles.

egg-min-tfan-tris int
default value = 4
The minimum number of triangles that must be involved in order to generate a triangle fan. Fewer than this is just interrupting a triangle strip.

egg-normal-scale double
default value = 1

egg-path search-path
The search path along which only egg files are searched. Generally, you should use model-path instead of egg-path.

egg-retesselate-coplanar bool
default value = 0
If this is true, the egg loader may reverse the tesselation direction of a single pair of planar triangles that share the same properties, if that will help get a better triangle strip. In some rare cases, doing so can distort the UV's on a face; turning this off should eliminate that artifact (at the cost of less-effective triangle stripping).

egg-rigid-geometry bool
default value = 0
Set this true to create rigid pieces of an animated character as separate static nodes, or false to leave these in with the parent node as vertex-animated geometry. Setting this true means less geometry has to be vertex-animated, but there will tend to be more separate pieces.

egg-show-normals bool
default value = 0

egg-show-qsheets bool
default value = 0
Set this true to color each quadsheet a random color, so you can visually observe the quadsheet algorithm.

egg-show-quads bool
default value = 0
Set this true to color each detected quad a random color, so you can visually observe the algorithm that unifies pairs of triangles into quads (prior to generating triangle strips).

egg-show-tstrips bool
default value = 0
Set this true to color each triangle strip a random color, with the leading triangle a little bit darker, so you can visually observe the quality of the triangle stripping algorithm.

egg-subdivide-polys bool
default value = 1
This is obsolete. In the old Geom implementation, it used to be true to force higher-order polygons that were not otherwise meshed to be subdivided into triangles. In the new Geom implementation, this happens anyway.

egg-support-old-anims bool
default value = 1
Set this true to support loading of old character animation files, which had the convention that the order "phr" implied a reversed roll.

egg-suppress-hidden bool
default value = 0
When this is true, objects flagged as "hidden" with the visibility scalar are not created at all. When false, these objects are created, but initially stashed.

egg-unify bool
default value = 1
When this is true, then in addition to flattening the scene graph nodes, the egg loader will also as many Geoms as possible within a given node into a single Geom. This has theoretical performance benefits, especially on higher-end graphics cards, but it also slightly slows down egg loading.

egg-unroll-fans bool
default value = 1
Set this true to allow the egg loader to convert weak triangle fans--triangles that share the same vertex but aren't connected enough to justify making a triangle fan primitive from them--into a series of zig-zag triangles that can make a triangle strip that might connect better with its neighbors.

even-animation bool
default value = 0
When this is true, characters' vertices will be recomputed every frame, whether they need it or not. This will tend to balance out the frame rate so that it is more uniformly slow. The default is to compute vertices only when they need to be computed, which can lead to an uneven frame rate.

exclude-texture-scale list
This is a list of glob patterns for texture filenames (excluding the directory part of the filename, but including the extension); for instance, 'digits_*.png'. Any texture filenames that match one of these patterns will not be affected by max-texture-dimension or texture-scale.

expected-ssl-server list

extractor-step-time double
default value = 0.1
Specifies the maximum amount of time that should be consumed by a single call to Extractor::step().

fake-texture-image string
default value =
Set this to enable a speedy-load mode in which you don't care what the world looks like, you just want it to load in minimal time. This causes all texture loads via the TexturePool to use the same texture file, which will presumably only be loaded once.

fake-view-frustum-cull bool
default value = 0
Set this true to cause culling to be performed by rendering the object in red wireframe, rather than actually culling it. This helps make culling errors obvious.

fft-error-threshold double
default value = 0.2

fft-exponent double
default value = 4

fft-factor double
default value = 0.1

fft-offset double
default value = 0.001

flatten-collision-nodes bool
default value = 0
Set this true to allow NodePath::flatten_medium() and flatten_strong() to combine multiple CollisionNodes into a single CollisionNode--but only if they share the same name and collide masks. When false, CollisionNodes are never combined. This is false by default, since collision tests rely heavily on bounding volume tests to be efficient, and combining CollisionNodes is likely to merge bounding volumes inappropriately.

fmod-number-of-sound-channels int
default value = 128
Guarantee this many channels you will have with FMOD. AKA the max number of sounds you can play at one time.

fmod-use-surround-sound bool
default value = 0
Determines if an FMOD Flavor of PANDA use 5.1 Surround Sound or Not.

frame-rate-meter-layer-sort int
default value = 1000

frame-rate-meter-scale double
default value = 0.05

frame-rate-meter-side-margins double
default value = 0.5

frame-rate-meter-text-pattern string
default value = %0.1f fps

frame-rate-meter-update-interval double
default value = 1.5

framebuffer-alpha bool
default value = 1
True if FM_alpha should be added to the default framebuffer properties, which requests an alpha channel if possible.

framebuffer-depth bool
default value = 1
True if FM_depth should be added to the default framebuffer properties, which requests a depth buffer.

framebuffer-hardware bool
default value = 1
True if FM_hardware should be added to the default framebuffer properties, which requests a hardware-accelerated display.

framebuffer-mode string
default value =
No longer has any effect. Do not use.

framebuffer-multisample bool
default value = 0
True if FM_multisample should be added to the default framebuffer properties, which requests a multisample-capable display, if possible. This can be used to implement full-screen antialiasing.

framebuffer-software bool
default value = 0
True if FM_software should be added to the default framebuffer properties, which requests a software-only display.

framebuffer-stencil bool
default value = 0
True if FM_stencil should be added to the default framebuffer properties, which requests an stencil buffer if possible.

framebuffer-stereo bool
default value = 0
True if FM_stereo should be added to the default framebuffer properties, which requests a stereo-capable display, if supported by the graphics driver.

fullscreen bool
default value = 0

game-server-timeout-ms int
default value = 20000
This represents the amount of time to block waiting for the TCP connection to the game server. It is only used when the connection method is NSPR.

geom-cache-min-frames int
default value = 1
Specifies the minimum number of frames any one particular object will remain in the geom cache, even if geom-cache-size is exceeded.

geom-cache-size int
default value = 5000
Specifies the maximum number of entries in the cache for storing pre-processed data for rendering vertices. This limit is flexible, and may be temporarily exceeded if many different Geoms are pre-processed during the space of a single frame.

hardware-animated-vertices bool
default value = 0
Set this true to allow the transforming of soft-skinned animated vertices via hardware, if supported, or false always to perform the vertex animation via software within Panda. If you have a card that supports this, and your scene does not contain too many vertices already, this can provide a performance boost by offloading some work from your CPU onto your graphics card. It may also help by reducing the bandwidth necessary on your computer's bus. However, in some cases it may actually reduce performance.

hardware-point-sprites bool
default value = 1
Set this true to allow the use of hardware extensions when rendering perspective-scaled points and point sprites. When false, these large points are always simulated via quads computed in software, even if the hardware claims it can support them directly.

http-client-certificate-filename filename
default value =
This provides a default client certificate to offer up should an SSL server demand one. The file names a PEM-formatted file that includes a public and private key specification. A connection-specific certificate may also be specified at runtime on the HTTPClient object, but this will require having a different HTTPClient object for each differently-certificated connection.

http-client-certificate-passphrase string
default value =
This specifies the passphrase to use to decode the certificate named by http-client-certificate-filename.

http-connect-timeout double
default value = 10
This is the default amount of time to wait for a TCP/IP connection to be established, in seconds.

http-direct-hosts string
default value =
This specifies the default value for HTTPClient::set_direct_host_spec(). It is a semicolon-delimited list of host names that do not require a proxy. See set_direct_host_spec() for more information.

http-max-connect-count int
default value = 10
This is the maximum number of times to try reconnecting to the server on any one document attempt. This is just a failsafe to prevent the code from attempting runaway connections; this limit should never be reached in practice.

http-proxy string
default value =
This specifies the default value for HTTPClient::set_proxy_spec(). It is a semicolon-delimited list of proxies that we use to contact all HTTP hosts that don't specify otherwise. See set_proxy_spec() for more information.

http-proxy-tunnel bool
default value = 0
This specifies the default value for HTTPChannel::set_proxy_tunnel(). If this is true, we will tunnel through a proxy for all connections, instead of asking the proxy to serve documents normally.

http-proxy-username string
default value =
This specifies a default username:password to pass to the proxy.

http-timeout double
default value = 20
This is the default amount of time to wait for the HTTP server (or proxy) to finish sending its response to our request, in seconds. It starts counting after the TCP connection has been established (http_connect_timeout, above) and the request has been sent.

http-try-all-direct bool
default value = 1
This specifies the default value for HTTPClient::set_try_all_direct(). If this is true, a direct connection will always be attempted after an attempt to connect through a proxy fails.

http-username list
Adds one or more username/password pairs to all HTTP clients. The client will present this username/password when asked to authenticate a request for a particular server and/or realm. The username is of the form server:realm:username:password, where either or both of server and realm may be empty, or just realm:username:password or username:password. If the server or realm is empty, they will match anything.

icon-filename filename
default value =

img-header-type enum
default value = short
IMG format is just a sequential string of r, g, b bytes. However, it may or may not include a "header" which consists of the xsize and the ysize of the image, either as shorts or as longs. Specify that with this variable, either 'short', 'long', or 'none' for no header at all (in which case you should also set img-size).

img-size int
default value = 0
If an IMG file without a header is loaded (e.g. img-header-type is set to 'none', this specifies the fixed x y size of the image.

inactivity-timeout double
default value = 0

interpolate-frames bool
default value = 0
Set this true to interpolate character animations between frames, or false to hold each frame until the next one is ready. This can also be changed on a per-character basis with PartBundle::set_frame_blend_flag().

interrogatedb-path search-path
The search path for interrogate's *.in files.

interval-precision double
default value = 1000
Set this to the default value for set_precision() for each CMetaInterval created.

jpeg-quality int
default value = 95
Set this to the quality percentage for writing JPEG files. 95 is the highest useful value (values greater than 95 do not lead to significantly better quality, but do lead to significantly greater size).

keep-temporary-files bool
default value = 0
Set this true to keep around the temporary files from downloading, decompressing, and patching, or false (the default) to delete these. Mainly useful for debugging when the process goes wrong.

keep-texture-ram bool
default value = 0
Set this to true to retain the ram image for each texture after it has been prepared with the GSG. This will allow the texture to be prepared with multiple GSG's, or to be re-prepared later after it is explicitly released from the GSG, without having to reread the texture image from disk; but it will consume memory somewhat wastefully.

load-file-type list
egg pandaegg (from /d/panda3d-a/built/etc/Confauto.prc)
p3ptloader (from /d/panda3d-a/built/etc/Confauto.prc)
List the model loader modules that Panda will automatically import when a new, unknown model type is loaded. This may be either the name of a module, or a space-separate list of filename extensions, followed by the name of the module.

loader-num-threads int
default value = 1
The number of threads that will be started by the Loader class to load models asynchronously. These threads will only be started if the asynchronous interface is used, and if threading support is compiled into Panda. The default is one thread, which allows models to be loaded one at a time in a single asychronous thread. You can set this higher, particularly if you have many CPU's available, to allow loading multiple models simultaneously.

lock-to-one-cpu bool
default value = 0
Set this to true if you want the entire process to use one CPU, even on multi-core and multi-CPU workstations. This is mainly a hack to solve a bug in which QueryPerformanceCounter returns inconsistent results on multi-core machines.

lod-fade-time double
default value = 0.5
The default amount of time (in seconds) over which a FadeLODNode transitions between its different levels.

m-dual bool
default value = 1
Set this false to disable TransparencyAttrib::M_dual altogether (and use M_alpha in its place).

m-dual-flash bool
default value = 0
Set this true to flash any objects that use M_dual, for debugging.

m-dual-opaque bool
default value = 1
Set this false to disable just the opaque part of M_dual.

m-dual-transparent bool
default value = 1
Set this false to disable just the transparent part of M_dual.

matrix-palette bool
default value = 0
Set this true to allow the use of the matrix palette when animating vertices in hardware. The matrix palette is not supported by all devices, but if it is, using it can allow animation of more sophisticated meshes in hardware, and it can also improve the performance of animating some simpler meshes. Without this option, certain meshes will have to be animated in software. However, this option is not enabled by default, because its support seems to be buggy in certain drivers (ATI FireGL T2 8.103 in particular.)

max-collect-indices int
default value = 65535
Specifies the maximum number of vertex indices that are allowed to be accumulated into any one GeomPrimitive as a result of collecting objects together during a flatten operation. This prevents the accidental generation of large index buffers from lots of smaller index buffers, while not imposing a limit on the original size of any one GeomPrimitive.

max-collect-vertices int
default value = 65535
Specifies the maximum number of vertices that are allowed to be accumulated into any one GeomVertexData structure as a result of collecting objects together during a flatten operation. This prevents the accidental generation of large vertex buffers from lots of smaller vertex buffers, while not imposing a limit on the original size of any one GeomVertexData structure.

max-compressed-vertex-data int
default value = 0
Specifies the maximum number of bytes of all vertex data that is allowed to remain compressed in system RAM at one time. If more than this number of bytes of vertices are created, the least-recently-used ones will be temporarily flushed to disk until they are needed. Set it to -1 for no limit.

max-disk-vertex-data int
default value = -1
Specifies the maximum number of bytes of vertex data that is allowed to be written to disk. Set it to -1 for no limit.

max-dt double
default value = -1
Sets a limit on the value returned by ClockObject::get_dt(). If this value is less than zero, no limit is imposed; otherwise, this is the maximum value that will ever be returned by get_dt(), regardless of how much time has actually elapsed between frames. See ClockObject::set_dt().

max-independent-vertex-data int
default value = -1
Specifies the maximum number of bytes of all vertex data that is independent of the paging system. This is an initial buffer before max-ram-vertex-data, specifically designed for vertex datas that are dynamic in nature and may change size or be created and destroyed frequently.

max-lag double
default value = 0
This represents the time in seconds by which to artificially lag inbound messages. It is useful to test a game's tolerance of network latency.

max-occlusion-vertices int
default value = 3000
The maximum number of vertices that may be included in a PandaNode and its descendents in order to perform an occlusion query for it. Subgraphs whose total vertex count exceeds this number will be subdivided further before performing an occlusion test--the hope is that we can eventually get to a finer-grained answer. GeomNodes and Geoms will not be subdivided, regardless of this limit.

max-resident-vertex-data int
default value = -1
Specifies the maximum number of bytes of all vertex data that is allowed to remain resident in system RAM at one time. If more than this number of bytes of vertices are created, the least-recently-used ones will be temporarily compressed in system RAM until they are needed. Set it to -1 for no limit.

max-texture-dimension int
default value = -1
Set this to the maximum size a texture is allowed to be in either dimension. This is generally intended as a simple way to restrict texture sizes for limited graphics cards. When this is greater than zero, each texture image loaded from a file (but only those loaded from a file) will be automatically scaled down, if necessary, so that neither dimension is larger than this value.

min-lag double
default value = 0
This represents the time in seconds by which to artificially lag inbound messages. It is useful to test a game's tolerance of network latency.

min-occlusion-vertices int
default value = 300
The minimum number of vertices a PandaNode or Geom must contain in order to perform an occlusion query for it. Nodes and Geoms smaller than this will be rendered directly, without bothering with an occlusion query.

model-path search-path
$MAIN_DIR (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/.. (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/../models (from /d/panda3d-a/built/etc/Config.prc)
The default directories to search for all models and general files loaded into Panda.

multifile-encryption-iteration-count int
default value = 0
This is a special value of encryption-iteration-count used to encrypt subfiles within a multifile. It has a default value of 0 (just one application), on the assumption that the files from a multifile must be loaded quickly, without paying the cost of an expensive hash on each subfile in order to decrypt it.

multisamples int
default value = 0
The minimum number of samples requested.

no-unsupported-copy bool
default value = 0
Set this true to make an attempt to copy an unsupported type generate an assertion failure instead of just a warning (which can then be trapped with assert-abort).

notify-output filename
default value =
The filename to which to write all the output of notify

occlusion-depth-bits int
default value = 1
The minimum number of depth bits requested for the occlusion buffer.

occlusion-size int
default value = 256 256
Specify the x y size of the buffer used for occlusion testing.

paranoid-clock bool
default value = 0
Set this to true to double-check the results of the high-resolution clock against the system clock.

paranoid-compose bool
default value = 0
Set this true to double-check the componentwise transform compose (or invert) operation against the equivalent matrix-based operation. This has no effect if NDEBUG is defined.

paranoid-const bool
default value = 0
Set this true to double-check that nothing is inappropriately modifying the supposedly const structures like RenderState, RenderAttrib, TransformState, and RenderEffect. This has no effect if NDEBUG is defined.

paranoid-hpr-quat bool
default value = 0
Set this true to doublecheck the quaternion-hpr compose and decompose operations against the quaternion-matrix and matrix-hpr operations. This only has effect if NDEBUG is not defined.

particle-path search-path
The directories to search for particle files to be loaded.

patcher-buffer-size int
default value = 16384
Limits the size of the buffer used in a single call to Patcher::run(). Increasing this may help the Patcher perform more work before returning.

patchfile-buffer-size int
default value = 4096

patchfile-increment-size int
default value = 8

patchfile-window-size int
default value = 16

patchfile-zone-size int
default value = 10000

physics_manager_random_seed int
default value = 139

polylight-info bool
default value = 0
Set this true to view some info statements regarding the polylight. It is helpful for debugging.

prefer-parasite-buffer bool
default value = 1
Set this true to make GraphicsOutput::make_texture_buffer() try to create a ParasiteBuffer before it tries to create an offscreen buffer (assuming it could not create a direct render buffer for some reason). This may reduce your graphics card memory requirements by sharing memory with the framebuffer, but it can cause problems if the user subsequently resizes the window smaller than the buffer.

prefer-single-buffer bool
default value = 1
Set this true to make GraphicsOutput::make_render_texture() first try to create a single-buffered offscreen buffer, before falling back to a double-buffered one (or whatever kind the source window has). This is true by default to reduce waste of framebuffer memory, but you might get a performance benefit by setting it to false (since in that case the buffer can share a graphics context with the window).

prefer-texture-buffer bool
default value = 1
Set this true to make GraphicsOutput::make_texture_buffer() always try to create an offscreen buffer supporting render-to-texture, if the graphics card claims to be able to support this feature. If the graphics card cannot support this feature, this option is ignored. This is usually the fastest way to render to a texture, and it presumably does not consume any additional framebuffer memory over a copy-to-texture operation (since the texture and the buffer share the same memory).

preload-textures bool
default value = 1
When this is true, texture images are loaded from disk as soon as the Texture is created from the TexturePool. When this is false, the Texture is created immediately, but the image data is not loaded from disk until the Texture is actually rendered (or otherwise prepared) on the GSG. This can help reduce wasted memory from Textures that are created but never used to render.

premunge-data bool
default value = 1
Set this true to preconvert vertex data at model load time to match the data requirements of the current GSG. For instance, color columns are pre-converted to match OpenGL or DirectX encoding requirements, as appropriate. When this is false, the data will be munged at render time instead.

preserve-triangle-strips bool
default value = 0
Set this true to indicate a preference for keeping triangle strips when possible, instead of decomposing them into triangles. When this is true, flatten_strong and unify operations may be less effective at combining multiple Geoms together, but they will not implicitly decompose triangle strips.

project-invert-uvs bool
default value = 0
If this is true, the UV's generated by all ProjectionScreens are inverted top-to-bottom. This used to be required to compensate for buggy graphics drivers that rendered to a texture upside-down in this manner, but nowadays Panda should be able to autodetect these graphics drivers. If it fails to do this, you should probably set copy-texture-inverted instead, which is more general.

pstats-average-time double
default value = 3

pstats-history double
default value = 60

pstats-host string
default value = localhost

pstats-max-rate double
default value = 1000
The maximum number of packets per second, per thread, to send to the remote PStats server. A packet is defined as a single UDP packet, or each 1024 bytes of a TCP message.

pstats-mem-other bool
default value = 1
Set this true to collect memory categories smaller than 0.1% of the total into a single "Other" category, or false to show each nonzero memory category.

pstats-name string
default value = Panda Stats

pstats-port int
default value = 5185

pstats-scroll-mode bool
default value = 1

pstats-target-frame-rate double
default value = 30
Specify the target frame rate to highlight on the PStats graph. This frame rate is marked with a different-colored line; otherwise, this setting has no effect.

pstats-tcp-ratio double
default value = 0.01
This specifies the ratio of frame update messages that are eligible for UDP that are sent via TCP instead. It does not count messages that are too large for UDP and must be sent via TCP anyway. 1.0 means all messages are sent TCP; 0.0 means all are sent UDP.

pstats-threaded-write bool
default value = 1
Set this true to write to the PStats channel in a sub-thread, if threading is available. Can't think of any reason why you wouldn't want this set true, unless you suspect something is broken with the threaded network interfaces.

pstats-unused-states bool
default value = 0
Set this true to show the number of unused states in the pstats graph for TransformState and RenderState counts. This adds a bit of per-frame overhead to count these things up.

read-compressed-channels bool
default value = 1
Set this false to disable reading of compressed animation channels, even if the decompression code is available. The only reason you might want to do this would be to speed load time when you don't care about what the animation looks like.

red-blue-stereo bool
default value = 0
Set this true to create windows with red-blue stereo mode enabled by default, if the framebuffer does not support true stereo rendering.

red-blue-stereo-colors string
default value = red blue
This defines the color channels that are used for the left and right eye, respectively, for red-blue-stereo mode. This should be a two-word string, where each word is one of 'red', 'blue', 'green', or 'alpha'.

rescale-normals enum
default value = auto
Specifies the kind of RescaleNormalAttrib that should be created for the top of the scene graph. This can automatically ensure that your lighting normals are unit-length, which may be particularly necessary in the presence of scales in the scene graph. Turning it off ('none') may produce a small performance benefit.

respect-effective-normal bool
default value = 1
This should be true to support the effective_normal interface of polygons. Set it false to disable this feature, so that all collision solids (including polygons and planes) use their actual normal for intersection and physics tests.

respect-prev-transform bool
default value = 0
Set this true to have all CollisionTraversers in the world respect the previous frame's transform (position) for a given object when determining motion for collision tests. If this is false, you must explicitly enable motion detection for a particular traverser. It is false by default to force programmers to decide on a case-by-case basis whether they really need this feature.

retransform-sprites bool
default value = 1
To render sprite-based particle effects, Panda must convert the sprite points from object space into clip space, and compute the corners of the quads in clip space. When this variable is false, the resulting quads are then sent to the graphics hardware in clip space. When this is true, the quads are re-transformed back into the original object space, which is necessary in order for fog to work correctly on the sprites.

screenshot-extension string
default value = jpg
This specifies the default filename extension (and therefore the default image type) to be used for saving screenshots.

screenshot-filename string
default value = %~p-%a-%b-%d-%H-%M-%S-%Y-%~f.%~e
This specifies the filename pattern to be used to generate screenshots captured via save_screenshot_default(). See DisplayRegion::save_screenshot().

scroll-continued-delay double
default value = 0.1
This is the amount of time, in seconds, to delay between lines scrolled while the user is continuing to hold down the scrollbar button.

scroll-initial-delay double
default value = 0.3
This is the amount of time, in seconds, to delay after the user first clicks and holds on a scrollbar button before the scrolling continues automatically.

sgi-imagename string
default value =
This string is written to the header of an SGI (*.rgb) file. It seems to have documentation purposes only.

sgi-storage-type enum
default value = rle
Use either 'rle' or 'verbatim' to indicate how SGI (*.rgb) files are written.

show-occlusion bool
default value = 0
Set this true to visualize the efforts of the occlusion test.

show-transparency bool
default value = 0
Set this true to flash any objects that are rendered in some transparency mode. The color chosen is based on the particular transparency mode in effect. This only has effect when NDEBUG is not defined.

show-vertex-animation bool
default value = 0
Set this true to flash any objects whose vertices are animated by Panda on the CPU (flash red) or by hardware (flash blue). This only has effect when NDEBUG is not defined.

skel-sample-config-variable int
default value = 3

sleep-precision double
default value = 0.01
This is the accuracy within which we can expect select() to return precisely. That is, if we use select() to request a timeout of 1.0 seconds, we can expect to actually sleep for somewhere between 1.0 and 1.0 + sleep-precision seconds.

sound-path search-path
$MAIN_DIR (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/.. (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/../models (from /d/panda3d-a/built/etc/Config.prc)
The directories to search for sound and music files to be loaded.

ssl-certificates list

ssl-cipher-list string
default value = DEFAULT
This is the default value for HTTPClient::set_cipher_list().

state-cache bool
default value = 1
Set this true to enable the cache of RenderState objects, similar to the TransformState cache controlled via transform-cache.

stencil-bits int
default value = 0
The minimum number of stencil buffer bits requested.

support-fade-lod bool
default value = 1
Set this false to make FadeLOD nodes behave like regular LOD nodes (ignoring the fade time). This may be useful, for instance, to test the performance impact of using FadeLOD nodes.

support-render-texture bool
default value = 1
Set this true allow use of the render-to-a-texture feature, if it is supported by your graphics card. Without this enabled, offscreen renders will be copied to a texture instead of directly rendered there.

support-rescale-normal bool
default value = 1
Set this true allow use of the rescale-normal feature, if it is supported by your graphics card. This allows lighting normals to be uniformly counter-scaled, instead of re-normalized, in the presence of a uniform scale, which should in principle be a bit faster. This feature is only supported by the OpenGL API.

support-threads bool
default value = 1
Set this false to disallow the creation of threads using Panda's Thread interface, even if threading support is compiled in. This does not affect the operation of mutexes and other synchronization primitives, just the creation of threads.

sync-flip bool
default value = 1
Set this true to attempt to flip all windows at the same time, or false to flip each window as late as possible. Setting this false can improve parallelization. This is a temporary variable; it will later be replaced with a more explicit control over synchronizing window flip.

sync-video bool
default value = 1
Configure this true to request the rendering to sync to the video refresh, or false to let your frame rate go as high as it can, irrespective of the video refresh. Usually you want this true, but it may be useful to set it false during development for a cheesy estimate of scene complexity. Some drivers may ignore this request.

temp-hpr-fix bool
default value = 1
Set this true to compute hpr's correctly. Historically, Panda has applied these in the wrong order, and roll was backwards relative to the other two. Set this false if you need compatibility with Panda's old hpr calculations.

text-anisotropic-degree int
default value = 1
This is the default anisotropic-degree that is set on dynamic font textures. Setting this to a value greater than 1 can help smooth out the antialiasing for small letters.

text-default-font filename
default value =
This names a filename that will be loaded at startup time as the default font for any TextNode that does not specify a font otherwise. The default is to use a special font that is compiled into Panda, if available.

text-embed-graphic-key int
default value = 5
This is the decimal character number that, embedded in a string, is used to bracket the name of a model added to the TextPropertiesManager object, to embed an arbitrary graphic image within a paragraph.

text-encoding enum
default value = iso8859
Specifies how international characters are represented in strings of 8-byte characters presented to Panda. See TextEncoder::set_encoding().

text-flatten bool
default value = 1
Set this true to flatten text when it is generated, or false to keep it as a deep hierarchy. Unless you are debugging the text interface, it is almost always a good idea to leave this at its default, true.

text-hyphen-ratio double
default value = 0.7
If the rightmost whitespace character falls before this fraction of the line, hyphenate a word to the right of that if possible.

text-magfilter enum
default value = linear
The default texture magfilter type for dynamic text fonts

text-max-never-break int
default value = 3
If we have more than this number of text-never-break-before characters in a row, do not treat any of them as special and instead break the line wherever we can.

text-minfilter enum
default value = linear_mipmap_linear
The default texture minfilter type for dynamic text fonts

text-native-antialias bool
default value = 1

text-page-size int
default value = 256 256
This is the default size for new textures created for dynamic fonts.

text-pixels-per-unit double
default value = 30

text-point-size double
default value = 10

text-poly-margin double
default value = 0
This is the amount by which to make each glyph polygon larger than strictly necessary, in screen units that are added to each margin. Increasing this value will decrease the tendency for letters to get chopped off at the edges, but it will also increase the tendency for adjacent glyphs to bleed into each other (unless you also increase text-texture-margin).

text-pop-properties-key int
default value = 2
This is the decimal character number that undoes the effect of a previous appearance of text_push_properties_key.

text-push-properties-key int
default value = 1
This is the decimal character number that, embedded in a string, is used to bracket the name of a TextProperties structure added to the TextPropertiesManager object, to control the appearance of subsequent text.

text-render-mode enum
default value = texture
The default render mode for dynamic text fonts

text-scale-factor double
default value = 2

text-small-caps bool
default value = 0
This controls the default setting for TextNode::set_small_caps().

text-small-caps-scale double
default value = 0.8
This controls the default setting for TextNode::set_small_caps_scale().

text-soft-break-key int
default value = 4
This is similar to text-soft-hyphen-key, except that when it is used as a break point, no character is introduced in its place.

text-soft-hyphen-key int
default value = 3
This is the decimal character number that, embedded in a string, is identified as the soft-hyphen character.

text-tab-width double
default value = 5
This controls the default setting for TextNode::set_tab_width().

text-texture-margin int
default value = 2
This is the number of texels of empty space reserved around each glyph in the texture. Setting this value larger will decrease the tendency for adjacent glyphs to bleed into each other at small sizes, but it will increase amount of wasted texture memory.

text-wrap-mode enum
default value = border_color
The default wrap mode for dynamic text fonts

texture-filter list
Names one or more external libraries that should be loaded for the purposes of performing texture filtering. This variable may be repeated several times. As in load-display, the actual library filename is derived by prefixing 'lib' to the specified name.

texture-path search-path
$MAIN_DIR (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/.. (from /d/panda3d-a/built/etc/Config.prc)
$THIS_PRC_DIR/../models (from /d/panda3d-a/built/etc/Config.prc)
A special directory path to search for textures only. Textures are also searched for along the model-path, so the use of texture-path is only useful if you have special directories that only contain textures.

texture-scale double
default value = 1
This is a global scale factor that is applied to each texture as it is loaded from disk. For instance, a value of 0.5 will reduce each texture to one-half its size in each dimension. This scale factor is applied before textures-power-2 or max-texture-dimension.

textures-header-only bool
default value = 0
If this is true, texture images will not actually be loaded from disk, but the image header information will be consulted to verify number of channels and so forth. The texture images themselves will be generated in a default blue color.

textures-power-2 enum
default value = down
Specify whether textures should automatically be constrained to dimensions which are a power of 2 when they are loaded from disk. Set this to 'none' to disable this feature, or to 'down' or 'up' to scale down or up to the nearest power of 2, respectively. This only has effect on textures which are not already a power of 2.

textures-square enum
default value = none
Specify whether textures should automatically be constrained to a square aspect ratio when they are loaded from disk. Set this to 'none', 'down', or 'up'. See textures-power-2.

tga-colormap bool
default value = 0
Set this true to write colormapped TGA files.

tga-grayscale bool
default value = 0
Set this true to enable writing grayscale TGA files.

tga-rle bool
default value = 0
Set this true to enable RLE compression when writing TGA files.

thread-stack-size int
default value = 4194304
Specifies the minimum size, in bytes, of the stack that will be created for each newly-created thread. Not all thread implementations respect this value.

threading-model string
default value =
This is the default threading model to use for new windows. Use empty string for single-threaded, or something like "cull/draw" for a 3-stage pipeline. See GraphicsEngine::set_threading_model(). EXPERIMENTAL and incomplete, do not use this!

track-memory-usage bool
default value = 0
Set this to true to enable full-force tracking of C++ allocations and recordkeeping by type. It's quite expensive.

transform-cache bool
default value = 1
Set this true to enable the cache of TransformState objects. Using the cache saves time computing transforms and inverse transforms, but imposes some overhead for maintaining the cache itself.

unambiguous-graph bool
default value = 0
Set this true to make ambiguous path warning messages generate an assertion failure instead of just a warning (which can then be trapped with assert-abort).

undecorated bool
default value = 0

uniquify-matrix bool
default value = 0
Set this true to look up arbitarary 4x4 transform matrices in the cache, to ensure that two differently-computed transforms that happen to encode the same matrix (an unlikely occurrence) will be collapsed into a single pointer (a tiny benefit). We're usually better off not paying the cost of this comparison, and just assuming that any two differently-computed transforms are essentially different.

use-high-res-clock bool
default value = 1
Set this to false to avoid using the high-precision clock, even if it is available.

use-vfs bool
default value = 1
Set this true to use the VirtualFileSystem mechanism for loading models, etc. Since the VirtualFileSystem is now the de facto filesystem for Panda, you should always keep this true, since there is now code that assumes it to be true. This variable is now deprecated.

verify-intervals bool
default value = 0
Set this true to generate an assertion failure if interval functions are called out-of-order.

verify-lods bool
default value = 0
When this is true, LODNodes will test when they are rendered to ensure that each child's geometry fits entirely within the radius defined by its switch-out distance. When it is false, LODNodes may have any switch in and out distances, regardless of the actual size of their geometry. This test is only made in NDEBUG mode (the variable is ignored in a production build).

verify-ssl bool
default value = 1
Configure this true (the default) to insist on verifying all SSL (e.g. https) servers against a known certificate, or false to allow an unverified connection. This controls the default behavior; the specific behavior for a particular HTTPClient can be adjusted at runtime with set_verify_ssl().

vertex-arrays bool
default value = 1
Set this true to allow the use of vertex arrays for rendering OpenGL vertex data. This, or vertex buffers, is the normal way of issuing vertices ever since OpenGL 1.1, and you almost always want to have this set to true. However, some very buggy graphics drivers may have problems handling vertex arrays correctly, so if you are experiencing problems you might try setting this to false. If this is false, Panda will fall back to using immediate-mode commands like glVertex3f(), etc., to issue the vertices, which is potentially much slower than vertex arrays. Setting this false also disables vertex buffers, effectively ignoring the setting of the vertex-buffers variable (since vertex buffers are a special case of vertex arrays in OpenGL). This variable is normally not enabled in a production build. This has no effect on DirectX rendering.

vertex-buffers bool
default value = 1
Set this true to allow the use of vertex buffers (or buffer objects, as OpenGL dubs them) for rendering vertex data. This can greatly improve rendering performance on higher-end graphics cards, at the cost of some additional graphics memory (which might otherwise be used for textures or offscreen buffers). On lower-end graphics cards this will make little or no difference.

vertex-data-compression-level int
default value = 1
Specifies the zlib compression level to use when compressing vertex data. The number should be in the range 1 to 9, where larger values are slower but give better compression.

vertex-data-page-size int
default value = 512
The number of bytes to allocate at a time for vertex data. This also controls the page size that is compressed and written to disk when vertex data pages are evicted from memory.

vertex-data-small-size int
default value = 64
When a GeomVertexArrayData is this number of bytes or smaller, it is deemed too small to pay the overhead of paging it in and out, and it is permanently retained resident.

vertex-data-threaded-paging bool
default value = 1
When this is true (and Panda has been compiled with thread support) then a sub-thread will be spawned to evict vertex pages to disk and read them back again. When this is false, this work will be done in the main thread, which may introduce occasional random chugs in rendering.

vertex-save-file-directory filename
default value =
The directory in which the saved vertex data file is created for saving vertex buffers that have been evicted from RAM. If this is the empty string, or an invalid directory, a system default directory will be chosen.

vertex-save-file-prefix string
default value = p3d_vdata_
A prefix used to generate the filename for the saved vertex data file which is created for saving vertex buffers that have been evicted from RAM. A uniquifying sequence number and filename extension will be appended to this string.

vfs-case-sensitive bool
default value = 1
Set this true to make the VirtualFileSystem present the native OS-provided filesystem as if it were a case-sensitive file system, even if it is not (e.g. on Windows). This variable has no effect if the native filesystem is already case-sensitive, and it has no effect on mounted multifile systems, which are always case-sensitive.

vfs-implicit-pz bool
default value = 1
When this is true, the VirtualFileSystem will pretend a named file exists even if it doesn't, as long as a filename with the same name and the additional extension .pz does exist. In this case, the VirtualFileSystem will implicitly open the .pz file and decompress it on-the-fly.

view-frustum-cull bool
default value = 1
This is normally true; set it false to disable view-frustum culling (primarily useful for debugging).

win-origin int
default value =
This is the default position at which to open a new window. This replaces the deprecated win-origin-x and win-origin-y variables.

win-size int
default value = 640 480
This is the default size at which to open a new window. This replaces the deprecated win-width and win-height variables.

window-inverted bool
default value = 0
Set this true to create all windows with the inverted flag set, so that they will render upside-down and backwards. Normally this is useful only for debugging.

window-title string
default value = Panda

yield-timeslice bool
default value = 0
Set this true to yield the timeslice at the end of the frame to be more polite to other applications that are trying to run.

z-order enum
default value = normal


Panda3D Manual: Accessing Config Vars in a Program

Panda3D uses a configuration file named Config.prc. Panda supplies functions to easily read values out of Config.prc, and to alter their values in memory (the modified values are not written back out to disk). The ability to read an alter configuration settings programmatically has two major uses:

  1. Storing your own configuration data.
  2. Tweaking Panda's behavior.

By "storing your own configuration data," I mean that your game might have its own settings that need to be stored. Rather than writing your own configuration file parser, you might consider adding your configuration data to the panda configuration file instead.

Suppose hypothetically that you are writing an online game, and your online game connects to a server. You need a configuration file to tell you the name of the server. Open up the "Config.prc" file and add the following line at the end of the file.

my-game-server pandagame.com

Note that I invented the variable name "my-game-server" out of thin air, this variable is not recognized by panda in any way. Therefore, this line has no effect on panda whatsoever.

To manipulate this variable programmatically, use code not unlike the following, which creates an object of class ConfigVariableString and then manipulates it using the methods setValue and getValue:

from pandac.PandaModules import ConfigVariableString

mygameserver = ConfigVariableString("my-game-server","127.0.0.1")
print "Server specified in config file: ", mygameserver.getValue()

# allow the user to change servers on the command-line:
if (sys.argv[1]=="--server"): mygameserver.setValue(sys.argv[2])

print "Server that we will use: ", mygameserver.getValue()

The second parameter to the ConfigVariableString constructor is the default value that should be returned, in case the line "my-game-server" does not appear in any Config.prc file. There is also an optional third parameter, which is a description of the purpose of the variable; this string will be displayed when the user executes the command print cvMgr.

The types of configuration variable are:

ConfigVariableString
ConfigVariableInt
ConfigVariableBool
ConfigVariableDouble
ConfigVariableFilename
ConfigVariableList
ConfigVariableSearchPath

Most of these follow the same form as ConfigVariableString, above, except that the default value (and the parameter from setValue() and getValue()) is of the indicated type, rather than a string. The two exceptions are ConfigVariableList and ConfigVariableSearchPath; these types of variables do not accept a default value to the constructor, since the default value in both cases is always the empty list or search path.

To display the current value of a particular variable interactively (in this example, for a string-type variable), type the following:

print ConfigVariableString('my-game-server')

Panda3D will automatically load any *.prc files it finds in its standard config directory at startup. You can view a list of the files it has actually loaded with the following command:

print cpMgr

It is helpful to do this to ensure that you are editing the correct Config.prc file.

Sometimes, it is desirable to load an additional configuration file from disk, by giving an explicit filename. To do so, use "loadPrcFile". Note that Panda Filename Syntax uses a forward slash, even under Windows:

from pandac.PandaModules import loadPrcFile
loadPrcFile("config/Config.prc")

The filename you specify is searched for along the model-path, in the same way that an egg or bam file is searched for when you use loader.loadModel().

You can also use "loadPrcFileData" to load a string that you define in your Python code, as if it were the contents read from a disk file. The loadPrcFileData() call requires two parameters; the first parameter is an arbitrary string name to assign to this "file" (and it can be the empty string if you don't care), while the second parameter is the contents of the file itself. This second parameter should contain newlines between variable definitions if you want to set the value of more than one variable.

For example, let's say that panda's configuration file contains this line:

fullscreen 0

By default, panda programs will run in a window, not fullscreen. However, if you do this:

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "fullscreen 1")
import direct.directbase.DirectStart

Then by the time you load direct.directbase.DirectStart, you will have changed the fullscreen-flag to true, and your program will run full-screen. There are other ways to go to fullscreen, this is not necessarily the most straightforward, but it illustrates the point.

You can get a more complete list of available config variables at runtime (once you have imported DirectStart), with the Python command:

cvMgr.listVariables()

For a full documentation about Panda3D's configuration system, click here to view the original documentation file.


Panda3D Manual: Models and Actors

Geometry Basics

There are two classes in Panda3D for 3D Geometry: the 'Model', which is for non-animated geometry, and the 'Actor', which is for animated geometry.

Note that a geometry is only considered animated if it changes shape. So for example, a baseball wouldn't be considered animated: it may fly all over the place, but it stays a sphere. A baseball would be a model, not an actor.

Panda does not distinguish between big stuff and small stuff. For example, if a virtual world contains a coffee cup on a table, and the table is in the middle of a small island, then the coffee cup, the table, and the island are all models: each is a piece of static non-animated geometry.

Many engines provide tools to create terrain, and store that terrain into heightmap images. Panda3D can generate geometry for kind of heightmap terrains, refer to the Terrain section for more information. For many simple terrains, though, many people prefer to use a static model rather than a heightmap image.

The following sections of the manual assume that you have a valid egg file which has an animatable model, and some additional egg files containing animations. To learn how to convert a model into an egg file see the Model Export section.

Panda's Primary File Format

In Panda3D, geometry is generally stored in EGG files. An EGG file can contain:

  • A Model (static geometry)
  • An Actor (dynamic geometry)
  • An Animation (to be applied to an actor)
  • Both an Actor and an Animation.

EGG files are created by exporting models from 3D modeling programs like Maya, Max, or Blender. Currently, the support for Maya is very strong, since the developers of Panda3D mostly use Maya. The Max exporter is not very reliable right now. There is a third-party exporter for Blender, which is said to be quite good.

It is not recommended to pack both an actor and an animation into an EGG file: this tends to result in developer confusion.

Panda's Other File Format

The EGG file is optimized for debugging, not speed. The first time you load an EGG file, it loads slowly. However, the second time you use that same EGG file, it loads fast.

This is possible because Panda3D is quietly translating the EGG file into a performance-optimized form: the BAM file. It stores these BAM files in a directory called the model cache. When developing a game, this works great: the only time you notice a delay is if you just created the EGG file for the first time. Otherwise, it runs blazing fast.

However, there is one situation where this doesn't work so well: if you are shipping your game to a customer. You don't want your customer's first experience with your game to have delays caused by egg file loading. In that case, it may make sense to ship BAM files instead of EGG files to the user. To do this, you would use a tool like egg2bam or packpanda to convert your EGG files into BAM files manually.

CAUTION: A BAM file only functions with the one version of Panda3D that created it! By contrast, EGG files will work with any version of Panda3D. Because of this, is is NOT recommended that you use BAM files for your day-to-day development. They should only be used when shipping to customers.


Panda3D Manual: Loading Models

The Basics

Loading static geometry is done using loader.loadModel

m = loader.loadModel('mymodel.egg')

The path name specified in the loadModel can be an absolute path, or a relative path. Relative is recommended. If a relative path is used, then Panda3D will search its model path to find the egg file. The model path is controlled by panda's configuration file.

Inserting the Model into the Scene Graph

Do not forget that loading the model does not, by itself, cause the model to be visible. To cause Panda3D to render the model, you must insert it into the scene graph:

m.reparentTo(render)

You can read more about The Scene Graph.

Panda Filename Syntax

The path used in the loadModel call must abide by Panda3D's filename conventions. For easier portability, Panda3D uses Unix-style pathnames, even on Microsoft Windows. This means that the directory separator character is always a forward slash, not the Windows backslash character, and there is no leading drive letter prefix. (Instead of a leading drive letter, Panda uses an initial one-letter directory name to represent the drive.)

There is a fairly straightforward conversion from Windows filenames to panda filenames. Always be sure to use Panda filename syntax when using a Panda3D library function, or one of the panda utility programs:

# WRONG:
loader.loadModel("c:\\Program Files\\My Game\\Models\\Model1.egg")

# RIGHT:
loader.loadModel("/c/Program Files/My Game/Models/Model1.egg")

Panda uses the Filename class to store Panda-style filenames; many Panda functions expect a Filename object as a parameter. The Filename class also contains several useful methods for path manipulation and file access, as well as for converting between Windows-style filenames and Panda-style filenames; see the API reference for a more complete list.

To convert a Windows filename to a Panda pathname, use code similar to the following:

from pandac.PandaModules import Filename
winfile = "c:\\MyGame\\Model1.egg"
pandafile = Filename.fromOsSpecific(winfile)
print pandafile

To convert a Panda filename into a Windows filename, use code not unlike this:

from pandac.PandaModules import Filename
pandafile = Filename("/c/MyGame/Model1.egg")
winfile = pandafile.toOsSpecific()
print winfile

The Filename class can also be used in combination with python's built-in path manipulation mechanisms. Let's say, for instance, that you want to load a model, and the model is in the "model" directory that is in the same directory as the main program's "py" file. Here is how you would load the model:

import sys,os
from pandac.PandaModules import Filename

# get the location of the 'py' file I'm running:
mydir = os.path.abspath(sys.path[0])

# convert that to panda's unix-style notation
mydir = Filename.fromOsSpecific(mydir).getFullpath()

# now load the model
model = loader.loadModel(mydir + "/models/mymodel.egg")

You need to keep in mind that the standard python functions (like os.remove()) work with OS specific paths. So do not forget to convert your OS Generic paths back to OS Specific paths when using Python's built-in functions.


Panda3D Manual: Loading Actors and Animations

Actor Basics

The python class Actor is designed to hold an animatable model and a set of animations. Since the Actor class inherits from the NodePath class, all NodePath functions are applicable to actors.

Note, however, that Actor is a Python class that extends the C++ NodePath class. For the most part, you don't have to think about this: Actor inherits sensibly from NodePath and generally does what you expect. There are a few subtle oddities, though. When you attach an Actor into a scene graph, the low-level C++ Panda constructs only records the NodePath part of the Actor in the scene graph, which is fine as long as you also keep a pointer to the Actor instance in your Python objects. If you let the Actor destruct, however, its visible geometry will remain, but it will cease animating (because it is no longer an Actor). Also, even if you keep the Actor object around, if you retrieve a new pointer to the Actor from the scene graph (for instance, as returned by the collision system), you will get back just an ordinary NodePath, not an Actor.

The Actor interface provides a high-level interface on the low-level Panda constructs. In Panda, the low-level node that performs the animation is called Character. You can see the Character node in the scene graph when you call actor.ls().

Do not confuse the Actor class with the ActorNode class, which is used for physics. They are completely unrelated classes with similar names.

Using Actors

The Actor class must be imported before any loading or manipulation of actors.

from direct.actor.Actor import Actor

Once the module is loaded, the actor object must be constructed, and the model and animations must be loaded:

nodePath = Actor()
nodePath.loadModel(‘Model Path’)
nodePath.loadAnims({‘Arbitrary Name1’:’Animation Path 1’})
nodePath.loadAnims({‘Arbitrary Name2’:’Animation Path 2’})

Loading each animation requires a tuple: the name one is giving the animation and the path to the animation. This entire process can be shortened to a single command:

nodePath = Actor('Model Path', {
  'Animation Name 1':'Animation Path 1',
  'Animation Name 2':'Animation Path 2',
})

Animations may also be unloaded using the same tuple used in creating them.

nodePath.unloadAnims({'Animation Name':'Animation Path'})

Although this is a rarely-used technique, it is possible to assemble a character model out of several separate pieces (separate models). This is further explained in the section Multi-Part Actors.

Panda3D supports both skeletal animation and morph animations.

Panda Filename Syntax

The filenames used in the Actor constructor must follow Panda's filename conventions. See Loading Models for more information. Loading actors and animations utilizes the panda model path, the same as for static models.


Panda3D Manual: Actor Animations

Since the Actor class inherits from NodePath, everything that can be done to a NodePath, such as reparentTo() and setPos(), etc., may also be done to an Actor. In addition to the basic NodePath functionality, Actors have several additional methods to control animation. In order for Actors to animate, their pointer (variable) must be retained in memory. The following is only a brief introduction; see the API reference for a complete list.

Basic animation playing

Animations may either be played or looped. When an animation is played, the actor goes through the animation once. When an animation is looped, the animation will play continuously. There is no tweening done between the last and the first frame, so if an animation is going to be looped, it needs to be constructed with that thought in mind. Finally, animations may be stopped at any point. When an animation is stopped, the actor will stay in the position it stopped on.

actor.play('Animation Name')
actor.loop('Animation Name')
actor.stop()

You may use the pose() method to tell an actor to hold a particular frame of the animation. Frames are numbered beginning at 0.

actor.pose('Animation Name', FrameNumber)

Posing an actor to a frame doesn't automatically specify the start frame of the next starting animation. Instead, if you don't want to start at the first frame, you can specify these using the optional parameters fromFrame and toFrame to the methods play() and loop():

actor.play('Animation Name', fromFrame = 10)
actor.loop('Animation Name', fromFrame = 24, toFrame = 36)

However, the loop method does have another optional parameter called restart, which is 1 by default, meaning the animation will restart from the beginning. If you pass it 0 instead, then the animation will begin looping from the current frame:

actor.pose('Animation Name', 30)
actor.loop('Animation Name', restart = 0, fromFrame = 24, toFrame = 36)

You can get more information about an animation with these functions:

print actor.getNumFrames('Animation Name') #returns the total number of frames in the animation
print actor.getCurrentAnim() #returns a string containing the name of the current playing animation
print actor.getCurrentFrame('Animation Name') #returns the current frame of the animation.

AnimControl

AnimControl is a class that provides control over a certain animation. You don't need to use this but this could be useful if you want to have the animation control functions over a certain animation in a separate class. NOTE: prior to Panda3D version 1.4, there is a required second parameter to actor.getAnimControl, which is the part name, or the literal string "modelRoot" if you don't have a multipart actor.

myAnimControl=actor.getAnimControl('Animation Name') #get the AnimControl

myAnimControl.isPlaying() #returns a boolean whether the animation is playing or not
myAnimControl.getFrame() #returns the current frame number
myAnimControl #returns the speed of the animation, in frames per second
myAnimControl.getFullFframe() #returns a floating-point frame number exceeding the framecount. Not recommended.
myAnimControl.getFullFrame() #returns an integer frame number exceeding the framecount. Not recommended.
myAnimControl.getNextFrame() #returns the number of the next frame on the queue.
myAnimControl.getNumFrames() #returns the total number of frames
myAnimControl.getPlayRate() #returns the playrate. explained further below
myAnimControl.loop() #starts playing the animation in a loop
myAnimControl.play() #starts playing the animation
myAnimControl.pose(frame) #poses at frame frame
myAnimControl.setPlayRate(rate) #sets the playrate. explained further below
myAnimControl.stop() #stops the animation

Play rate

The animation play rate may be set to any floating point value, which can be used to speed up or slow down the animation. This is a scale factor on the base animation rate; 1.0 means to play the animation at its normal speed, while 2.0 plays it twice as fast, and 0.5 plays it at half speed. It is also possible to play an animation backwards by specifying a negative play rate, for instance -1.0.

actor.setPlayRate(newPlayRate, 'Animation Name')

Blending

Multiple different animations for an actor may be played at the same time, and the animations blended together at runtime. The net result is that, each frame, the actor ends up somewhere between the different poses it would be in for each contributing animation, if each animation were playing independently.

Note that in blend mode each contributing animation still affects the actor's entire body. If you want to play one animation on, say, the left arm, while a different animation is playing on the legs, then you need to use half-body animation, which is different from blending.

To use blending, you must first call enableBlend() to activate the blending mode and indicate your intention to play multiple animations at once. While the actor is in blend mode, playing a new animation does not automatically stop the previously playing animation. Also, while in blend mode, you must explicitly specify how much each animation contributes to the overall effect, with the setControlEffect() method (the default for each animation is 0.0, or no contribution). For example:

actor.enableBlend()
actor.setControlEffect('animation1', 0.2)
actor.setControlEffect('animation2', 0.8)
actor.loop('animation1')
actor.loop('animation2')

The above specifies that 20% of animation1 and 80% of animation2 will be visible on the character at the same time. Note that you still have to start both animations playing (and they can be playing from different frames or at different play rates). Starting or stopping an animation in blend mode does not change its control effect; you must set an animation's control effect to 0.0 if you don't want it to have any more affect on the actor.

When you call stop() in blend mode, you can stop a particular animation by name, if you want; or you can stop all of the animations by calling stop() with no parameters:

actor.stop('animation1')

Note that specifying an animation name to stop() is only meaningful when you are in blend mode. When not in blend mode, actor.stop() will always stop whatever animation is currently playing, regardless of the animation name you specify.

When you are done using blending and want to return to the normal mode of only playing one animation at a time, call disableBlend():

actor.disableBlend()

Half-body animation

If you want different parts of your actor to play seperate animations without blending them together you have to create subparts. Each of these can then play one animation without influencing the others. Call actor.makeSupart() with the desired name, a list of joints to be included and a list of joints to be excluded in the subpart. Inclusion / exclusion will descend attached joints. Exclude always overwrite include.

actor.makeSubpart("legs", ["Left Thigh", "Right Thigh"])
actor.makeSubpart("torso", ["Head"], ["Left Thigh", "Right Thigh"])

If you want to play an animation on a subpart make sure to pass the name.

actor.loop("walk", partName="legs")
actor.loop("reload", partName="torso")

Interpolation

Since Panda 1.3.0 intra-frame interpolation is supported. If you play an animation with only few frames per second you can see your model "jump" from one frame to the next. If you enable interpolation between frames, those "jumps" will be smoothed out. This allows smooth animations with framerates as low as one frame per second or even less. Intra-frame interpolation is disabled by default. To enable it just add the following lines to your code

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "interpolate-frames 1")

From the FAQ:

"Interpolate-frames flag gets set in the PartBundle at the time it is first created, and then baked into the bam cache.

Thenceforth, later changes to the interpolate-frames variable mean nothing. If you changed interpolate-frames flag, you will also need to empty your modelcache folder.

Actually, it is not recommended to use interpolate-frames; it is a global setting. It's better to achieve the same effect via actor.setBlend(frameBlend = True), which is a per-actor setting (and doesn't get baked into the model cache)."

Actor Intervals

Another way to play an animation on an actor is to use an ActorInterval, which gives you a lot more frame-by-frame control over the animation, and is particularly useful when building a complex script using Intervals. However, the ActorInterval interface is a little bit slower than the above interfaces at runtime, so you should prefer the more fundamental interfaces unless there is a good reason to use ActorInterval.

The Task manager

On a more complex program, you may find that Animations can not be loaded from any point in your program. In any application there needs to be exactly one call to run(), and it should be the last thing you do after starting up. This starts the task manager. Think of this as the main loop of the application: your startup procedure is to set up your loading screen, start any initial tasks or intervals, hang any initial messenger hooks, and then go get lost in run(). Thereafter everything must run in a task, in an interval, or is a response to a message. This is true for both animations and sound.


Panda3D Manual: Multi-Part Actors

It is possible to assemble a character model out of several separate pieces (separate models). If this is the case, then the pieces must contain bones that can be attached to each other. For example, if you have a robot consisting of a set of legs and a swappable torso, and if you want to glue them together at the waist, then the legs model should contain a bone "waist", and the torso model should also contain a bone "waist". You can then attach them together:

nodePath = Actor.Actor({
    'legs':'RobotLegs.egg',
    'torso':'RobotTorso1.egg',
  },{'legs':{'dance':'RobotLegs-Dance.egg'},
     'torso':{'dance':'RobotTorso1-Dance.egg'}})
nodePath.attach('torso','legs','waist')

Multi-part actors are fairly complicated. Each part is loaded from a separate egg file, and each part has its own set of animations that are applied to it. For each animation you want to play, you will need to have a corresponding egg file for each part.

In the Actor constructor, you specify the list of model files with a dictionary of part name to egg file, as shown above. The list of animation files is more complicated; it's a dictionary of dictionaries. The outer dictionary maps part names to animation dictionaries. Each animation dictionary maps animation name to animation egg file for the corresponding part.

Here's another example:

myactor = Actor(

    # part dictionary
    {"head":"char/dogMM/dogMM_Shorts-head-mod",
     "torso":"char/dogMM/dogMM_Shorts-torso-mod",
     "legs":"char/dogMM/dogMM_Shorts-legs-mod"},

    # dictionary of anim dictionaries
    {"head":{"walk":"char/dogMM/dogMM_Shorts-head-walk",
             "run":"char/dogMM/dogMM_Shorts-head-run"},
     "torso":{"walk":"char/dogMM/dogMM_Shorts-torso-walk",
              "run":"char/dogMM/dogMM_Shorts-torso-run"},
     "legs":{"walk":"char/dogMM/dogMM_Shorts-legs-walk",
             "run":"char/dogMM/dogMM_Shorts-legs-run"}
     })

In addition multipart actor parts need to be connected together in a meaningful fashion:

myactor.attach("head", "torso", "joint-head")
myactor.attach("torso", "legs", "joint-hips")

The attach() call names two parts, and reparents the part named by the first parameter onto the part named by the second parameter, at the node named by the third parameter, which should be an exposed joint (that is, a joint in the part named by the second parameter). You must have already exposed the joint before this call, either with the egg-optchar command line tool, or by calling actor.exposeJoint() at runtime.

After calling attach(), the stacked part will inherit the animation from the attachment joint, by virtue of the scene graph relationship.

Animation

You can animate the parts as normal animations, but you need to supply the partname, like this:

myactor.play('Animation Name', 'Part Name')

If you want to use AnimControl, as explained in this section, you must supply the partname as second parameter in getAnimControl():

#you can see you just need to call
#  actor.getAnimControl('Animation Name','Part Name')
#to get access to the AnimControl of that part.

ac=actor.getAnimControl('Animation Name','Part Name')
ac.isPlaying() #returns a boolean whether the animation is playing or not
ac.getFrame() #returns the current frame number
ac.getFrameRate() #returns the speed of the animation, in frames per second
ac.getFullFframe() #returns a floating-point frame number. Note: This number keeps counting and may exceed the total number of frames.
ac.getFullFrame() #returns an integer frame number. Note: This number keeps counting and may exceed the total number of frames.
ac.getNextFrame() #returns the number of the next frame on the queue.
ac.getNumFrames() #returns the total number of frames
ac.getPlayRate() #returns the playrate. explained further below
ac.loop() #starts playing the animation in a loop
ac.play() #starts playing the animation
ac.pose(frame) #poses at frame frame
ac.setPlayRate(rate) #sets the playrate.  explained further below
ac.stop() #stops the animation


Panda3D Manual: Attaching an Object to a Joint

If an actor has a skeleton, then it is possible to locate one of the joints, and attach an object to that joint:

myNodePath = actorNodePath.exposeJoint(None,"modelRoot","Joint Name")

This function returns a nodepath which is attached to the joint. By reparenting any object to this nodepath, you can cause it to follow the movement of the joint.

The string "modelRoot" represents the name of the model node - the string "modelRoot" is usually the correct value.

The string "Joint Name" represents the name of the joint. Typically it would be something like "Femur", or "Neck", or "L Finger1". This is usually set inside the modeling package. For example, in MAX, each object in the scene has a name, including the bones. If necessary, you can determine the joint names by scanning the egg file for strings like <Joint> Femur.


Panda3D Manual: Controlling a Joint Procedurally

Sometimes one wishes to procedurally take control of a model's joint. For example, if you wish to force a character model's eyes to follow the mouse, you will need to procedurally take control of the neck and head. To achieve this, use controlJoint.

myNodePath = actor.controlJoint(None,"modelRoot","Joint Name")

This creates a dummy node. Every frame, the transform is copied from the dummy node into the joint. By setting the transform of the dummy node, you can control the joint. Normally, one would want to use setHpr to rotate the joint without moving it. The dummy node is initialized in such a way that the joint is in its default location, the one specified in the model's egg file.

You must store a local (not global) transform in the dummy node. In other words, the transform is relative to the joint's parent bone. If you are controlling the forearm of a model, for instance, the transform will be relative to the upperarm.

The string "modelRoot" represents the name of the model node - the string "modelRoot" is usually the correct value.

The string "Joint Name" represents the name of the joint. Typically it would be something like "Femur", or "Neck", or "L Finger1". This is usually set inside the modeling package. For example, in MAX, each object in the scene has a name, including the bones. If necessary, you can determine the joint names by scanning the egg file for strings like <Joint> Femur. You can also use the call actor.listJoints() to show the complete hierarchy of joints.

Cautions and limitations

Prior to Panda3D version 1.5, there were several important limitations to controlJoint(). These have been lifted as of Panda3D version 1.5.

  • In previous versions of Panda, controlJoint only worked when an animation is playing on the joint. This is no longer true; controlJoint now takes effect whether an animation is playing or not.
  • It used to be important to make all of your controlJoint() calls for a particular model before you made the first call to play(), loop(), or pose(). This is no longer necessary; you may call controlJoint() at any time.
  • In previous versions of Panda, controlJoint could not be undone. Beginning in Panda3D version 1.5, you may call releaseJoint("modelRoot", "Joint Name") to undo a previous call to controlJoint().


Panda3D Manual: Render Attributes

Render Attributes Basics

After loading a model, you can alter its appearance by altering its attributes. For example, you can apply a color to the model, you can illuminate it with lights, you can cause it to be obscured by fog, you can make it partially transparent, and so forth. All of these are called render attributes.

Collectively, all the attributes of an object are called the object's render state, or sometimes just the object's state.

Propagation of Attributes

Attributes can be stored on any node of the scene graph; setting an attribute on a node automatically applies it to that node as well as to all of the children of the node (unless an override is in effect, but that's a more advanced topic).

It is possible to create these attributes and assign them to a node directly:

nodePath.node().setAttrib(attributeObject)

But in many cases, especially with the most commonly-modified attributes, you don't need to create the attributes directly as there is a convenience function on NodePath (e.g. nodePath.setFog()) that manages the creation of the attributes for you; there will also be a corresponding clear function on NodePath to remove the attribute (nodePath.clearFog()).

Render Attribute Priorities

Every attribute has a priority. By default, that priority is zero. That priority value affects the inheritance of attributes.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: List of All Attributes

List of All Attributes

The following is a concise list of all the RenderAttribs supported by Panda3D. Additional documentation can be found by clicking on the name of the RenderAttrib.

  • AlphaTestAttrib: Hides part of the model, based on the texture's alpha channel.
  • AntialiasAttrib: Controls full-screen antialiasing and polygon-edge antialiasing.
  • AudioVolumeAttrib: Applies a scale to audio volume for positional sounds.
  • AuxBitplaneAttrib: Causes shader generator to produce extra data.
  • ClipPlaneAttrib: Slices off a piece of the model, using a clipping plane.
  • ColorAttrib: Tints the model. Only works if the model is not illuminated.
  • ColorBlendAttrib: This specifies how colors are blended into the frame buffer, for special effects.
  • ColorScaleAttrib: Modulates vertex colors with a flat color.
  • ColorWriteAttrib: Causes the model to not affect the R, G, B, or A channel of the framebuffer.
  • CullBinAttrib: Controls the order in which Panda renders geometry.
  • CullFaceAttrib: Causes backfaces or frontfaces of the model to be visible.
  • DepthOffsetAttrib: Causes the Z-buffer to treat the object as if it were closer or farther.
  • DepthTestAttrib: Alters the way the Z-buffer affects the model.
  • DepthWriteAttrib: Controls whether or not the model affects the Z-buffer.
  • DrawMaskAttrib: Controls which cameras can see which objects.
  • FogAttrib: Causes the model to be obscured by fog if it is far from the camera.
  • LightAttrib: Causes the model to be illuminated by certain lights.
  • LightRampAttrib: Enables HDR tone mapping or cartoon shading.
  • MaterialAttrib: Changes the way the model reflects light.
  • RenderModeAttrib: Used to enable wireframe rendering.
  • RescaleNormalAttrib: Can disable the automatic correction of non-unit normals.
  • ShadeModelAttrib: Can cause the model to appear faceted instead of smooth.
  • ShaderAttrib: Gives almost unlimited control, but difficult to use.
  • StencilAttrib: Causes the model to affect the stencil buffer, or be affected by the stencil buffer.
  • TexGenAttrib: Causes the system to synthesize texture coordinates for the model.
  • TexMatrixAttrib: Alters the existing texture coordinates.
  • TextureAttrib: Applies a texture map to the model.
  • TransparencyAttrib: Causes the model to be partially transparent.

Undocumented

Unfortunately, the Panda3D manual is still a work in progress: there are many aspects of it that are not fully documented yet. These attributes are not yet documented:

AudioVolumeAttrib, CullBinAttrib, DepthOffsetAttrib, DrawMaskAttrib, RenderModeAttrib, RescaleNormalAttrib, ShadeModelAttrib

However, although the manual does not document these classes, the Reference documentation does.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Lighting

Lighting Basics

In the real world, if you put a light in a room, objects in that room are illuminated. For example, if you put a table lamp in your living room, that lamp automatically illuminates your sofa and your chair. In a 3D engine like Panda3D, lights don't illuminate things automatically. Instead, you must tell the chair and the sofa to be illuminated by the lamp.

So to reiterate, lighting a scene in Panda3D consists of two steps:
1. Creating lights, and positioning them within the scene.
2. Telling the other objects to be illuminated by the lights.

Panda3D defines four different kinds of light objects: point, directional, ambient, and spotlight. Each of these is a node that should be attached somewhere within the scene graph. Like anything you put into the scene, lights have a position and orientation, which is determined by the basic scene graph operations like setPos(), setHpr(), etc. The lookAt() method is particularly useful for pointing spotlights and directional lights at a particular object. The following code inserts a directional light into the scene:

dlight = DirectionalLight('my dlight')
dlnp = render.attachNewNode(dlight)

Note that, unlike a real, physical light bulb, the light objects are not themselves directly visible. Although you can't see a Panda light itself, you can see the effect it has on the geometry around it. If you want to make a light visible, one simple trick is to load a simple model (like a sphere) and parent it directly to the light itself.

Creating the light and putting it in the scene graph doesn't, by itself, have any visible effect. Your next step is to tell some object to be illuminated by the light. To do this, use the nodePath.setLight() method, which turns on the light for the indicated NodePath and everything below it in the scene graph.

In the simplest case, you want all of your lights to illuminate everything they can, so you turn them on at render, the top of the scene graph:

render.setLight(plnp)

You can remove the light setting from render:

render.clearLight(plnp)

You could also apply the setLight() call to a sub-node in the scene graph, so that a given light only affects a particular object or group of objects:

sofa.setLight(plnp)

Note that there are two (or more) different NodePaths involved here: the NodePath of the light itself, which defines the position and/or orientation of the light, and the NodePath(s) on which you call setLight(), which determines what subset of the scene graph the light illuminates. There's no requirement for these two NodePaths to be related in any way.

Lots of Lights: Performance Implications

Each light slows down rendering a little. Using a half-dozen lights to illuminate an object is no problem at all. However, if you were to use a hundred lights to illuminate an object, that object would render slowly.

Because of this, when you create a big virtual world, you need to pick and choose which lights affect which objects. For example, if you had a dungeon containing a hundred torches, it would not be practical to tell every object to be illuminated by every torch. Instead, for each object in the dungeon, you would want to search for the three or four nearest torches, and tell the object to be illuminated only by those three or four torches.

When per-pixel lighting is enabled, lights are considerably more costly.

Colored Lights

All lights have a color, which is specified by light.setColor(VBase4(r, g, b, a)). The default color is full white: setColor(VBase4(1, 1, 1, 1)). The alpha component is largely irrelevant.

Note: The R, G, B values can be larger than 1, if you want brighter lights! However, you can't use lighting to make a model brighter than its texture color.

Point Lights

Point lights are the easiest kind of light to understand: a point light simulates a light originating from a single point in space and shining in all directions, like a very tiny light bulb. A point light's position is important, but its orientation doesn't matter.

plight = PointLight('plight')
plight.setColor(VBase4(0.2, 0.2, 0.2, 1))
plnp = render.attachNewNode(plight)
plnp.setPos(10, 20, 0)
render.setLight(plnp)

Attenuation

You can set the attenuation coefficients, which causes the light to drop off gradually with distance. There are three attenuation coefficients: A, B, C.

plight.setAttenuation(Point3(A, B, C))

Setting coefficients A and B to 0 and C between 0 and 1 works well.

plight.setAttenuation(Point3(0, 0, 0.5))

Directional Lights

A directional light is an infinite wave of light, always in the same direction, like sunlight. A directional light's position doesn't matter, but its orientation is important. The default directional light is shining down the forward (+Y) axis; you can use nodePath.setHpr() or nodePath.lookAt() to rotate it to face in a different direction.

dlight = DirectionalLight('dlight')
dlight.setColor(VBase4(0.8, 0.8, 0.5, 1))
dlnp = render.attachNewNode(dlight)
dlnp.setHpr(0, -60, 0)
render.setLight(dlnp)

Ambient Lights

An ambient light is used to fill in the shadows on the dark side of an object, so it doesn't look completely black. The light from an ambient light is uniformly distributed everywhere in the world, so the ambient light's position and orientation are irrelevant.

Usually you don't want to create an ambient light without also creating one of the other kinds of lights, since an object illuminated solely by ambient light will be completely flat shaded and you won't be able to see any of its details. Typically, ambient lights are given a fairly dark gray color, so they don't overpower the other lights in the scene.

alight = AmbientLight('alight')
alight.setColor(VBase4(0.2, 0.2, 0.2, 1))
alnp = render.attachNewNode(alight)
render.setLight(alnp)

Spotlights

Spotlights represent the most sophisticated kind of light. A spotlight has both a point and a direction, and a field-of-view. In fact, a spotlight contains a lens, just like a camera does; the lens should be a PerspectiveLens and is used to define the area of effect of the light (the light illuminates everything within the field of view of the lens).

Note that the English word "spotlight" is one word, as opposed to the other kinds of lights, which are two words. Thus, the class name is correctly spelled "Spotlight", not "SpotLight".

slight = Spotlight('slight')
slight.setColor(VBase4(1, 1, 1, 1))
lens = PerspectiveLens()
slight.setLens(lens)
slnp = render.attachNewNode(slight)
slnp.setPos(10, 20, 0)
slnp.lookAt(myObject)
render.setLight(slnp)

Putting it all Together

Here is an example of lighting. There are an ambient light and two directional lights lighting the scene, and a green ambient light that only affects one of the pandas.

import direct.directbase.DirectStart
from pandac.PandaModules import *

# Put two pandas in the scene, panda x and panda y.
x= loader.loadModel("panda")
x.reparentTo(render)
x.setPos(10,0,-6)

y= loader.loadModel("panda")
y.reparentTo(render)
y.setPos(-10,0,-6)

# Position the camera to view the two pandas.
base.trackball.node().setPos(0, 60, 0)

# Now create some lights to apply to everything in the scene.

# Create Ambient Light
ambientLight = AmbientLight( 'ambientLight' )
ambientLight.setColor( Vec4( 0.1, 0.1, 0.1, 1 ) )
ambientLightNP = render.attachNewNode( ambientLight.upcastToPandaNode() )
render.setLight(ambientLightNP)

# Directional light 01
directionalLight = DirectionalLight( "directionalLight" )
directionalLight.setColor( Vec4( 0.8, 0.2, 0.2, 1 ) )
directionalLightNP = render.attachNewNode( directionalLight.upcastToPandaNode() )
# This light is facing backwards, towards the camera.
directionalLightNP.setHpr(180, -20, 0)
render.setLight(directionalLightNP)

# Directional light 02
directionalLight = DirectionalLight( "directionalLight" )
directionalLight.setColor( Vec4( 0.2, 0.2, 0.8, 1 ) )
directionalLightNP = render.attachNewNode( directionalLight.upcastToPandaNode() )
# This light is facing forwards, away from the camera.
directionalLightNP.setHpr(0, -20, 0)
render.setLight(directionalLightNP)

# Now attach a green light only to object x.
ambient = AmbientLight('ambient')
ambient.setColor(Vec4(.5,1,.5,1))
ambientNP = x.attachNewNode(ambient.upcastToPandaNode())

# If we did not call setLightOff() first, the green light would add to
# the total set of lights on this object.  Since we do call
# setLightOff(), we are turning off all the other lights on this
# object first, and then turning on only the green light.
x.setLightOff()
x.setLight(ambientNP)

#run the example
run()


Panda3D Manual: Materials

Materials

Materials affect how the surfaces of models appear when lights are enabled in Panda. These have various effects such as how shiny an object appears, the brightness of it's colors etc. Material properties are combined with textures and lighting to get the final look of an object.

It must be emphasized that materials only work when lights are applied to an object. Otherwise, materials have no effect.

Explanation of Lighting

When light strikes a 3D model, light reflects off the model. If there were no light reflecting off the model, the model would appear pitch black. The light reflecting off the model is what causes it to have a non-black color onscreen. In the real world, light is incredibly complicated --- so complicated, that it is infeasible to do realistic calculations. Instead, Panda3D leaves it in your hands, giving you some basic tools to express how much light you want reflecting from each surface.

The tools provided are lights and materials. Lights are used to express how much light is striking the model. Materials are used to express how much of the light striking the model is reflected.

Panda3D separates the light striking the model into two general categories: nondirectional, and directional. Directional light is light that comes straight from a particular lamp. Because we know where it's coming from, we also know what direction it is coming from. Nondirectional light is light that maybe came from somewhere, bounced around a bit, and then eventually hit the model. Because we don't know exactly where it came from, we don't know what direction it is coming from. Panda3D handles nondirectional and directional light separately.

There are four kinds of lights in Panda3D: ambient, point, diffuse, and directional. The ambient light only creates nondirectional light. The other three create directional light.

When light strikes the surface of the model, it is the Material that governs how much of it reflects. The Material consists of four values:

  • Ambient Scattering: governs how much of the nondirectional light is reflected. Nondirectional light is always assumed to come from all directions, and it always reflects in all directions equally.
  • Diffuse Scattering: governs how much of the directional light is scattered. Scattering means that the light may have arrived from a particular direction, but it bounces off the model in all directions. Scattering looks like light hitting a painted white wall.
  • Specular Reflection: governs how much of the directional light is reflected. Specular reflection looks like light hitting a shiny piece of plastic: you can vaguely see a reflection of the lamp in the plastic, though the reflection just looks like a bright spot.
  • Emissivity: governs how much light the surface produces itself, for glowing surfaces like neon or glow sticks.

Default Behavior and Explicit Behavior

If the model does not have an explicit material, does not have a flat color, and does not have vertex colors, the behavior is this:

  1. All nondirectional light is reflected without being tinted.
  2. All directional light is scattered without being tinted.
  3. No specular reflection occurs.
  4. No emissivity occurs.

If the model does not have an explicit material, but it does have a flat color or a vertex color, the behavior is this:

  1. All nondirectional light is reflected after being modulated by the model's color.
  2. All directional light is scattered after being modulated by the model's color.
  3. No specular reflection occurs.
  4. No emissivity occurs.

When you set an explicit material on an object, the behavior is as follows:

  1. All nondirectional light is reflected after being modulated by the explicit ambient color.
  2. All directional light is scattered after being modulated by the explicit diffuse color.
  3. All directional light is reflected specularly after being modulated by the explicit specular color.
  4. The explicit emissive color is added to the light.

It is possible to mix-and-match explicit with default behavior. For example, you can specify an explicit specular color, but not specify an explicit ambient, diffuse, or emissive color. If you do that, the behavior would be:

  1. All nondirectional light is reflected after being modulated by the model's color.
  2. All directional light is scattered after being modulated by the model's color.
  3. All directional light is reflected specularly after being modulated by the explicit specular color.
  4. No emissivity occurs.

Creating and Using Materials

To use explicit materials, import the Materials module when you first begin your script. Then creating Materials is a matter of creating instances of the Material class and setting the relevant properties:

from pandac.PandaModules import Material
from pandac.PandaModules import VBase4
#VBase4 has to be imported in order to set colors in the methods

myMaterial = Material()
myMaterial.setShininess(5.0) #Make this material shiny
myMaterial.setAmbient(VBase4(0,0,1,1)) #Make this material blue

myNode = loader.loadModel("panda") #Load the model to apply the material to
myNode.setMaterial(myMaterial) #Apply the material to this nodePath

Material Properties

The following table details the properties available in a material, its effects as well as the relevant setter method. Most of these properties have additional get and clear methods as well.

Property | Effects | Setter Method
Ambient | This is the color of the object as it appears in the absence of direct light. This will be the multiplied by any ambient lights in effect on the material to set its base color. | material.setAmbient(VBase4(R,G,B,A))
Diffuse | This is the primary color of an object; the color of the object as it appears in direct light, in the absence of highlights. This will be multiplied by any lights in effect on the material to get the color in the parts of the object illuminated by the lights. | material.setDiffuse(VBase4(R,G,B,A))
Emission | This is the color of the object as it appears in the absence of any light whatsover, including ambient light. It is as if the object is glowing by this color (although of course it will not illuminate neighboring objects) | material.setEmission(VBase4(R,G,B,A))
Shininess | This controls the size of the specular highlight spot. In general, larger number produce a smaller specular highlight, which makes the object appear shinier. Smaller numbers produce a larger highlight, which makes the object appear less shiny. | material.setShininess(<float>)
Specular | This is the highlight color of an object: the color of small highlight reflections. | material.setSpecular(VBase4(R,G,B,A))

Other Material Methods

Besides the setter methods covered above, you can also get material properties using their get methods, such as material.getShininess() , material.getDiffuse(), etc.

Properties can also be reset by using the clear methods: material.clearAmbient(),material.clearSpecular(), etc. Shininess does not have a clear method.

Additionally you can check if a material has a property with the has methods: material.hasAmbient() , material.hasEmission(), etc.

Materials have two other methods that have not been covered yet, setLocal(<bool>) and setTwoside(<bool>). setLocal controls whether to use camera-relative specular highlights or orthogonal specular highlights. This should be set to True unless an orthogonal projection camera is in use. setTwoside controls if lighting should appear on both sides of a polygon. Both these methods have equivalent get methods.

Related Classes

  • Material
  • MaterialCollection


Panda3D Manual: Depth Test and Depth Write

Enabling or Disabling the Depth Buffer

By default, depth buffer is enabled and functions normally. It is possible to turn off the use of the depth buffer. It is also possible to alter the behavior of the depth buffer.

The most common thing to want to do is to disable the depth-write. This means that geometry will still be tested against the depth buffer, but it will not affect the depth buffer. This is often used when rendering objects such as particles that are transparent. To disable or enable the depth-write, use:

nodePath.setDepthWrite(False)  # Disable
nodePath.setDepthWrite(True)   # Enable

It may also be desirable to disable the depth-test. This means that the geometry pays no attention whatsoever to the contents of the depth-buffer. This is often used for rendering things like heads-up displays, which have no relation to the 3D depth of the scene. To disable or enable the depth-test, use:

nodePath.setDepthTest(False)  # Disable
nodePath.setDepthTest(True)   # Enable

One can remove these settings using clearDepthTest and clearDepthWrite.

Altering the Depth Buffer

Occasionally, it is desirable to alter the functionality of the depth buffer. Normally, the depth buffer only renders things that are in front, but it can be made to render things that are in back, or equal. This is rarely used, but it can be important for certain unusual algorithms like shadow volumes.

To do this, you need to use the DepthTestAttrib directly, in one of the following variants:

nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MNone))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MNever))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MLess))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MEqual))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MLessEqual))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreater))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MGreaterEqual))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MNotEqual))
nodePath.setAttrib(DepthTestAttrib.make(RenderAttrib.MAlways))

Depth Sorting

When turning depth test off, it is sometimes desirable to use depth sorting instead. Depth sorting is controlled by the culling system, which can be controlled by the CullBinAttrib.

Transparency

Certain settings of the TransparencyAttrib can also affect the depth-test.


Panda3D Manual: Fog

Basic Fog

To turn on fog, create an object of class Fog, and then apply it using the setFog operator:

myFog = Fog("Fog Name")
myFog.setColor(R,G,B)
myFog.setExpDensity(Float 0 to 1)
render.setFog(myFog)

However, there is more here than meets the eye. We have created a fog node, which goes into the scene graph. Therefore, the fog has a position, a place where the fog is (conceptually) thickest.

If the fog object is not parented into the scene graph (in the example above, for instance), then the fog's position is ignored, and the fog is camera-relative. Likewise, if the fog is exponential, the fog's position is ignored, and the fog is camera-relative.

The setFog directive creates a fog attribute object. Like any Render Attribute, the fog attribute affects the node that it is attached to, and any nodes below it in the scene graph. So you can easily cause only a subset of the objects (or just a single model) to be affected by the fog, by calling setFog on the root of the subgraph you want to be affected. To remove the fog attribute later, use the clearFog directive:

render.clearFog()

While you have fog in effect, it is often desirable to set the background color to match the fog:

base.setBackgroundColor( myFogColor )

Fog Modes

There are two fog modes in Panda: Fog.MExponential and Fog.MLinear. You can switch the mode of a Fog object using fog.getMode() and fog.setMode(Fog.Mode). This explicit mode switching isn't normally necessary, as Fog methods implicitly switch the mode for you.

A Fog object in Panda3D is a node that can be parented into the scene graph with a position, colour and orientation like any other node (importantly, Fog is a subclass of PandaNode, not of NodePath) (do Fog nodes have a scale?).

The position of a Fog node in the scene graph does not determine which objects the fog affects, it determines the origin and direction of the fog when it is in linear mode. When a fog node is in exponential mode its position and orientation in the scene graph are irrelevant. Either way, a Fog node must be activated by calling nodePath.setFog(fogNode) on some NodePath in the scene graph. Which NodePath you call the setFog method on determines which parts of the scene will be fogged: that NodePath and all its children.

Linear Fog

This is the default mode. In this mode the position and orientation of a Fog node are important. A linear-mode Fog node must first be parented into the scene graph, then activated by calling setFog(fogNode) on some NodePath in the scene graph.

Setup a linear fog node at the origin:

colour = (0.5,0.8,0.8)
linfog = Fog("A linear-mode Fog node")
linfog.setColor(*colour)
linfog.setLinearRange(0,320)
linfog.setLinearFallback(45,160,320)
render.attachNewNode(linfog)
render.setFog(linfog)

In linear mode, the onset and opaque distances of the fog are defined as offsets along the local forward (+Y) axis of the fog node. The onset distance is the distance from the fog node at which the fog will begin to have effect, and the opaque distance is the distance from the fog node at which the fog will be completely opaque. From reading the API page for the Fog class, it sounds as if beyond this opaque point there is no fog (rather than continuing opaque fog up to the location of the fog node as you might expect): "the fog will be rendered as if it extended along the vector from the onset point to the opaque point."

These settings can be modified using the methods getLinearOnsetPoint(), getLinearOpaquePoint(), setLinearOnsetPoint(float x,y,z), setLinearOpaquePoint(Point3D pos) and setLinearRange(float onset, float opaque) of Fog.

There is a hardware issue with rendering fog which means that linear fog can breakdown and vanish depending on the angle from which it is viewed:

"the underlying fog effect supported by hardware is generally only one-dimensional, and must be rendered based on linear distance from the camera plane. Thus, this in-the-world effect is most effective when the fog vector from onset point to opaque point is most nearly parallel to the camera’s eye vector. As the angle between the fog vector and the eye vector increases, the accuracy of the effect diminishes, up to a complete breakdown of the effect at a 90 degree angle."

The Fog method setLinearFallback(float angle, float onset, float opaque) defines how the fog should be rendered when the fog effect is diminished in this way. angle is the minimum viewing angle (angle between the camera direction and fog direction) at which the fallback effect will be employed. onset and opaque specify camera-relative onset and opaque distances that will be fallen back on, overriding the Fog node’s own onset and opaque distances.

The setLinearFallback(float angle, float onset, float opaque) workaround will only look good in certain situations, for example when the fog is deep inside a dark cave. So in general, exponential mode fog is more useful than the default linear mode fog.

Exponential Fog

In exponential fog mode the position and orientation of your fog node in the scene graph and the onset and opaque points are ignored (in fact you don’t even have to put your fog node in the scene graph). Instead, fog is rendered camera relative according to a density factor: the fog begins at the camera and continues to infinity, with an exponentially increasing density determined by the density factor. The fog moves with the camera as the camera’s position and orientation changes:

"the onset point and opaque point are not used, and the fog effect is based on the value specified to set_exp_density(), and it doesn’t matter to which node the fog object is parented, or if it is parented anywhere at all."

The fog.setExpDensity(float) method determines the density value used for exponential fog calculations.

You activate an exponential fog effect by calling the setFog(Fog) method of NodePath, for example: render.setFog(myFog):

Setup some scene-wide exponential fog:

colour = (0.5,0.8,0.8)
expfog = Fog("Scene-wide exponential Fog object")
expfog.setColor(*colour)
expfog.setExpDensity(0.005)
render.setFog(expfog)
base.setBackgroundColor(*colour)

The last line in the sample above doesn't actually affect the fog, however, it generally looks better if the scene background color matches the color of the fog.

Since setFog is called on render it effects the entire scene. setFog can just as easily be called on some other NodePath and will effect only that NodePath and its children.

The expontential fog effect can be turned off again using clearFog:

render.clearFog()


Panda3D Manual: Alpha Testing

Alpha Testing

The alpha test attribute governs whether or not a part of a node will be rendered based on the alpha value of its texture. This is particularly useful for rendering complex geometry into the depth or stencil buffer with a textured card rather than explicitly creating the shapes.

This test is different from rendering with respect to alpha transparency value. If you set an alpha test attribute on a node which is rendering into the color buffer, you may be surprised by the result. All pixels that pass the alpha test will be rendered just as if no test had been performed, including their appropriate transparency and pixels that fail the test will not be rendered at all.

Remember to set your attribute's priority to override any other alpha test attributes inherited from higher in the scene graph.

In the following example, we create an attribute that would cause objects to render only if their alpha value is below one quarter intensity.

lowPassFilter = AlphaTestAttrib.make(RenderAttrib.MLess,0.25)

And now, this attribute can be added to a node to enable the action.

nodePath.setAttrib(lowPassFilter)

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Color Write Masks

Color Write Masks

Color write masks enable you to block writes to the Red, Green, Blue, or Alpha channels of the framebuffer. This is not a frequently-used capability, but it does have a few applications:

  • When using red-blue 3D glasses, you might want to render the red image, then the blue image. (In fact, Panda uses this technique automatically when you set red-blue-stereo 1 in your Config.prc file.)
  • Battletech Battle Pods connect 3 black-and-white monitors to a single R,G,B video card output (really!) With the help of color write masks, you could update an individual monitor.
  • Sometimes, one wants to store data in the alpha-channel of the framebuffer. Using a color mask can avoid accidentally overwriting that data.

Using a color write-mask is not free. During normal rendering, each pixel written to the frame buffer requires a memory write. With a color-mask active, a memory read-modify-write cycle is needed, which is more expensive.

By default, color write masks are off.

Turning on the Color Mask

To enable writes to all the channels of the framebuffer, use this:

bits = ColorWriteAttrib.CAlpha
bits |= ColorWriteAttrib.CRed
bits |= ColorWriteAttrib.CGreen
bits |= ColorWriteAttrib.CBlue
nodePath.setAttrib(ColorWriteAttrib.make(bits))

To disable writes to one or more channels, omit that bit. You can also use:

nodePath.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.CAll))

To enable all bits.


Panda3D Manual: Antialiasing

Antialiasing

The antialias attribute of a node controls what kind of antialiasing is to be applied to that node. To choose one of the various forms of antialiasing, invoke one of the following variants:

np.setAntialias(AntialiasAttrib.MNone)
np.setAntialias(AntialiasAttrib.MPoint)
np.setAntialias(AntialiasAttrib.MLine)
np.setAntialias(AntialiasAttrib.MPolygon)
np.setAntialias(AntialiasAttrib.MMultisample)
np.setAntialias(AntialiasAttrib.MAuto)

In general, when rendering polygonal models, multisample antialiasing looks best. However, when rendering lines and points, it usually looks better to choose one of the specialized antialiasing modes. The MAuto setting automatically selects the kind that usually works best for the geometry in question. Thus, if you want to enable antialiasing on the whole scene, just use:

render.setAntialias(AntialiasAttrib.MAuto)

In order for multisample antialiasing to work, you have to have multisample bits available in your framebuffer. To request this, add:

framebuffer-multisample 1
multisamples 2

to your Config.prc file. Note that not all graphics cards have this capability. You may also be able to request more multisamples, such as 4 or 8, depending on your graphics card. If your card can provide additional samples, it produces a higher-quality antialiasing, at a small cost to render time.

The function clearAntialias can be used to remove the antialias setting. The function setAntialias takes an optional priority parameter, to control attribute overrides.


Panda3D Manual: Clip Planes

General Warning about Clip Planes

Most drivers do a very poor job of implementing clip planes. Furthermore, the way that clip planes combine with shaders can be unpredictable. The use of clip planes is not recommended. It is possible to emulate clip planes in a shader using the discard instruction.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Tinting and Recoloring

Color and the Model Loader

When you create a 3D model in Max, Maya, or the like, you can color the model right in the modeling program. I'm not talking about using a texture. I'm talking about just setting a single color to the model as whole. This is called a "flat color." These art programs also allow you to "paint vertex colors": you can color each vertex of the model a different color. Of course, sometimes you do neither, in which case the model is just white.

Every model you load already has a color attribute. Color Attributes are usually not created by the programmer explicitly, they're usually created by the model loader only. There are three possible color attributes created by the model loader:

Method | Explanation
ColorAttrib.makeVertex() | Used by the model loader to indicate that the model has vertex colors stored in its vertex arrays.
ColorAttrib.makeFlat(Vec4(R,G,B,A)) | Used by the model loader to indicate that the model has a single flat color.
ColorAttrib.makeOff() | Used by the model loader to indicate that no particular color was specified. Essentially the same as flat white, but possibly a little faster to render.

Panda combines these color attributes with the textures. If you are accustomed to photoshop, you should think of the model's color as the bottom (background) layer, and the textures go above that. By default, each texture is modulated (multiplied) with the previous layer, but as in photoshop, that can be changed.

Recoloring the Model

If you wish, you can manually override the color attribute which has been specified by the model loader.

nodePath.setColor(r,g,b,a)

Again, this is an override. If the model already had vertex colors, these will disappear: the setColor method is replacing those colors with a new one. If the model already had a flat color, that will be replaced with the new flat color.

It should be mentioned that the color attribute created by the model loader has low priority. That means that even a default-priority setColor is enough to override it.

You can remove a previous setColor using clearColor.

Tinting the Model

Sometimes, you don't want to replace the existing color, sometimes, you want to tint the existing colors. For this, you need setColorScale:

nodePath.setColorScale(r,g,b,a)

This color will be modulated (multiplied) with the existing color.

You can remove a previous setColorScale using clearColorScale.

Demonstration

To see the difference between setColor and setColorScale, try the code sample below. You will need the nik-dragon model from the Cartoon Shading sample program, which has vertex colors and no texture:

import direct.directbase.DirectStart
from pandac.PandaModules import ColorAttrib

# Load three copies of Nik's dragon, which has vertex colors.
model1 = loader.loadModel("nik-dragon")
model2 = loader.loadModel("nik-dragon")
model3 = loader.loadModel("nik-dragon")

# Put them in the scene.
model1.reparentTo(render)
model2.reparentTo(render)
model3.reparentTo(render)

# Arrange them left-to-right
model1.setPos(-10,50,0)
model2.setPos(  0,50,0)
model3.setPos( 10,50,0)

# Model 1 will be left alone, so you can see the original.
# Model 2 will be recolored light blue.
# Model 3 will be tinted light blue.
model2.setColor(0.6, 0.6, 1.0, 1.0)
model3.setColorScale(0.6, 0.6, 1.0, 1.0)

run()

This produces the following output:

The model on the left is the original, unaltered model. Nik has used vertex colors throughout. The yellow of the belly, the black eyes, the red mouth, these are all vertex colors. The one in the middle has been setColored to a medium-blue color. As you can see, the setColor completely replaces the vertex colors. The one on the right bas been setColorScaleed to the same medium-blue color, but this only tints the model.

Related Classes

  • ColorScaleAttrib
  • ColorAttrib
  • NodePath


Panda3D Manual: Backface Culling and Frontface Culling

Backface and Frontface Culling

By default, Panda3D automatically culls (doesn't render) backfaces of polygons. In other words, the polygon acts like a one-way mirror: you can see it from one side, but from the other side, it's see-through. Backface culling is a very useful performance optimization. Without it, the 3D engine would have to render the inside surface of 3D models. Since this surface is not visible anyhow, this is entirely wasted work. Since the surface area of the inside is equal to the surface area of the outside, this would roughly double the amount of work the video card has to do. This is why backface culling is enabled by default.

Interestingly, this means that if you move the camera inside of a closed 3D model, you can usually see out. This is actually pretty convenient most of the time.

However, there are cases when you do want to be able to see backfaces. There are also very rare cases when you don't want to see front-faces. Therefore, backface and frontface culling can be controlled.

Caution: inexperienced 3D modelers sometimes create models with the polygons facing inward: ie, the visible side of the polygon is on the inside of the 3D model, and the see-through side is on the outside. As a result, the 3D model can look very weird - it can have holes, or it can look inside-out. Turning off backface culling can sort of "fix" these models, at a heavy cost: first, it makes them render half as fast, and second, it causes weird lighting artifacts (because the video card is calculating the lighting for the inside of the model, not the outside). This is not a real solution to bad 3D modeling: the only real fix is to make the 3D models correctly in the first place.

In other words, don't alter the backface or frontface culling unless you're using an algorithm that requires it, such as stencil shadows.

Rendering Double-Sided

There is a quick to render a certain NodePath in your scene double-sided, which means no culling is performed at all and both sides are visible, without hassling with attribs:

nodePath.setTwoSided(True)

If you want more advanced control over the culling you might want to look at the CullFaceAttrib:

Controlling Backface and Frontface Culling

There are three valid settings for culling:

nodePath.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullNone))
nodePath.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise))
nodePath.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise))

None means that all faces are visible, both back and front. Clockwise is the default setting, it causes backfaces to be culled. CounterClockwise is the reverse setting, it causes frontfaces to be culled.


Panda3D Manual: Light Ramps

Light Ramps

In standard OpenGL and DirectX lighting, the following calculations are performed:

  • the lighting value is calculated
  • it is clamped to the range 0-1
  • it is combined with the textures
  • it is clamped to the range 0-1 again
  • it is written to the frame buffer

This process contains two clamps. The LightRampAttrib is a means to specify that you wish to replace these two clamping operators with something a little smarter. This is particularly relevant for two major graphics algorithms: HDR tone mapping, and cartoon shading.

It must be emphasized that light ramps have no effect unless per-pixel lighting is enabled via the shader generator.

HDR Tone Mapping

In HDR tone mapping, the first clamp is removed entirely, and the second one is replaced with the tone mapping operator. The tone mapping operator maps brightness values in the range 0-infinity to new brightness values in the range 0-1, however, it does so without clamping. To turn on HDR tone mapping, use one of the following:

np.setAttrib(LightRampAttrib.makeHdr0())
np.setAttrib(LightRampAttrib.makeHdr1())
np.setAttrib(LightRampAttrib.makeHdr2())

The HDR2 tone mapping operator is a familiar operator that is used in many systems. It has the downside that it tends to reduce contrast a lot:

  • FINAL_RGB = (RGB) / (RGB + 1)

The HDR1 tone mapping operator is similar, but it allocates more of the contrast range to brightnesses in the range 0-1, and less to brightnesses in the range 1-infinity. This yields a higher-contrast scene, but with more washout:

  • FINAL_RGB = (RGB^2 + RGB) / (RGB^2 + RGB + 1)

The HDR0 tone mapping operator allocates even more of the available contrast range to brightnesses in the range 0-1. This is even more contrasty, but with even more washout:

  • FINAL_RGB = (RGB^3 + RGB^2 + RGB) / (RGB^3 + RGB^2 + RGB + 1)

Cartoon Shading (Quantized Lighting)

In cartoon shading, the first clamp is removed entirely, and the second one is replaced with a quantization function. This replaces a continuous gradient of brightness values with a discrete set of light levels. This quantization function only applies to directional lights, not ambient ones.

To enable quantized lighting, use one of these:

np.setAttrib(LightRampAttrib.makeSingleThreshold(t0, l0))
np.setAttrib(LightRampAttrib.makeDoubleThreshold(t0, l0, t1, l1))

In a single-threshold system, the brightness of the diffuse lighting contribution is compared to the threshold t0. If the threshold is not met, the diffuse light contribution is eliminated. If it is met, the pixel's brightness is normalized to the specified level l0.

In a double-threshold system, the brightness of the diffuse lighting contribution is compared to the thresholds t0 and t1. If neither is attained, the diffuse light contribution is eliminated. If it is met, the pixel's brightness is normalized to either l0 or l1, depending on which threshold was passed.

Future Light Ramps

We are interested in knowing if there are any other light ramps you would like to see. If so, please notify us on the forums.


Panda3D Manual: Auxiliary Bitplane Control

Auxiliary Bitplane Control

The framebuffer always contains a color bitplane and a depth bitplane. It may also have a stencil bitplane or accumulation bitplane. In addition, if it is a render-to-texture framebuffer, it may have auxiliary bitplanes. These auxiliary bitplanes can be used to store more or less arbitrary user-defined data.

When per-pixel lighting is enabled via the shader generator, the shader generator can be asked to produce extra data into the auxiliary bitplanes. This is done by setting an AuxBitplaneAttrib:

np.setAttrib(AuxBitplaneAttrib.make(bits))

Where bits is a set of bits indicating what should be written into the auxiliary bitplanes.

Although the framebuffer's alpha channel is not technically an auxiliary bitplane. However, since it is not generally used to store any data of value, it can also be thought of as an auxiliary bitplane: a place to store user-defined data.

When the shader generator is not enabled, this attrib has no effect.

Values That Can be Requested

The following is a list of bits that can be passed to AuxBitplaneAttrib.make:

  • AuxBitplaneAttrib.ABOGlow: copy the glow map (aka self-illumination map) into the alpha channel of the framebuffer. Usually this is a prelude to running a bloom filter over the scene.
  • AuxBitplaneAttrib.ABOAuxNormal: Store the camera-space normal of the polygon surface in the RGB channels of the first auxiliary bitplane. This is often used to help detect edges in a cartoon inking filter.
  • AuxBitplaneAttrib.ABOAuxGlow: copy the glow map (aka self-illumination map) into the alpha channel of the first auxiliary bitplane.


Panda3D Manual: Stencil Test/Write Attribute

The StencilAttrib is used for testing and writing to the stencil buffer. Note that both of these actions can be performed simultaneously with a single StencilAttrib.

The stencil buffer is an ancillary graphics buffer, in addition to the more well-known color and depth buffers. It provides a per-pixel mask for the rendering pipeline which can be exploited to selectively render objects or parts of objects. Typical applications of the stencil buffer include binary masking, shadowing and planar reflections.

Usually, using the stencil buffer involves creating some objects which are not rendered into the color buffer. Their contribution to the rendered scene is to provide an invisible boundary that can be used to turn color buffer rendering on and off for other objects in the scene. Think of it as a cardboard cut-out through which the world is viewed.

During a stencil comparison, the StencilAttrib's reference value is compared against the value stored in the stencil buffer. The order matters here. For example, consider comparison function StencilAttrib.SCFGreaterThan with reference value r=1. A pixel passes the stencil test if r > Sp, where Sp is the value in the stencil buffer at pixel p. Objects contributing values to the stencil buffer that will be read by other StencilAttributes' comparison functions must be rendered first, or unexpected results will occur. See How to Control Render Order.

The stencil buffer is disabled by default. In order to use StencilAttribs, you must add the following line to your config.prc file:

framebuffer-stencil #t

StencilAttribs are defined exclusively by their constructor functions, so let's examine one to understand what each part does. The following code creates an attribute which tells an object to render only if the stencil buffer is exactly 1, and does not itself modify the stencil buffer.

stencilReader =
  StencilAttrib.make(1,StencilAttrib.SCFEqual,StencilAttrib.SOKeep,
                       StencilAttrib.SOKeep,StencilAttrib.SOKeep,1,1,0)

The first parameter is a boolean. If this parameter is zero, the StencilAttrib is not processed. Next is the comparison function this attribute uses, in this case Equal. The next three parameters determine what happens to the stencil buffer depending on the result of the comparison. We'll get to these in a minute. The three Keep values tell this attribute never to modify the values in the buffer. Next is the reference value for the comparison function. Before the reference value is passed to the comparison function, however, it is bitwise ANDed with a mask. In our case, we're interested in reading but not in writing the stencil buffer, so we pass 1 and 0 for the read and write masks, respectively. These masks are the last two parameters for the StencilAttrib.

Next, we'll look at a stencil attribute that writes the stencil buffer. Presumably, these two functions will work in tandem to create an effect.

constantOneStencil =
  StencilAttrib.make(1,StencilAttrib.SCFAlways,StencilAttrib.SOZero,
                       StencilAttrib.SOReplace,StencilAttrib.SOReplace,1,0,1)

Again we start by enabling the attribute. The comparison function here is Always, meaning that the test passes no matter the parameters. Next is the operation to perform on the stencil buffer if the test fails (which in this case will never happen) -- we set the stencil buffer value to zero. The next parameter determines what should happen if the stencil function passes, but the depth test fails, and finally, what should happen if both the stencil and depth tests pass. In our case we want to set the value of the stencil buffer whether we pass the depth test or not, so both are set to Replace. The reference value to set in the stencil buffer is 1. We're writing regardless of what's in the buffer already, so we'll set the read and write masks to 0 and 1, respectively.

Now we can add these attributes to nodes in the scene to exploit the effect.

cm = CardMaker("cardmaker")
cm.setFrame(-.5,.5,-.5,.5)
viewingSquare = render.attachNewNode(cm.generate())
viewingSquare.node().setAttrib(constantOneStencil)
viewingSquare.node().setAttrib(ColorWriteAttrib.make(0))
viewingSquare.setBin('background',0)
viewingSquare.setDepthWrite(0)

view = loader.loadModel("models/Panda")
view.reparentTo(render)
view.setScale(3)
view.setY(150)
view.node().setAttrib(stencilReader)


Panda3D Manual: Texturing

At its simplest, texturing merely consists of applying a texture in your modeling program. When you export the model, pay attention to the relative path between the egg file you create, and the image files. That relative path is encoded into the egg file. When panda attempts to load the egg file, it will look in the same position relative to the egg file. Panda can load JPG, PNG, TIF, and a number of other file formats.

More advanced texturing methods are described in the sections that follow.


Panda3D Manual: Simple Texturing

A texture map or texture image is a two-dimensional image file, like a JPEG or a Windows BMP file, that is used to apply color to a 3-D model. It is called a "texture" because one of the earliest uses of this technique was to apply an interesting texture to walls and floors that would otherwise be one flat, plastic-looking color. Nowadays texturing is so common in 3-D applications that it is often the only thing used to apply color to models--without texture maps, many models would simply be white.

There are a vast array of rendering effects that can be achieved with different variants on texturing. Before you can learn about them, it is important to understand the basics of texturing first.

In simple texturing--by far the most common form--you can think of the texture map as a layer of paint that is applied to the model. In order for the graphics hardware to know in what direction the paint should be applied, the model must have been created with texture coordinates--a special (u, v) coordinate pair that is associated with each vertex of your model. Each vertex's (u, v) texture coordinates place the vertex at a particular point within the texture map, in the same way that the vertex's (x, y, z) coordinates place the vertex at a particular point in 3-D space.

These texture coordinates are sometimes called uv's because of the (u, v) name of the coordinate pair. Almost any modeling package that you might use to create a model can create texture coordinates at the same time, and many do it without even asking.

By convention, every texture map is assigned a (u, v) coordinate range such that the u coordinate ranges from 0 to 1 from right to left, and the v coordinate ranges from 0 to 1 from bottom to top. This means that the bottom-left corner of the texture is at coordinate (0, 0), and the top-right corner is at (1, 1). For instance, take a look at some typical texture maps:

The assignment of (u, v) values to texture images

It is the (u, v) texture coordinates that you assign to the vertices that determine how the texture map will be applied to your model. When each triangle of your model is drawn, it is drawn with the colors from your texture map that fall within the same triangle of vertices in (u, v) texture map space. For instance, the sample smiley.egg model that ships with Panda has its vertices defined such that the u coordinate increases from 0 to 1 around its diameter, and the v coordinate increases from 0 at the bottom to 1 at the top. This causes the texture image to be wrapped horizontally around the sphere:

The assignment of (u, v) values to vertices

Note that the (u, v) range for a texture image is always the same, 0 to 1, regardless of the size of the texture.


Panda3D Manual: Choosing a Texture Size

Standard Texture Sizes

Most graphics hardware requires that your texture images always be a size that is a power of two in each dimension. That means you can use any of the following choices for a texture size: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, or so on (but unless you have a really high-end card, you'll probably need to stop there).

The textures don't usually have to be square: they don't have to have the same size in both dimensions. But each dimension does usually have to be a power of two. So 64 × 128 is all right, for instance, or 512 × 32, or 256 × 256. But you can't make a texture image that is 200 × 200 pixels, since 200 isn't a power of two.

By default, Panda3D will automatically rescale any texture image down to the nearest smaller power of two when you read it from disk, so you usually don't have to think about this--but your application will load faster if you scale your textures properly in the first place.

If you would like Panda3D to rescale your images up to the next larger power of two instead of down to the next smaller power of two, use:

textures-power-2 up

In your config file. The default is:

textures-power-2 down

Note that the size you choose for the texture image has nothing to do with the size or shape of the texture image onscreen--that's controlled by the size and shape of the polygon you apply it to. Making a texture image larger won't make it make it appear larger onscreen, but it will tend to make it crisper and more detailed. Similarly, making a texture smaller will tend to make it fuzzier.

Padded Textures

Sometimes, you need to load data into a texture every frame. The most common example is when you're playing a movie. Let's say, for example, that the movie is encoded at 640x480 at 30fps. Neither of those dimensions is a power-of-two. It would theoretically be possible for Panda3D to rescale the image to 512x512, but it would have to do it 30 times per second, which is too expensive to be practical.

Instead, panda pads the data. Panda creates a 1024x512 texture, which is the smallest power-of-two size that can hold a 640x480 movie. It sticks the 640x480 movie into the lower-left corner of the texture. Then, it adds a black border to the right edge and top edge of the movie, padding it out to 1024x512.

From that point forward, it's just a texture with a movie in the lower-left corner, and black bars on the upper and right sides. However, if you use UV coordinates carefully, you can cause just the movie to be displayed. To do this, you need to know how big those black bars are:

padx = texture.getPadXSize()
pady = texture.getPadYSize()

Panda3D only uses padded textures in a few very special cases: 1. When playing a non-power-of-two movie. 2. When using render-to-texture, and a non-power-of-two buffer.

Nonstandard Texture Sizes

Some newer graphics cards can render textures that are not a power of two. This is generally not very useful for loading art from disk: after all, game art is usually created in power-of-two sizes no matter what. However, it is useful to avoid wasteful movie padding of the kind described above. If you have one of these cards and you want to enable the use of non-power-of-two textures, you can put the following line in your Config.prc:

textures-power-2 none

You can also attempt to have panda detect your video card's capabilities automatically, using this command:

textures-auto-power-2 #t

If this variable is true, then panda will wait until you open a window, and then ask the window's driver if the driver supports non-power-of-two textures. If so, then the config variable textures_power_2 will automatically be adjusted. In this way, you can configure Panda3D to use non-power-of-two textures if they are available.

Caution: there is a potential pitfall when using textures-auto-power-2. If you open a window that supports non-power-of-two textures, panda will switch into textures-power-2 none mode. If you then open a second window using a different video card which doesn't support non-power-of-two textures, then panda will have no choice but to print an error message.

Note that some cards appear to be able to render non-power-of-two textures, but the driver is really just scaling the textures at load time. With cards like these, you're better off letting Panda do the scaling, or dynamic textures may render very slowly.


Panda3D Manual: Texture Wrap Modes

As described earlier, the (u, v) texture coordinates that you assign to your vertices are what determines how the texture fits on your geometry. Often, you will use texture coordinates that always fall within the range [0, 1], which is the complete range of the pixels of your texture image. However, it is also legal to use texture coordinates that go outside this range; you can have negative values, for instance, or numbers higher than 1.

So if the texture image is only defined over the range [0, 1], what does the texture look like outside this range? You can specify this with the texture wrap mode.

texture.setWrapU(wrapMode)
texture.setWrapV(wrapMode)

The wrapMode parameter is specified separately for the u and v directions (there is also a setWrapW() for 3-D textures, but that's an advanced topic). The wrapMode may be any of the following values:

Texture.WMRepeat | The texture image repeats to infinity.
Texture.WMClamp | The last pixel of the texture image stretches out to infinity.
Texture.WMBorderColor | The color specified by texture.setBorderColor() is used to fill the space.
Texture.WMMirror | The texture image flips back-and-forth to infinity.
Texture.WMMirrorOnce | The texture image flips backwards, once, and then the "border color" is used.

The default wrap mode is WMRepeat.

Consider the following simple texture image:

We will apply this texture in the center of a large polygon whose texture coordinates range considerably farther than [0, 1] in both directions.

WMRepeat

texture.setWrapU(Texture.WMRepeat)
texture.setWrapV(Texture.WMRepeat)

WMRepeat mode is often used to tile a relatively small texture over a large surface.

WMClamp

texture.setWrapU(Texture.WMClamp)
texture.setWrapV(Texture.WMClamp)

WMClamp mode is rarely used on large polygons because, frankly, it looks terrible when the pixels stretch out to infinity like this; but this mode is usually the right choice when the texture exactly fills its polygon (see One caution about a common wrap error, below).

WMBorderColor

texture.setWrapU(Texture.WMBorderColor)
texture.setWrapV(Texture.WMBorderColor)
texture.setBorderColor(VBase4(0.4, 0.5, 1, 1))

The above blue color was chosen for illustration purposes; you can use any color you like for the border color. Normally, you would use the background color of the texture as the border color, like this:

texture.setWrapU(Texture.WMBorderColor)
texture.setWrapV(Texture.WMBorderColor)
texture.setBorderColor(VBase4(1, 1, 1, 1))

Some very old graphics drivers don't support WMBorderColor. In this case, Panda3D will fall back to WMClamp, which will look similar as long as there is a sufficient margin of background color around the edge of your texture (unlike our sample texture, which goes all the way out the edge).

WMMirror

texture.setWrapU(Texture.WMMirror)
texture.setWrapV(Texture.WMMirror)

Many older graphics drivers do not support WMMirror. In this case, Panda3D will fall back to WMRepeat.

WMMirrorOnce

texture.setWrapU(Texture.WMMirrorOnce)
texture.setWrapV(Texture.WMMirrorOnce)
texture.setBorderColor(VBase4(0.4, 0.5, 1, 1))

Few graphics drivers support WMMirrorOnce. In this case, Panda3D will fall back to WMBorderColor.

Setting different wrap modes

It is possible to set different wrap modes in the u and v directions:

texture.setWrapU(Texture.WMRepeat)
texture.setWrapV(Texture.WMClamp)

WMClamp and WMRepeat

One caution about a common wrap mode error

When you apply a texture that is intended to exactly fill a polygon--that is, the texture coordinates range from 0 to 1, but no further--you should usually set its wrap mode to clamp. This is because if you let it keep the default value of repeat, the color may bleed in from the opposite edge, producing a thin line along the edge of your polygon, like this:

A common wrap mode error

This is a particularly common error with a texture that is painted as an alpha cutout, where there is an image with a fully transparent background: you will often see an thin, barely-visible edge floating along the top (for instance) of the polygon. This edge is actually the bottom edge of the texture bleeding onto the top, because the designer specified WMRepeat instead of the correct mode, WMClamp.


Panda3D Manual: Texture Filter Types

It's rare that the pixels of a texture image match one-to-one with actual screen pixels when a texture is visible onscreen. Usually, it is the case that either a single pixel of the texture is stretched over multiple screen pixels (texture magnification--the texture image is stretched bigger), or the opposite, that multiple pixels of a texture contribute to the color of a single screen pixel (texture minification--the texture image is squished smaller). Often, a single polygon will have some texture pixels that need to be magnified, and some pixels that need to be minified (the graphics card can handle both cases on a single polygon).

You can control how the texture looks when it is magnified or minified by setting its filter type.

texture.setMagfilter(filterType)
texture.setMinfilter(filterType)

There is a separate filterType setting for magnification and for minification. For both magnification and minification, the filterType may be one of:

Texture.FTNearest | Sample the nearest pixel.
Texture.FTLinear | Sample the four nearest pixels, and linearly interpolate them.

For minification only, in addition to the above two choices, you can also choose from:

Texture.FTNearestMipmapNearest | Point sample the pixel from the nearest mipmap level.
Texture.FTLinearMipmapNearest | Bilinear filter the pixel from the nearest mipmap level.
Texture.FTNearestMipmapLinear | Point sample the pixel from two mipmap levels, and linearly blend.
Texture.FTLinearMipmapLinear | Bilinearly filter the pixel from two mipmap levels, and linearly blend the results. This is also called trilinear filtering.

The default filter type for both magnification and minification is FTLinear.

Consider the visual effects of the various filter types on magnification and minification of the following texture:

A fractal image

FTNearest

texture.setMagfilter(Texture.FTNearest)
texture.setMinfilter(Texture.FTNearest)

Usually, FTNearest is used only to achieve a special pixelly effect.

FTLinear

texture.setMagfilter(Texture.FTLinear)
texture.setMinfilter(Texture.FTLinear)

FTLinear is a good general-purpose choice, though it isn't perfect.

Mipmaps

Many graphics tutorials will go on for pages and pages about exactly what mipmapping means and how it all works inside. We'll spare you those details here; but you should understand the following things about mipmapping:

(1) It requires 33% more texture memory (per mipmapped texture), but it renders quickly.

(2) It helps the texture look much smoother than filtering alone when it is minified.

(3) Mipmapping doesn't have anything at all to do with magnification.

(4) It has a tendency to blur minified textures out a little too much, especially when the texture is applied to a polygon that is very nearly edge-on to the camera.

There are four different filter types that involve mipmapping, but you almost always want to use just the last one, FTLinearMipmapLinear. The other modes are for advanced uses, and sometimes can be used to tweak the mipmap artifacts a bit (especially to reduce point 4, above). If you don't understand the description in the table above, it's not worth worrying about.

texture.setMinfilter(Texture.FTLinearMipmapLinear)

Anisotropic Filtering

There is one final addition to the texture filtering equation: you can enable anisotropic filtering on top of any of the above filter modes, which enables a more expensive, slightly slower rendering mode that generally produces superior effects. In particular, anisotropic filtering is usually better at handling texture minification than mipmapping, and doesn't tend to blur out the texture so much.

To enable anisotropic filtering, you specify the degree:

texture.setAnisotropicDegree(degree)

The degree should be an integer number. The default value is 1, which indicates no anisotropic filtering; set it to a higher number to indicate the amount of filtering you require. Larger numbers are more expensive but produce a better result, up to the capability of your graphics card. Many graphics cards don't support any degree other than 2, which is usually sufficient anyway.

texture.setAnisotropicDegree(2)

Magnification w/anisotropic filtering Minification w/anisotropic filtering

At the present, anisotropic filtering is only supported by the DirectX interfaces. Some older graphics cards cannot perform anisotropic filtering at all.


Panda3D Manual: Simple Texture Replacement

Although usually you will load and display models that are already textured, you can also apply or replace a texture image on a model at runtime. To do this, you must first get a handle to the texture, for instance by loading it directly:

myTexture = loader.loadTexture("myTexture.png")

The above loadTexture() call will search along the current model-path for the named image file (in this example, a file named "myTexture.png"). If the texture is not found or cannot be read for some reason, None is returned.

Once you have a texture, you can apply it to a model with the setTexture() call. For instance, suppose you used the CardMaker class to generate a plain white card:

cm = CardMaker('card')
card = render.attachNewNode(cm.generate())

Then you can load up a texture and apply it to the card like this:

tex = loader.loadTexture('maps/noise.rgb')
card.setTexture(tex)

(Note that it is not necessary to use the override parameter to the setTexture() call--that is, you do not need to do card.setTexture(tex, 1)--because in this case, the card does not already have any other texture applied to it, so your texture will be visible even without the override.)

In order for this to work, the model you apply it to must already have texture coordinates defined (see Simple Texturing). As it happens, the CardMaker generates texture coordinates by default when it generates a card, so no problem there.

You can also use setTexture() to replace the texture on an already-textured model. In this case, you must specify a second parameter to setTexture, which is the same optional Panda override parameter you can specify on any kind of Panda state change. Normally, you simply pass 1 as the second parameter to setTexture(). Without this override, the texture that is assigned directly at the Geom level will have precedence over the state change you make at the model node, and the texture change won't be made.

For instance, to change the appearance of smiley:

smiley = loader.loadModel('smiley.egg')
smiley.reparentTo(render)
tex = loader.loadTexture('maps/noise.rgb')
smiley.setTexture(tex, 1)

Smiley with noise.egg applied

Often, you want to replace the texture on just one piece of a model, rather than setting the texture on every element. To do this, you simply get a NodePath handle to the piece or pieces of the model that you want to change, as described in the section Manipulating a Piece of a Model, and make the setTexture() call on those NodePaths.

For instance, this car model has multiple textures available in different colors:

For the most part, this car was painted with one big texture image, which looks like this:

But we also have a blue version of the same texture image:

Although it is tempting to use setTexture() to assign the blue texture to the whole car, that would also assign the blue texture to the car's tires, which need to use a different texture map. So instead, we apply the blue texture just to the pieces that we want to change:

car = loader.loadModel('bvw-f2004--carnsx/carnsx.egg')
blue = loader.loadTexture('bvw-f2004--carnsx/carnsx-blue.png')
car.find('**/body/body').setTexture(blue, 1)
car.find('**/body/polySurface1').setTexture(blue, 1)
car.find('**/body/polySurface2').setTexture(blue, 1)

And the result is this:


Panda3D Manual: Multitexture Introduction

Panda3D provides the ability to apply more than one texture image at a time to the polygons of a model. The textures are applied on top of each other, like coats of paint; very much like the "layers" in a popular photo-paint program.

To layer a second texture on a model, you will have to understand Panda's concept of a TextureStage. Think of a TextureStage as a slot to hold a single texture image. You can have as many different TextureStages as you want in your scene, and each TextureStage might be used on one, several, or all models.

When you apply a texture to a model, for instance with the setTexture() call, you are actually binding the texture to a particular TextureStage. If you do not specify a TextureStage to use, Panda assumes you mean the "default" TextureStage object, which is a global pointer which you can access as TextureStage.getDefault().

Each TextureStage can hold one texture image for a particular model. If you assign a texture to a particular TextureStage, and then later (or at a lower node) assign a different texture to the same TextureStage, the new texture completely replaces the old one. (Within the overall scene, a given TextureStage can be used to hold any number of different textures for different nodes; but it only holds one texture for any one particular node.)

However, you can have as many different TextureStages as you want. If you create a new TextureStage and use it to assign a second texture to a node, then the node now has both textures assigned to it.

Although there is no limit to the number of TextureStages you assign this way, your graphics card will impose some limit on the number it can render on any one node. Modern graphics cards will typically have a limit of 4 or 8 textures at once; some older cards can only do 2, and some very old cards have a limit of 1 (only one texture at a time). You can find out the multitexture limit on your particular card with the call base.win.getGsg().getMaxTextureStages().

Remember, however, that this limit only restricts the number of different TextureStages you can have on any one particular node; you can still have as many different TextureStages as you like as long as they are all on different nodes.

Let's revisit the example from Simple Texture Replacement, where we replaced the normal texture on smiley.egg with a new texture image that contains a random color pattern. This time, instead of assigning the new texture to the default TextureStage, we'll create a new TextureStage for it, so that both textures will still be in effect:

smiley = loader.loadModel('smiley.egg')
smiley.reparentTo(render)
tex = loader.loadTexture('maps/noise.rgb')
ts = TextureStage('ts')
smiley.setTexture(ts, tex)

Note that we can create a new TextureStage object on the fly; the only parameter required to the TextureStage parameter is a name, which is significant only to us. When we pass the TextureStage as the first parameter to setTexture(), it means to assign the indicated texture to that TextureStage. Also note that we no longer need to specify an override to the setTexture() call, since we are not overriding the texture specified at the Geom level, but rather we are adding to it.

And the result is this:

To undo a previous call to add a texture, use:

smiley.clearTexture(ts)

passing in the same TextureStage that you used before. Or, alternatively, you may simply use:

smiley.clearTexture()

to remove all texture specifications that you previously added to the node smiley. This does not remove the original textures that were on the model when you loaded it; those textures are assigned at a different node level, on the Geom objects that make up the model.


Panda3D Manual: Texture Modes

There are several kinds of textures: color textures, normal maps, gloss maps, glow maps, and so forth. To distinguish these, use the TextureStage.setMode function.

Futhermore, within the general category of color textures, there are several ways to combine the colors: Modulate, Decal, Blend, and so forth. These, too, are controlled using the TextureStage.setMode function.

The Basics

Let's go back to the example of applying a texture to the smiley model. In this case, we'll create a new TextureStage to apply the following texture image:

Sample multitexture image

To this scene:

Smiley with no multitexture

Note that the circular white part of the sample image is actually not white at all, but an alpha cutout (you are seeing through the image to the white page background). We have rendered smiley.egg against a colored background so you can see the effects of alpha in the various modes below; in some of them, the alpha is propagated through to the final color, so smiley is transparent in those parts of the image, but in other modes, the alpha is used for a different purpose, and smiley is not transparent there.

Note also that, for the purposes of illustration, we have only applied the sample texture image to a portion of the smiley model, rather than to the whole model. (This was done by transforming the texture coordinates of this texture stage, which is covered in a later topic.)

Modulate mode

This is the default blend mode. In this mode, the top texture color is multiplied by the bottom texture color to produce the result. This means the resulting texture color will be darker (or at least, no brighter) than both of the original texture colors.

ts = TextureStage('ts')
ts.setMode(TextureStage.MModulate)
smiley.setTexture(ts, tex)

Modulate blend mode

Note that in this mode, an alpha cutout in the top texture produces an alpha cutout in the resulting image.

Add mode

In this mode, the top texture color is added to the bottom texture color, and clamped to 1 (white). This means the resulting texture color will be brighter (or at least, no darker) than both of the original texture colors.

ts = TextureStage('ts')
ts.setMode(TextureStage.MAdd)
smiley.setTexture(ts, tex)

Add blend mode

Note that in this mode, as in modulate mode, an alpha cutout in the top texture produces an alpha cutout in the resulting image. Also note that, unless one or both of your source textures was rather dark, there is a tendency for the colors to get washed out at white where everything clamps to 1.

Replace mode

In this mode the top texture completely replaces the bottom texture. This mode is not often used.

ts = TextureStage('ts')
ts.setMode(TextureStage.MReplace)
smiley.setTexture(ts, tex)

Replace blend mode

Note that the alpha cutout is preserved, but the effects of lighting (which are considered part of the underlying texture) have been lost.

Decal mode

In this mode the top texture completely replaces the bottom texture, but only where alpha = 1 in the top texture. When alpha = 0, the bottom texture shows through, and there is a smooth blending for alpha values between 0 and 1.

ts = TextureStage('ts')
ts.setMode(TextureStage.MDecal)
smiley.setTexture(ts, tex)

Decal blend mode

Note that the alpha cutout is no longer preserved in this mode, because alpha is used to determine which texture should be visible. Also note that the effects of lighting are lost for the decalled part of the texture.

Panda3D also provides a built-in decal capability, for rendering a small polygon coplanar with and embedded within a larger polygon, which is not related to the decal texture blend mode.

Blend mode

Blend mode is similar to decal mode, except you can specify the color of the decal as a parameter at runtime. You can vary the color and you don't have to have a different texture image prepared for each possible color. However, the decal will always be monochromatic (it will be drawn in different shades of whatever color you specify).

Blend mode can only be used with a grayscale texture, and it does not use alpha. Since the sample texture above is not a grayscale texture, we will use a different texture for this example:

This texture does not have an alpha channel; it is simply a grayscale image with a large white "A" on a field of black. Blend mode will produce the original color where the image is black, and the color we specify with TextureStage.setColor() where the image is white. Where the image is shades of gray, there will be a smooth blending between the colors.

ts = TextureStage('ts')
ts.setMode(TextureStage.MBlend)
ts.setColor(Vec4(1, 0, 0, 1))
smiley.setTexture(ts, tex)

Blend mode, with a red image

And we can change the color of the decal at will, simply with:

ts.setColor(Vec4(0, 0, 1, 1))

Blend mode, with a blue image

Note that, as with the decal example above, the lighting information is lost where the decal is applied.

Normal Map Mode

Normal maps are maps that cause surfaces to appear to have raised and lowered areas. They are also called "bump maps." Unlike most maps, normal maps do not affect the color of the model - they affect the lighting. When light hits a normal-mapped model, the light creates highlights and shadows throughout those ridges and bumps. If no lights are applied to the model, then there will be no shadows or highlights, and the normal map will be invisible. Currently, Panda3D only allows one normal map per polygon, additional normal maps will be ignored.

ts = TextureStage('ts')
ts.setMode(TextureStage.MNormal)
smiley.setTexture(ts, tex)

The creation of normal maps is a sufficiently complex process that it is beyond the scope of this manual. However, there are many good tools out there like Z-Brush, Crazy Bump, and the like which create normal maps.

See also: Sample Programs: Normal Mapping

Normal maps are new to Panda3D as of version 1.5.0. They only function when shader generation is enabled.

Gloss Map Mode

A gloss map is a black-and-white image indicating where the model is supposed to be shiny and where it is supposed to be dull. Gloss maps do not affect the color of the model - they affect the degree to which the model reflects specular highlights. So if no lights are applied to the model, and thus there are no specular highlights at all, then the gloss map will be invisible.

More precisely, the gloss map affects the material specular color of the model. If the model already has a material specular color, then it is multiplied by the value (0-1) in the gloss map. In this way, the gloss map can effectively turn on or off the specularity on a per-pixel basis. If the model does not already have a material specular color specified, then applying a gloss map causes the material specular color to range from off to bright white.

A gloss map must be an alpha-texture:

ts = TextureStage('ts')
ts.setMode(TextureStage.MGloss)
smiley.setTexture(ts, tex)

If you wish, you can pack a modulate-texture and a gloss-map into a single RGBA texture. In that case, the RGB components are just regular color values that are modulated onto the model, and the A controls gloss:

ts = TextureStage('ts')
ts.setMode(TextureStage.MModulateGloss)
smiley.setTexture(ts, tex)

Currently, Panda3D only allows one gloss map per polygon, additional gloss maps will be ignored.

Gloss maps are new to Panda3D as of version 1.5.0. They only function when shader generation is enabled.

Glow Map Mode

A glow map is a black-and-white texture which indicates that a model should be glowing in certain areas. More specifically, the contents of the glow map are added to the material emission. This causes those parts of the model to be lit even when the lights are otherwise off.

In practice, though, things don't really look like they're glowing unless you apply a bloom filter to your scene. Panda can be made to copy the contents of your glow map into the framebuffer alpha, from which it can be read by the bloom filter. The bloom filter then really creates a dramatic glow effect. All of this is documented in the chaper on the bloom filter.

A glow map must be an alpha-texture:

ts = TextureStage('ts')
ts.setMode(TextureStage.MGlow)
smiley.setTexture(ts, tex)

If you wish, you can pack a modulate-texture and a glow-map into a single RGBA texture. In that case, the RGB components are just regular color values that are modulated onto the model, and the A controls glow:

ts = TextureStage('ts')
ts.setMode(TextureStage.MModulateGlow)
smiley.setTexture(ts, tex)

Currently, Panda3D only allows one glow map per polygon, additional glow maps will be ignored.

See also: Sample Programs: Glow Filter

Glow maps are new to Panda3D as of version 1.5.0. They only function when shader generation is enabled.


Panda3D Manual: Texture Order

When there are multiple textures in effect, depending on the Texture Blend Mode in use, it may be important to control the order in which the textures apply. For instance, although Modulate mode and Add mode are order-independent, texture order makes a big difference to Decal mode, Replace mode, and Blend mode.

To specify the texture order, use TextureStage.setSort() on one or more of your TextureStages. If you do not specify a sort value, the default sort value is 0. When the geometry is rendered, all of the textures are rendered in increasing order of sort value, such that the largest sort value is rendered on top. Thus, if you want to use Decal mode, for instance, to apply a texture on top of a lower texture, it would be a good idea to use setSort() to give a higher sort value to your decal texture.

Also, since some hardware might not be able to render all of the TextureStages that you have defined on a particular node, Panda provides a way for you to specify which texture(s) are the most important. Use TextureStage.setPriority() for this.

The priority value is only consulted when you have applied more TextureStages to a particular node than your current hardware can render. In this case, Panda will select the n textures with the highest priority value (and then sort them in order by the setSort() value). Between two textures with the same priority, Panda will prefer the one with the lower sort value. The default priority is 0.


Panda3D Manual: Texture Combine Modes
Some of the information in this page might not be correct.

See the Discussion page for more details.

In addition to the several Texture Blend Modes described previously, there is a more advanced interface on TextureStage that allows for a larger vocabulary of texture blending options.

Although several of the following options (CMReplace, CMModulate, CMAdd) have obvious parallels with the simpler blend modes described previously, they are in fact more powerful, because with each of the following you may specify the particular source or sources to be used for the operation; you are not limited to simply applying the operation to the top texture and the texture below.

RGB modes

The following specify the effect of the RGB (color) channels. A separate set of methods, below, specifies the effect of the alpha channel.

ts.setCombineRgb(TextureStage.CMReplace, source, operand)

This mode is similar to "replace mode". Whatever color is specified by source and operand becomes the new color.

ts.setCombineRgb(TextureStage.CMModulate, source0, operand0, source1, operand1)

This mode is similar to "modulate mode". The color from source0/operand0 is multiplied by the color from source1/operand1.

ts.setCombineRgb(TextureStage.CMAdd, source0, operand0, source1, operand1)

This mode is similar to "add mode". The color from source0/operand0 is added to the color from source1/operand1, and the result is clamped to 1 (white).

ts.setCombineRgb(TextureStage.CMAddSigned, source0, operand0, source1, operand1)

In this mode, the colors are added as signed numbers, and the result wraps.

ts.setCombineRgb(TextureStage.CMSubtract, source0, operand0, source1, operand1)

In this mode, source1/operand1 is subtracted from source0/operand0.

ts.setCombineRgb(TextureStage.CMInterpolate,
                 source0, operand0, source1, operand1, source2, operand2)

This is the only mode that uses three sources. The value of source2/operand2 is used to select between source0/operand0 and source1/operand1. When source2 is 0, source0 is selected, and when source2 is 1, source1 is selected. When source2 is between 0 and 1, the color is smoothly blended between source0 and source1.

Alpha modes

The following methods more-or-less duplicate the functionality of the above, but they control what happens to the alpha channel. Thus, you have explicit control over whether an alpha cutout in the top texture should produce an alpha cutout in the resulting object.

ts.setCombineAlpha(TextureStage.CMReplace, source, operand)
ts.setCombineAlpha(TextureStage.CMModulate, source0, operand0, source1, operand1)
ts.setCombineAlpha(TextureStage.CMAdd, source0, operand0, source1, operand1)
ts.setCombineAlpha(TextureStage.CMAddSigned, source0, operand0, source1, operand1)
ts.setCombineAlpha(TextureStage.CMSubtract, source0, operand0, source1, operand1)
ts.setCombineAlpha(TextureStage.CMInterpolate, source0, operand0, source1, operand1,
                   source2, operand2)

Source values

This table lists the legal values for any of source, source0, source1, or source2, in the above calls. This broadly gives you control over which two (or three) textures are used as inputs to the above combine modes.

TextureStage.CSTexture | The current, or "top" texture image.
TextureStage.CSConstant | A constant color, specified via TextureStage.setColor().
TextureStage.CSConstantColorScale | The same as CSConstant, but the color will be modified by NodePath.setColorScale().
TextureStage.CSPrimaryColor | The "primary" color of the object, before the first texture stage was applied, and including any lighting effects.
TextureStage.CSPrevious | The result of the previous texture stage; i.e. the texture below.
TextureStage.CSLastSavedResult | The result of any of the previous texture stages; specifically, the last stage for which TextureStage.setSavedResult(True) was called.

Operands

This table lists the legal values for any of operand, operand0, operand1, or operand2, in the above calls. This fine-tunes the channel data that is used from each texture input.

TextureStage.COSrcColor | Use the RGB color. When used in a setCombineAlpha() call, RGB is automatically aggregated into grayscale.
TextureStage.COOneMinusSrcColor | The complement of the RGB color.
TextureStage.COSrcAlpha | Use the alpha value. When used in a setCombineRgb() call, alpha is automatically expanded into uniform RGB.
TextureStage.COOneMinusSrcAlpha | The complement of the alpha value.


Panda3D Manual: Texture Transforms

It is possible to apply a matrix to transform the (u, v) texture coordinates of a model before rendering. In this way, you can adjust the position, rotation, or scale of a texture, sliding the texture around to suit your particular needs.

Use the following NodePath methods to do this:

nodePath.setTexOffset(TextureStage, uOffset, vOffset);
nodePath.setTexScale(TextureStage, uScale, vScale);
nodePath.setTexRotate(TextureStage, degrees);

If you don't have a particular TextureStage, use TextureStage.getDefault() as the first parameter.

Note that the operation in each case is applied to the (u, v) texture coordinates, not to the texture; so it will have the opposite effect on the texture. For instance, the call nodePath.setTexScale(ts, 2, 2) will effectively double the values of the texture coordinates on the model, which doubles the space over which the texture is applied, and thus makes the texture appear half as large.

The above methods apply a 2-d transform to your texture coordinates, which is appropriate, since texture coordinates are usually two-dimensional. However, sometimes you are working with 3-d texture coordinates, and you really do want to apply a 3-d transform. For those cases, there are the following methods:

nodePath.setTexPos(TextureStage, uOffset, vOffset, wOffset);
nodePath.setTexScale(TextureStage, uScale, vScale, wScale);
nodePath.setTexHpr(TextureStage, h, p, r);

And there is also one generic form:

nodePath.setTexTransform(TextureStage, transform);

This last method sets a generic TransformState object. This is the same kind of 4x4 transform matrix object that you can get from a NodePath via e.g., NodePath.getTransform(). You can also construct a new TransformState via a number of methods like TransformState.makePos(VBase3(0, 1, 0)). If you intend to apply a 2-d transform only, you should restrict yourself to methods like TransformState.makePos2d(VBase2(0, 1)); using only 2-d operations may allow the graphics backend to use a slightly simpler calculation.

Note that the texture transform is associated with a particular TextureStage; it is not a fixed property of the model or its texture coordinates. You can therefore apply a different texture transform to each different TextureStage, so that if you have multiple textures in effect on a particular node, they need not all be in the same place, even if they all use the same texture coordinates. For instance, this technique was used to generate the sample images in the Texture Blend Modes section. In fact, the following code was used to place this sample texture (excerpted):

smiley = loader.loadModel('smiley.egg')
ts = TextureStage('ts')
pattern = loader.loadTexture('color_pattern.png')
smiley.setTexture(ts, pattern)
smiley.setTexScale(ts, 8, 4)
smiley.setTexOffset(ts, -4, -2)

and the resulting texture:

In the above example, we have applied a scale of (8, 4) to reduce the size of the decal image substantially, and then we specified an offset of (-4, -2) to slide it around in the positive (u, v) direction to smiley's face (since the (0, 0) coordinate happens to be on smiley's backside). However, these operations affect only the decal image; the original smiley texture is unchanged from its normal position, even though both textures are using the same texture coordinates.


Panda3D Manual: Multiple Texture Coordinate Sets

In addition to simple texture transforms, it is also possible to have more than one set of texture coordinates on a model. Panda allows you to define as many different sets of texture coordinates as you like, and each set can be completely unrelated to all of the others.

When you have multiple texture coordinate sets (sometimes called multiple UV sets) on a model, each set will have its own name, which is any arbitrary string. The default texture coordinate set has no name (its name is the empty string).

Normally, you create multiple texture coordinate sets in the same modeling package that you use to create the model. Not all modeling packages, and not all Panda converters, support multiple texture coordinates. In fact, as of the time of this writing, only the Panda3D 1.1 version (or newer) of the maya2egg converter is known to convert multiple texture coordinates into Panda.

If you happen to have a model with multiple texture coordinate sets, you can specify which set a particular texture should use by calling TextureStage.setTexcoordName("name"). Remember, a TextureStage is used to apply a texture to a model, and so every texture will have an associated TextureStage (though most textures just use the default TextureStage). If you do not call this method for a particular TextureStage, the default behavior is to use the default, unnamed texture coordinate set.

The different TextureStages on a model might share the same texture coordinate sets, or they might each use a different texture coordinate set, or any combination.


Panda3D Manual: Automatic Texture Coordinates

In addition to using texture coordinates that are built into the model, it is also possible to generate texture coordinates at runtime. Usually you would use this technique to achieve some particular effect, such as projective texturing or environment mapping, but sometimes you may simply want to apply a texture to a model that does not already have texture coordinates, and this is the only way to do that.

The texture coordinates generated by this technique are generated on-the-fly, and are not stored within the model. When you turn off the generation mode, the texture coordinates cease to exist.

Use the following NodePath method to enable automatic generation of texture coordinates:

nodePath.setTexGen(TextureStage, texGenMode)

The texGenMode parameter specifies how the texture coordinates are to be computed, and may be any of the following options. In the list below, "eye" means the coordinate space of the observing camera, and "world" means world coordinates, e.g. the coordinate space of render, the root of the scene graph.

TexGenAttrib.MWorldPosition | Copies the (x, y, z) position of each vertex, in world space, to the (u, v, w) texture coordinates.
TexGenAttrib.MEyePosition | Copies the (x, y, z) position of each vertex, in camera space, to the (u, v, w) texture coordinates.
TexGenAttrib.MWorldNormal | Copies the (x, y, z) lighting normal of each vertex, in world space, to the (u, v, w) texture coordinates.
TexGenAttrib.MEyeNormal | Copies the (x, y, z) lighting normal of each vertex, in camera space, to the (u, v, w) texture coordinates.
TexGenAttrib.MEyeSphereMap | Generates (u, v) texture coordinates based on the lighting normal and the view vector to apply a standard reflection sphere map.
TexGenAttrib.MEyeCubeMap | Generates (u, v, w) texture coordinates based on the lighting normal and the view vector to apply a standard reflection cube map.
TexGenAttrib.MWorldCubeMap | Generates (u, v, w) texture coordinates based on the lighting normal and the view vector to apply a standard reflection cube map.
TexGenAttrib.MPointSprite | Generates (u, v) texture coordinates in the range (0, 0) to (1, 1) for large points so that the full texture covers the square. This is a special mode that should only be applied when you are rendering sprites, special point geometry that are rendered as squares. It doesn't make sense to apply this mode to any other kind of geometry. Normally you wouldn't set this mode directly; let the SpriteParticleRenderer do it for you.
TexGenAttrib.MLightVector | Generates special (u, v, w) texture coordinates that represent the vector from each vertex to a particular Light in the scene graph, in each vertex's tangent space. This is used to implement normal maps. This mode requires that each vertex have a tangent and a binormal computed for it ahead of time; you also must specify the NodePath that represents the direction of the light. Normally, you wouldn't set this mode directly either; use NodePath.setNormalMap(), or implement normal maps using programmable shaders.

Note that several of the above options generate 3-D texture coordinates: (u, v, w) instead of just (u, v). The third coordinate may be important if you have a 3-D texture or a cube map (described later), but if you just have an ordinary 2-D texture the extra coordinate is ignored. (However, even with a 2-D texture, you might apply a 3-D transform to the texture coordinates, which would bring the third coordinate back into the equation.)

Also, note that almost all of these options have a very narrow purpose; you would generally use most of these only to perform the particular effect that they were designed for. This manual will discuss these special-purpose TexGen modes in later sections, as each effect is discussed; for now, you only need to understand that they exist, and not worry about exactly what they do.

The mode that is most likely to have general utility is the first one: MWorldPosition. This mode converts each vertex's (x, y, z) position into world space, and then copies those three numeric values to the (u, v, w) texture coordinates. This means, for instance, that if you apply a normal 2-D texture to the object, the object's (x, y) position will be used to look up colors in the texture.

For instance, the teapot.egg sample model that ships with Panda has no texture coordinates built in the model, so you cannot normally apply a texture to it. But you can enable automatic generation of texture coordinates and then apply a texture:

teapot = loader.loadModel('teapot.egg')
tex = loader.loadTexture('maps/color-grid.rgb')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
teapot.setTexture(tex)

And you end up with something like this:

Teapot with a grid applied

You can use this in conjunction with a texture transform to further manipulate the texture coordinates. For instance, to rotate the texture 90 degrees, you could do something like this:

teapot.setTexTransform(TextureStage.getDefault(), TransformState.makeHpr(VBase3(0, 90, 0)))

Teapot with a grid applied, rotated

Finally, consider that the only two choices for the coordinate frame of the texture coordinate generation are "world" and "eye", for the root NodePath and the camera NodePath, respectively. But what if you want to generate the texture coordinates relative to some other node, say the teapot itself? The above images are all well and good for a teapot that happens to be situated at the origin, but suppose we want the teapot to remain the same when we move it somewhere else in the world?

If you use only MWorldPosition, then when you change the teapot's position, for instance by parenting it to a moving node, the teapot will seem to move while its texture pattern stays in place--maybe not the effect you had in mind. What you probably intended was for the teapot to take its texture pattern along with it as it moves around. To do this, you will need to compute the texture coordinates in the space of the teapot node, rather than in world space.

Panda3D provides the capability to generate texture coordinates in the coordinate space of any arbitrary node you like. To do this, use MWorldPosition in conjunction with Panda's "texture projector", which applies the relative transform between any two arbitrary NodePaths to the texture transform; you can use it to compute the relative transform from world space to teapot space, like this:

teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
teapot.setTexProjector(TextureStage.getDefault(), render, teapot);

It may seem a little circuitous to convert the teapot vertices to world space to generate the texture coordinates, and then convert the texture coordinates back to teapot space again--after all, didn't they start out in teapot space? It would have saved a lot of effort just to keep them there! Why doesn't Panda just provide an MObjectPosition mode that would convert texture coordinates from the object's native position?

That's a fair question, and MObjectPosition would be a fine idea for a model as simple as the teapot, which is after all just one node. But for more sophisticated models, which can contain multiple sub-nodes each with their own coordinate space, the idea of MObjectPosition is less useful, unless you truly wanted each sub-node to be re-textured within its own coordinate space. Rather than provide this feature of questionable value, Panda3D prefers to give you the ability to specify the particular coordinate space you had in mind, unambiguously.

Note that you only want to call setTexProjector() when you are using mode MWorldPosition. The other modes are generally computed from vectors (for instance, normals), not positions, and it usually doesn't makes sense to apply a relative transform to a vector.


Panda3D Manual: Projected Textures

In a previous section, we introduced ways to apply an explicit transformation to a model's texture coordinates, with methods like setTexOffset() and setTexScale(). In addition to this explicit control, Panda3D offers a simple mechanism to apply an automatic texture transform each frame, as computed from the relative transform between any two nodes.

nodePath.setTexProjector(textureStage, fromNodePath, toNodePath)

When you have enabled this mode, the relative scene-graph transform from fromNodePath to toNodePath--that is, the result of fromNodePath.getTransform(toNodePath)--is automatically applied as a texture-coordinate transform to the indicated textureStage. The result is more-or-less as if you executed the following command every frame:

nodePath.setTexTransform(textureStage, fromNodePath.getTransform(toNodePath))

There is no need for either fromNodePath or toNodePath to have any relation to the nodePath that is receiving the setTexProjector() call; they can be any two arbitrary NodePaths. If either of them is just NodePath(), it stands for the top of the graph.

This has several useful applications. We have already introduced one application, in conjunction with MWorldPosition, to move the generated texture coordinates from the root of the graph to the model itself.

Interval-animated texture transforms

Another handy application for a TexProjector is to enable the use of the various LerpIntervals to animate a texture transform. Although there are no LerpIntervals that directly animate texture transforms, you can make a LerpInterval animate a NodePath--and then set up a TexProjector effect to follow that NodePath. For example:

smiley = loader.loadModel('smiley.egg')
lerper = NodePath('lerper')
smiley.setTexProjector(TextureStage.getDefault(), NodePath(), lerper)
i = lerper.posInterval(5, VBase3(0, 1, 0))
i.loop()

Note that you don't even have to parent the animated NodePath into the scene graph. In the above example, we have set up the interval i to repeatedly move the standalone NodePath lerper from position (0, 0, 0) to (0, 1, 0) over 5 seconds. Since smiley is assigned a TexProjector that copies the relative transform from NodePath() to lerper--that is, the net transform of lerper--it means we are really animating the texture coordinates on smiley from (0, 0) to (0, 1) (the Z coordinate is ignored for an ordinary 2-D texture).

Projected Textures

Another useful application of the TexProjector is to implement projected textures--that is, a texture applied to geometry as if it has been projected from a lens somewhere in the world, something like a slide projector. You can use this to implement a flashlight effect, for instance, or simple projected shadows.

This works because the TexProjector effect does one additional trick: if the second NodePath in the setTexProjector() call happens to be a LensNode, then the TexProjector automatically applies the lens's projection matrix to the texture coordinates (in addition to applying the relative transform between the nodes).

To implement projected textures, you need to do three steps:

1. Apply the texture you want to the model you want to project it onto, usually on its own TextureStage, so that it is multitextured.

2. Put the MWorldPosition TexGen mode on the model. This copies the model's vertex positions into its texture coordinates, for your texture's TextureStage.

3. Call model.setTexProjector(textureStage, NodePath(), projector), where projector is the NodePath to the LensNode you want to project from.

For your convenience, the NodePath class defines the following method that performs these three steps at once:

nodePath.projectTexture(textureStage, texture, lensNodePath)

For instance, we could use it to project the bamboo texture ("envir-reeds.png") onto the ripple.egg model, like this:

Bamboo projected onto ripple

You could move around the projector in the world, or even change the lens field of view, and the bamboo image would follow it. (In the above image, the camera model and the projection lines are made visible only for illustration purposes; normally you wouldn't see them.)

This image was generated with the following code (excerpted; click here for the complete program):

ripple = Actor.Actor('ripple.egg')
ripple.reparentTo(render)

proj = render.attachNewNode(LensNode('proj'))
lens = PerspectiveLens()
proj.node().setLens(lens)
proj.reparentTo(render)
proj.setPos(1.5, -7.3, 2.9)
proj.setHpr(22, -15, 0)

tex = loader.loadTexture('maps/envir-reeds.png')
ts = TextureStage('ts')
ripple.projectTexture(ts, tex, proj)


Panda3D Manual: Simple Environment Mapping

There is a classic technique in real-time computer graphics for making objects appear shiny or reflective. It's called environment mapping or sometimes reflection mapping or, in this case, sphere mapping.

Environment mapping is not ray tracing. But it's a cheesy way to get a similar effect. The idea for both of them is that, mathematically, it's easy to calculate the direction from which a ray of light must have been coming before it bounced off a particular point of a shiny object and entered your eye. If the renderer were using ray tracing, it would follow this ray, for each point on your shiny object, backwards from your eye, and determine what object in the environment the ray came from; and that's what you'd see in the reflection.

Ray tracing is still too computation-intensive to be done in real time. But a reflection vector is easy to calculate per-vertex, and if we could turn a reflection vector into a (u, v) texture coordinate pair, the graphics hardware is particularly good at looking up the color in a texture image that corresponds to that (u, v) pair. So all we need is an image that shows the objects in our environment.

In sphere mapping, the 3-D reflection vector is turned into a 2-D texture coordinate pair by mathematically applying a spherical distortion. This means the environment map should be a view of the world as seen through a 360-degree fisheye lens, or as reflected in a shiny ball like a holiday ornament. You can see why it is called sphere mapping.

bvw-f2004--streetscene as a sphere map

Panda3D can generate sphere maps for you. The above sphere map was generated with the following code:

scene = loader.loadModel('bvw-f2004--streetscene/street-scene.egg')
scene.reparentTo(render)
scene.setZ(-2)
base.saveSphereMap('streetscene_env.jpg', size = 256)

The idea is simply to put the camera in the middle of your environment, approximately where your shiny object would be. Then just call base.saveSphereMap(), and a suitable sphere map image will be generated and written to disk for you. Note that this feature is new as of Panda3D 1.1.

Now you can apply the environment map to just about any object you like. For instance, the teapot:

tex = loader.loadTexture('streetscene_env.jpg')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MEyeSphereMap)
teapot.setTexture(tex)

The teapot with sphere map

In this example, you can see that the key to sphere mapping in Panda is to set the TexGen mode to MEyeSphereMap. This mode computes a spherical (u, v) texture coordinate pair based on the reflection vector for each vertex of the teapot. In order for this to work, your model must have normals defined for all its vertices (the teapot has good normals).

Shiny teapots are one thing, but it would be nice to make something like, say, a car look shiny. We could just do exactly the same thing as above, but our car has a texture map already. If we just replace the texture map with the environment map we'll end up with a chrome car:

car = loader.loadModel('bvw-f2004--carnsx/carnsx.egg')
tex = loader.loadTexture('streetscene_env.jpg')
car.setTexGen(TextureStage.getDefault(), TexGenAttrib.MEyeSphereMap)
car.setTexture(tex, 1)

The car with sphere map

That looks pretty silly. So we'd really prefer to use multitexture to apply both the car's regular texture, and layer a little bit of shine on top of that. We'll use Add mode to add the environment map to the existing color, which is appropriate for a shiny highlight on an object.

In order to use Add mode without oversaturating the colors, we need to darken the environment map substantially. We could use any image processing program to do this; for this example, we'll use Panda3D's image-trans utility:

image-trans -cscale 0.2 -o streetscene_env_dark.jpg streetscene_env.jpg

So the new map looks like this:

The darkened environment map

While we're fixing things up, let's move the wheels to a different node, so we can assign the shine just to the metal and glass body of the car:

car = loader.loadModel('bvw-f2004--carnsx/carnsx.egg')
body = car.find('**/body')
body.findAllMatches('**/FL_wheel*').reparentTo(car)

And now the shine is applied like this:

tex = loader.loadTexture('streetscene_env_dark.jpg')
ts = TextureStage('env')
ts.setMode(TextureStage.MAdd)
body.setTexGen(ts, TexGenAttrib.MEyeSphereMap)
body.setTexture(ts, tex)

The car with color and shine together

Note that the shiny highlights are now quite subtle, but still compelling, especially when you see the car move.

The sphere map technique isn't perfect. The biggest problem with it is that you have to prepare it ahead of time, which means you have to know exactly what will be reflected in your shiny objects--it's impossible for an object to reflect a dynamic object (for instance, an adjacent car).

Another problem is that the point-of-view is baked into the sphere map, so that if the camera were to swing around to view the car from the other side, the things you could see in the reflection would still be the objects behind the camera on this side.

Both of these problems can be solved by cube mapping, which is a more advanced technique for, among other things, applying environment maps. However, cube maps aren't always ideal; very often, the venerable sphere map really is the best choice.

It is rare that an application presents a closeup view of a smooth, round mirrored object in which you can see reflections clearly, like the teapot example above; usually, reflections are just a subtle glinting on the surface, like the car. In these cases the sphere map is ideal, since it is not so important exactly what the reflections are, but simply that there are reflections. And the sphere map is the easiest and fastest way to render reflections.


Panda3D Manual: 3-D Textures

So far, we have only talked about ordinary 2-D texture maps. Beginning with version 1.1, Panda3D also supports the concept of a 3-D texture map. This is a volumetric texture: in addition to a height and a width, it also has a depth:

A sample 3-D texture.

The 3-D texture image is solid all the way through; if we were to cut away part of the cube we would discover that the checkerboard pattern continues within:

A sample 3-D texture on a sphere.

This is true no matter what shape we carve out of the cube:

A sample 3-D texture on a teapot.

In addition to the usual u and v texture dimensions, a 3-D texture also has w. In order to apply a 3-D texture to geometry, you will therefore need to have 3-D texture coordinates (u, v, w) on your geometry, instead of just the ordinary (u, v).

There are several ways to get 3-D texture coordinates on a model. One way is to assign appropriate 3-D texture coordinates to each vertex when you create the model, the same way you might assign 2-D texture coordinates. This requires that your modeling package (and its Panda converter) support 3-D texture coordinates; however, at the time of this writing, none of the existing Panda converters currently do support 3-D texture coordinates.

More commonly, 3-D texture coordinates are assigned to a model automatically with one of the TexGen modes, especially MWorldPosition. For example, to assign 3-D texture coordinates to the teapot, you might do something like this:

teapot = loader.loadModel('teapot.egg')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
teapot.setTexProjector(TextureStage.getDefault(), render, teapot)
teapot.setTexPos(TextureStage.getDefault(), 0.44, 0.5, 0.2)
teapot.setTexScale(TextureStage.getDefault(), 0.2)

The above assigns 3-D texture coordinates to the teapot based on the (x, y, z) positions of its vertices, which is a common way to assign 3-D texture coordinates. The setTexPos() and setTexScale() calls in the above are particular to the teapot model; these numbers are chosen to scale the texture so that its unit cube covers the teapot.

Storing 3-D texture maps on disk is a bit of a problem, since most image formats only support 2-D images. By convention, then, Panda3D will store a 3-D texture image by slicing it into horizontal cross-sections and writing each slice as a separate 2-D image. When you load a 3-D texture, you specify a series of 2-D images which Panda3D will load and stack up like pancakes to make the full 3-D image.

The above 3-D texture image, for instance, is stored as four separate image files:

The four images that make up the 3-D Texture

Note that, although the image is stored as four separate images on disk, internally Panda3D stores it as a single, three-dimensional image, with height, width, and depth.

The Panda3D convention for naming the slices of a 3-D texture is fairly rigid. Each slice must be numbered, and all of the filenames must be the same except for the number; and the first (bottom) slice must be numbered 0. If you have followed this convention, then you can load a 3-D texture with a call like this:

tex = loader.load3DTexture("grid_#.png")

The hash sign ("#") in the filename passed to loader.load3DTexture() will be filled in with the sequence number of each slice, so the above loads files named "grid_0.png", "grid_1.png", "grid_2.png", and so on. If you prefer to pad the slice number with zeros to a certain number of digits, repeat the hash sign; for instance, loading "grid_###.png" would look for files named "grid_000.png", "grid_001.png", and so on. Note that you don't have to use multiple hash marks to count higher than 9. You can count as high as you like even with only one hash mark; it just won't pad the numbers with zeros.

Remember that you must usually choose a power of two for the size of your texture images. This extends to the w size, too: for most graphics cards, the number of slices of your texture should be a power of two. Unlike the ordinary (u, v) dimensions, Panda3D won't automatically rescale your 3-D texture if it has a non-power-of-two size in the w dimension, so it is important that you choose the size correctly yourself.

Applications for 3-D textures

3-D textures are often used in scientific and medical imagery applications, but they are used only rarely in 3-D game programs. One reason for this is the amount of memory they require; since a 3-D texture requires storing (u × v × w) texels, a large 3-D texture can easily consume a substantial fraction of your available texture memory.

But probably the bigger reason that 3-D textures are rarely used in games is that the texture images in games are typically hand-painted, and it is difficult for an artist to paint a 3-D texture. It is usually much easier just to paint the surface of an object.

So when 3-D textures are used at all, they are often generated procedurally. One classic example of a procedural 3-D texture is wood grain; it is fairly easy to define a convincing woodgrain texture procedurally. For instance, click here to view a Panda3D program that generates a woodgrain texture and stores it as a series of files named woodgrain_0.png, woodgrain_1.png, and so on. The following code applies this woodgrain texture to the teapot, to make a teapot that looks like it was carved from a single block of wood:

teapot = loader.loadModel('teapot.egg')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
teapot.setTexProjector(TextureStage.getDefault(), render, teapot)
teapot.setTexPos(TextureStage.getDefault(), 0.44, 0.5, 0.2)
teapot.setTexScale(TextureStage.getDefault(), 0.2)

tex = loader.load3DTexture('woodgrain_#.png')
teapot.setTexture(tex)

A wooden teapot.

However, even procedurally-generated 3-D textures like this are used only occasionally. If the algorithm to generate your texture is not too complex, it may make more sense to program a pixel shader to generate the texture implicitly, as your models are rendered.

Still, even if it is used only occasionally, the 3-D texture remains a powerful rendering technique to keep in your back pocket.


Panda3D Manual: Cube Maps

There is one more special kind of texture map: the cube map, which is introduced in Panda3D version 1.1. A cube map is similar to a 3-D texture, in that it requires 3-D texture coordinates (u, v, w); also, a cube map is stored on disk as a sequence of ordinary 2-D images.

But unlike a 3-D texture, which is defined by stacking up an arbitrary number 2-D images like pancakes to fill up a volume, a cube map is always defined with exactly six 2-D images, which are folded together to make a cube.

The six images of a cube map are numbered from 0 to 5, and each image corresponds to one particular face of the cube:

image 0 | The +u (or +x) face (right)
image 1 | The -u (or -x) face (left)
image 2 | The +v (or +y) face (forward)
image 3 | The -v (or -y) face (back)
image 4 | The +w (or +z) face (up)
image 5 | The -w (or -z) face (down)

By +x face, we mean the face of the cube farthest along the positive X axis. In Panda3D's default Z-up coordinate system, this is the right face. Similarly, the -x face is the face farthest along the negative X axis, or the left face, and so on for the Y and Z faces. Since the coordinates of a texture map are called (u, v, w) instead of (x, y, z), it is technically more correct to call these the +u and -u faces, though it is often easier to think of them as +x and -x.

The faces are laid out according to the following diagram:

The arrangement of the six faces of a cube map

Imagine that you cut out the above diagram and folded it into a cube. You'd end up with something like this:

A solid-mapped cube

Note that, when you hold the cube so that the axis indications for each face are in the appropriate direction (as in the picture above), several of the faces are upside-down or sideways. That's because of the way the graphics card manufacturers decided to lay out the cube map faces (and also because of Panda3D's default coordinate system). But in fact, it doesn't matter which way the faces are oriented, as long as you always generate your cube map images the same way.

In some sense, a cube map is a kind of surface texture, like an ordinary 2-D texture. But in other sense, it is also volumetric like a 3-D texture: every point within the 3-D texture coordinate space is colored according to the face of the cube it comes closest to. A sphere model with the cube map applied to it would pick up the same six faces:

A wireframe cube, showing the internal mapped space

Note that, while a 3-D texture assigns a different pixel in the texture to every point within a volume, a cube map assigns a different pixel in the texture to every direction from the center.

You can load a cube map from a series of six image files, very similar to the way you load a 3-D texture:

tex = loader.loadCubeMap('cubemap_#.png')

As with a 3-D texture, the hash mark ("#") in the filename will be filled in with the image sequence number, which in the case of a cube map will be a digit from 0 to 5. The above example, then, will load the six images "cubemap_0.png", "cubemap_1.png", "cubemap_2.png", "cubemap_3.png", "cubemap_4.png", and "cubemap_5.png", and assemble them into one cube map.


Panda3D Manual: Environment Mapping with Cube Maps

Although there are other applications for cube maps, one very common use of cube maps is as an environment map, similar to sphere mapping. In fact, it works very much the same as sphere mapping.

Just as with a sphere map, you can have Panda3D generate a cube map for you:

scene = loader.loadModel('bvw-f2004--streetscene/street-scene.egg')
scene.reparentTo(render)
scene.setZ(-2)
base.saveCubeMap('streetscene_cube_#.jpg', size = 256)

Click here to see the six images generated by the above sample code.

With the cube map saved out as above, you could apply it as an environment map to the teapot like this:

tex = loader.loadCubeMap('streetscene_cube_#.jpg')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MEyeCubeMap)
teapot.setTexture(tex)

And the result looks very similar to the sphere map:

The cube map on a teapot.

In fact, it looks so similar that one might wonder why we bothered. So far, a cube map looks pretty similar to a sphere map, except that it consumes six times the texture memory. Hardly impressive.

But as we mentioned earlier, there are two problems with sphere maps that cube maps can solve. One of these problems is that the point-of-view is permanently baked into the sphere map. Cube maps don't necessarily have the same problem. In fact, we can solve it with one simple variation:

tex = loader.loadCubeMap('streetscene_cube_#.jpg')
teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldCubeMap)
teapot.setTexture(tex)

By changing MEyeCubeMap to MWorldCubeMap, we have indicated that we would like this cube map to vary its point-of-view as the camera moves. Now the reflected environment will vary according to the direction we are looking at it, so that it shows what is behind the camera at runtime, instead of always showing the area behind the camera when the cube map was generated, as a sphere map must do. In order for this to work properly, you should ensure that your camera is unrotated (that is, setHpr(0, 0, 0)) when you generate the cube map initially.

Even with MWorldCubeMap, though, the image is still generated ahead of time, so the reflection doesn't actually show what is behind the camera at runtime. It just uses the current camera direction to figure out what part of the reflection image to show.

However, you can make a cube map that truly does reflect dynamic objects in the scene, by rendering a dynamic cube map. This will be discussed in the next section.


Panda3D Manual: Automatic Texture Animation

It's possible to generate a model that automatically rotates through a sequence of textures when it is in the scene graph, without having to run a special task to handle this.

To do this, use the egg-texture-cards command-line utility. This program will accept a number of texture filenames on the command line, and output an egg file that rotates through each texture at the specified frame rate:

egg-texture-cards -o flip.egg -fps 30 explosion*.jpg

This actually creates a model with a different polygon for each frame of the texture animation. Each polygon is put in a separate node, and all the nodes are made a child of a special node called a SequenceNode.

The SequenceNode is a special node that only draws one of its children at a time, and it rotates through the list of children at a particular frame rate. You can parent the model under render and it will automatically start animating through its textures. If you need it to start at a particular frame, use something like this:

flip = loader.loadModel('flip.egg')
flip.find('**/+SequenceNode').node().pose(startFrame)
flip.reparentTo(render)

By default, all of the polygons created by egg-texture-cards will have the same size. This means that all of your textures must be the same size as well. While this is a simple configuration, it may not be ideal for certain effects. For instance, to animate an explosion, which starts small and grows larger, it would be better to use a small texture image on a small polygon when the image is small, and have a larger image on a larger polygon when it grows larger. You can achieve this effect, with the -p parameter; specifying -p scales each frame's polygon in relation to the size of the corresponding texture.

egg-texture-cards -o flip.egg -fps 30 -p 240,240 explosion*.jpg

There are several other parameters as well; use egg-texture-cards -h for a complete list.


Panda3D Manual: Playing MPG and AVI files

Panda now supports AVI format for textures in Panda.

Usage

myMovieTexture=loader.loadTexture("myMovie.avi")
myObject.setTexture(myMovieTexture)

The movie subsystem is implemented using FFMPEG. Therefore, it supports all of the formats that FFMPEG supports. The functions to control the movie are as follows:

movie.play()
movie.stop()
movie.setTime(t)
movie.getTime()
movie.setLoopCount(n)
movie.getLoopCount()
movie.setPlayRate(speed)
movie.getPlayRate()
movie.isPlaying()

If you want to hear the movie's audio as well, you need to load it twice: once as a texture, and once as a sound file:

mySound=loader.loadSfx("myMovie.avi")

Then, you can synchonize the video to the audio:

myMovieTexture.synchronizeTo(mySound)

From that point forward, playing the audio will cause the texture to update. This is more accurate than synchronizing the video manually.

For powers-of-two limited graphics hardware

If your graphics hardware does not support non power-of-two texture, your movie texture size would be shifted up to the next larger power of two size. For example, it you have a movie of 640 x 360 in size, the generated texture would be actually 1024 x 512. The result is a texture that contains a movie in the lower-left corner, and a black pad region to the right and upper portion of the texture.

To work around this limit, you have to display just the lower-left portion of the texture. To see a complete demonstration of this process, see the Media Player sample program.

Issues

The video texure works by decoding on a frame by frame basis and copying into the texture buffer. As such, it is inadvisable to use more than a few high res video textures at the same time.

Certain encoding formats do not work. So far, DV format has been determined incompatible with Panda.


Panda3D Manual: Transparency and Blending

Don't use Image Files with Alpha Channels

There are several image file formats that can store an alpha channel. Among these are PNG, TIF, TGA, and others. However, the programs that create these image files (such as Photoshop) have such unpredictable and unreliable behavior that it's more trouble than its worth.

The following section summarizes the difficulties associated with creating alpha channels in Photoshop. The section after that describes an easier method: storing opacity maps in a separate file.

Creating Alpha Channels in Photoshop

Photoshop has three separate, unrelated ways to store transparency:

Using an alpha channel: using the Photoshop "Layers and Channels" dialog, select the channels tab, and then click the "Create new Channel" button.

Using a layer mask: using the Photoshop "Layers and Channels" dialog, select a layer, then using the "Layer" menu, select "Layer Mask/Reveal All."

Using cutout transparency: use the eraser tool to create transparency. Or, select an area of the image, and then click "delete."

Unfortunately, when trying to export an image from Photoshop and import it into Panda, data is often lost in the process. The following table shows which export/import procedures work, and which ones fail:

Image Format | Transparency Type | Results
PNG | Alpha | Photoshop export loses data
PNG | Layer Mask | Success!
PNG | Cutout | Photoshop export loses data
TGA | Alpha | Panda import loses data
TGA | Layer Mask | Photoshop export loses data
TGA | Cutout | Photoshop export loses data
TIF | Alpha | Success!
TIF | Layer Mask | Panda import loses data
TIF | Cutout | Panda import loses data

Even the ones that yielded "success" were not necessarily 100% straightforward. These image files that load correctly into Panda3D do not always load back into Photoshop.

Storing Opacity Maps Separately

A better way, that avoids the difficulties associated with Photoshop and alpha channels, is to store your opacity maps in a separate file.

When loading a texture, Panda3D can load a pair of files together, like this:

tex = loader.loadTexture("mytexture-color.jpg", "mytexture-opacity.jpg")

The idea here is that mytexture-color is an ordinary RGB (not RGBA) color map, and mytexture-opacity is a black-and-white image representing opacity. The two images must both be the same size. When loading, Panda3D will combine the two images together into a single RGBA texture.

Panda has model exporters for a number of different 3D modeling programs. Some of those exporters are better than others. The best among them will make this process automatic. For example, if you apply a color map and an opacity map to your model in Maya, then the maya exporter will automatically put annotations into the EGG file indicating that those textures should be merged at load time.

Dealing with Depth-Sorting

Note: this page is cut-and-pasted from a howto we found. We'll polish it later.

Usually transparency works as expected in Panda automatically, but sometimes it just seems to go awry, where a semitransparent object in the background seems to partially obscure a semitransparent object in front of it. This is especially likely to happen with large flat polygon cutouts, or when a transparent object is contained within another transparent object, or when parts of a transparent object can be seen behind other parts of the same object.

The fundamental problem is that correct transparency, in the absence of special hardware support involving extra framebuffer bits, requires drawing everything in order from farthest away to nearest. This means sorting each polygon--actually, each pixel, for true correctness--into back-to-front order before drawing the scene.

It is, of course, impossible to split up every transparent object into individual pixels or polygons for sorting individually, so Panda sorts objects at the Geom level, according to the center of the bounding volume. This works well 95% of the time.

You run into problems with large flat polygons, though, since these tend to have parts that are far away from the center of their bounding volume. The bounding-volume sorting is especially likely to go awry when you have two or more large flats close behind the other, and you view them from slightly off-axis. (Try drawing a picture, of the two flats as seen from the top, and imagine yourself viewing them from different directions. Also imagine where the center of the bounding volumes is.)

Now, there are a number of solutions to this sort of problem. No one solution is right for every situation.

First, the easiest thing to do is to use M_dual transparency. This is a special transparency mode in which the completely invisible parts of the object aren't drawn into the Z-buffer at all, so that they don't have any chance of obscuring things behind them. This only works well if the flats are typical cutouts, where there is a big solid part (alpha == 1.0) and a big transparent part (alpha == 0.0), and not a lot of semitransparent parts (0.0 < alpha < 1.0). It is also a slightly more expensive rendering mode than the default of M_alpha, so it's not enabled by default in Panda. But egg-palettize will turn it on automatically for a particular model if it detects textures that appear to be cutouts of the appropriate nature, which is another reason to use egg-palettize if you are not using it already.

If you don't use egg-palettize (you really should, you know), you can just hand-edit the egg files to put the line:

 <Scalar> alpha { dual }

within the <Texture> reference for the textures in question.

A second easy option is to use M_multisample transparency, which doesn't have any ordering issues at all, but it only looks good on very high-end cards that have special multisample bits to support full-screen antialiasing. Also, at the present it only looks good on these high-end cards in OpenGL mode (since our pandadx drivers don't support M_multisample explicitly right now). But if M_multisample is not supported by a particular hardware or panda driver, it automatically falls back to M_binary, which also doesn't have any ordering issues, but it always has jaggy edges along the cutout edge. This only works well on texture images that represent cutouts, like M_dual, above.

If you use egg-palettize, you can engage M_multisample mode by putting the keyword "ms" on the line with the texture(s). Without egg-palettize, hand-edit the egg files to put the line:

 <Scalar> alpha { ms }

within the <Texture> reference for the textures in question.

A third easy option is to chop up one or both competing models into smaller pieces, each of which can be sorted independently by Panda. For instance, you can split one big polygon into a grid of little polygons, and the sorting is more likely to be accurate for each piece (because the center of the bounding volume is closer to the pixels). You can draw a picture to see how this works. In order to do this properly, you can't just make it one big mesh of small polygons, since Panda will make a mesh into a single Geom of tristrips; instead, it needs to be separate meshes, so that each one will become its own Geom. Obviously, this is slightly more expensive too, since you are introducing additional vertices and adding more objects to the sort list; so you don't want to go too crazy with the smallness of your polygons.

A fourth option is simply to disable the depth write on your transparent objects. This is most effective when you are trying to represent something that is barely visible, like glass or a soap bubble. Doing this doesn't improve the likelihood of correct sorting, but it will tend to make the artifacts of an incorrect sorting less obvious. You can achieve this by using the transparency option "blend_no_occlude" in an egg file, or by explicitly disabling the depth write on a loaded model with node_path.set_depth_write(false). You should be careful only to disable depth write on the transparent pieces, and not on the opaque parts.

A final option is to make explicit sorting requests to Panda. This is often the last resort because it is more difficult, and doesn't generalize well, but it does have the advantage of not adding additional performance penalties to your scene. It only works well when the transparent objects can be sorted reliably with respect to everything else behind them. For instance, clouds in the sky can reliably be drawn before almost everything else in the scene, except the sky itself. Similarly, a big flat that is up against an opaque wall can reliably be drawn after all of the opaque objects, but before any other transparent object, regardless of where the camera happens to be placed in the scene. See howto.control_render_order.txt for more information about explicitly controlling the rendering order.


Panda3D Manual: Texture Management

Different graphics cards provide different amounts of texture memory. If you're loading a lot of different textures, especially if they're large, you can easily consume all of your available texture memory. In principle, this shouldn't cause problems, as long as you don't have all of your textures onscreen at once: both OpenGL and DirectX are supposed to automatically evict textures from graphics memory as needed.

In practice, it doesn't always work this cleanly. In some integrated graphics cards, the "graphics memory" is actually your system memory, so the graphics driver never needs to evict textures--but if you load too many textures, there may not be enough memory left for your application. Furthermore, some graphics drivers have major bugs that manifest as you start to approach the limit of your graphics memory, causing strange rendering artifacts or even crashes.

For these reasons, it may be useful to limit the amount of graphics memory your application uses. Panda provides several tools to help with this.

Automatically reducing textures

You can reduce textures automatically as they are loaded. The easiest way to do this is to put a line like:

texture-scale 0.5

in your Config.prc file. The above example will scale all textures by a factor of 0.5 in each dimension (for an overall reduction to 1/4 of the total memory requirement). If there are certain textures that should not be scaled, for instance GUI textures, you can exclude them with lines like:

exclude-texture-scale digits_*.png
exclude-texture-scale gui*.jpg

Enabling texture compression

Another possibility is to enable and use Texture Compression, as described on the next page. If supported by your graphics card, this will reduce texture memory requirements dramatically, to 1/4 or 1/8 of the original. There is some reduction of quality, but not as much as the quality reduction you'd get from downscaling the textures by the equivalent amount. It is also possible to enable texture compression in conjunction with texture scaling.

Limiting graphics memory usage overall

Finally, it may be prudent to limit the amount of graphics memory that Panda attempts to use, with a line like:

graphics-memory-limit 67108864

The above example imposes a limit of 64MB (64 * 1024 * 1024) on the graphics memory that Panda will attempt to use. This can be a good idea to avoid allocating runaway textures on integrated graphics cards with no fixed texture limit, or to work around buggy graphics drivers that crash when you use too much. Panda will automatically start to unload textures when the specified limit is exceeded, even if the graphics driver would allow allocating more.

Ideally, it would be great to query the amount of useful graphics memory provided by the card, and set this as the graphics-memory-limit; unfortunately, this is impractical for several reasons, including the reasons given above. Typically, if you wish your application to work on a variety of hardware, you will need to come up with a handful of default settings and allow the user to select between them, according to his own knowledge of his hardware capabilities.

Note that graphics-memory-limit is a new feature of Panda, and is not available in versions before Panda3D 1.6.

Monitoring memory usage

You can see how much graphics memory you are actually consuming with the PStats tool. Select the "Graphics memory" option. This graph will show the amount of memory required for active (onscreen), and inactive (offscreen) textures. It also includes memory required for vertex and index buffers, though these are typically much smaller than your texture memory requirements.


Panda3D Manual: Texture Compression

You may be familiar with image file formats like JPEG, which can compress image data dramatically and make image files much smaller than they would be otherwise, in return for sacrificing a tiny bit of the original image quality.

JPEG compression only applies onto to the image size on disk, however. Once you load a texture image into memory, whether it came from a JPEG, PNG, or TGA file, it always takes up the same amount of texture memory on your graphics card (based on the size of the texture).

There is a different option, however, for compressing texture images in-memory. Most modern graphics cards can use a handful of run-time compression algorithms to render directly from a texture that has been compressed in-memory. This is called DXT compression. It's not really very much like JPEG compression internally, but you can think of it in the same way. It does have some things in common: it reduces the size of the image dramatically (4 times or even 8 times smaller), and it sacrifices a tiny bit of image quality.

Runtime texture compression

The easiest way to enable compressed texture images is to put the following in your Config.prc file:

compressed-textures 1

This will ask your graphics driver to compress each texture as it loads. This means it takes a tiny bit longer to load each texture, but the resulting texture will take up much less space in texture memory.

There's one important advantage to compressing the textures at runtime this way: the graphics driver will be able to compress all the textures using whatever texture compression algorithm it understands, DXT or otherwise. Not all graphics cards support all compression algorithms, so using this option allows the driver to choose the best algorithm it supports. If the graphics driver doesn't support any compression algorithms at all, it will simply load the textures uncompressed. Either way, your application will still run and all of your textures will be visible.

TXO file format

Panda has a native file format for storing texture images, called TXO (the abbreviation is for "texture object"). This is similar to BAM files. A TXO file contains all of the texture image data in a format very similar to Panda's internal representation, so it loads into memory very quickly.

More importantly, perhaps, TXO files can optionally store pre-compressed texture images. You can use the command:

egg2bam -txo -ctex model.egg -o model.bam

to convert your model to a BAM file, and all of its textures to TXO files, with the image data pre-compressed within the TXO file so that it will not need to be compressed at runtime later. (You may need to specify "pandagl" instead of "pandadx9" as your rendering engine while you run the egg2bam command--at the time of this writing, there were issues with using Panda's DirectX driver in an offline mode like this. However, the resulting TXO files will load on either OpenGL or DirectX at runtime.)

TXO files have the same drawbacks as BAM files: they are tied to a particular version of Panda, so you may need to regenerate them when you next upgrade your Panda version.

A bigger drawback to storing pre-compressed texture images this way is that your application might no longer run on all graphics cards. Not all graphics cards support all kinds of DXT compression, and if you try to load a TXO file that a graphics card doesn't understand, it simply won't load. Thus, pre-compressing all of your textures may make your application less portable.

DDS file format

In addition to Panda's native TXO file format, there is a fairly standard format called DDS, which has some of the same properties of TXO. Like TXO, you can store pre-compressed images in a DDS file. The biggest advantage of the DDS file format is that there are already several tools available on the internet to generate DDS files, including GIMP and Photoshop plugins. (Note, however, that loading DDS files is a new feature of Panda, and these files are not supported in Panda versions before 1.6.)

Generating your own DDS files has several advantages; chief among them is that you have complete control over the compression artifacts of your texture. However, it has the same portability issues as storing pre-compressed texture images in TXO files: there is a possibility some graphics cards don't support the texture compression you have used, in which case it simply won't load.

Texture cache

There is a compromise between dynamic compression and pre-compressed textures: you can ask Panda to compress textures on the fly, and then save the resulting compressed image to a TXO file on disk. The next time you load that particular texture, it will load quickly from its TXO file. Since the TXO file was generated by the user's graphics driver, it will presumably use a supported compression algorithm.

To enable this feature, simply insert the following lines in your Config.prc file:

compressed-textures 1
model-cache-dir /c/temp/panda-cache
model-cache-compressed-textures 1

Where model-cache-dir specifies any folder on the disk (it will be created if it doesn't already exist). Note that the model-cache-dir may already be specified; the default distribution of Panda specifies a model-cache to speed up loading bam files.

Like DDS file format, the model-cache-compressed-textures variable is a new feature in Panda, and isn't available in versions prior to 1.6.


Panda3D Manual: Pixel and Vertex Shaders

What are Shaders?

Once upon a time, each 3D video card had a fixed list of things that it could do. At first, the feature lists were short: the cards could draw polygons, do some basic texturing, a little alpha blending, and that was it. As time went on, the list of features increased and increased: offscreen buffers, multisampling, hardware transform and lighting, and so forth. Every time somebody thought of some new rendering trick they wanted to try, they had to design a new video card with the new ability. People are creative, so soon, the list of known features exceeded 350 - this is not an exaggeration, see the OpenGL extension registry! Each of these features corresponded to each of the hundreds of rendering tricks that people had thought of so far. Yet, people kept designing new video card features, because their creativity for inventing new rendering tricks had not nearly been exhausted.

Eventually, somebody realized that video cards needed to be more like computers. When you want your computer to have a new capability, you don't design a new computer. You install a new piece of software. Adding software gives a computer new capabilities. If you could download new software to your video card, they reasoned, you wouldn't need design a new video card every time you thought of a new rendering trick. You could upgrade the new feature right into the card, right when you needed it.

And so, shaders were invented. Shaders are software for video cards. Shaders look just like computer programs, but they're executed by the video card, not the computer. If a video card supports shaders, it can be upgraded to support almost any desired behavior. Pretty much, the only thing that limits a shader-based video card is its speed.

Automatic use of Shaders

As of version 1.5.0, panda has several advanced rendering techniques such as per-pixel lighting, normal mapping, gloss mapping, glow mapping, HDR, bloom, and cartoon inking. In order to make these work, panda is quietly uploading shaders to your video card. You don't need to write shaders to use these features, or even know much about shaders. You just turn these features on. To learn how to turn these features on, read about these features in their own sections of the manual.

Writing your own Shaders

If what you want to do is one of the things that panda already supports automatically, such as per-pixel lighting, normal mapping, gloss mapping, glow mapping, HDR, bloom, or cartoon inking, you don't need to write any shaders. Just let panda handle it.

But if you want to do anything else --- for instance, if you wanted to do painterly rendering, or water reflections, or lens flare, or ... well, your imagination's the limit --- in that case, you need to write your own shaders. The following sections will tell you about how to do that in Panda3D.


Panda3D Manual: Shader Basics

Writing Panda3D Shaders

Currently, Panda3D only supports the Cg shading language. This section assumes that you have a working knowledge of the Cg shader language. If not, it would be wise to read about Cg before trying to understand how Cg fits into Panda3D.

To write a shader, you must create a shader program that looks much like the one shown below. This example preserves position but switches the red and green channels of everything it is applied to:

//Cg

void vshader(float4 vtx_position : POSITION,
  float4 vtx_color: COLOR,
  out float4 l_position : POSITION,
  out float4 l_color0 : COLOR0,
  uniform float4x4 mat_modelproj)
{
  l_position=mul(mat_modelproj, vtx_position);
  l_color0=vtx_color;
}

void fshader(
  float4 l_color0 : COLOR0,
  out float4 o_color : COLOR)
{
  o_color=float4(l_color0[1],l_color0[0], l_color0[2],l_color0[3]);
}

The first line of a Cg shader needs to be //Cg. Do not put a space between the two slashes and the word "Cg". In the future, we may support other shader languages, in which case, those shader languages will have their own header identifiers.

The shader must contain the two subroutines named vshader and fshader, the vertex shader and fragment shader. In addition, it may contain additional routines named vshader1, fshader1, vshader2, fshader2, and so forth. These latter pairs of subroutines represent fallback codepaths, to be used when the video card doesn't support the first pair. If none of the pairs is supported, the shader is disabled and has no effect (ie, rendering proceeds normally using the standard pipeline).

In the following code sample, a shader is loaded and applied to a model:

myShader = Shader.load("myshader.sha")
myModel.setShader(myShader)

In the first line, the shader is loaded. The object returned is of class Shader. The call to setShader causes myModel to be rendered with that shader. Shaders propagate down the scene graph: the node and everything beneath it will use the shader.

The Shader can Fetch Data from the Panda Runtime

Each shader program contains a parameter list. Panda3D scans the parameter list and interprets each parameter name as a request to extract data from the panda runtime. For example, if the shader contains a parameter declaration float3 vtx_position : POSITION, Panda3D will interpret that as a request for the vertex position, and it will satisfy the request. Panda3D will only allow parameter declarations that it recognizes and understands.

Panda3D will generate an error if the parameter qualifiers do not match what Panda3D is expecting. For example, if you declare the parameter float3 vtx_position, then Panda3D will be happy. If, on the other hand, you were to declare uniform shader2d vtx_position, then Panda3D would generate two separate errors: Panda3D knows that vtx_position is supposed to be a float-vector, not a texture, that it is supposed to be varying, not uniform.

Again, all parameter names must be recognized. There is a list of possible shader inputs that shows all the valid parameter names, and the data that Panda3D will supply.

Supplying data to the Shader Manually

Most of the data that the shader could want can be fetched from the panda runtime system by using the appropriate parameter names. However, it is sometimes necessary to supply some user-provided data to the shader. For this, you need setShaderInput. Here is an example:

myModel.setShaderInput("tint", Vec4(1.0, 0.5, 0.5, 1.0))

The method setShaderInput stores data that can be accessed by the shader. It is possible to store data of type Texture, NodePath, and Vec4. The setShaderInput method also accepts separate floating point numbers, which it combines into a Vec4.

The data that you store using setShaderInput isn't necessarily used by the shader. Instead, the values are stored in the node, but unless the shader explicitly asks for them, they will sit unused. So the setShaderInput("tint", Vec4(1.0, 0.5, 0.5, 1.0)) above simply stores the vector, it is up to the shader whether or not it is interested in a data item labeled "tint."

To fetch data that was supplied using setShaderInput, the shader must use the appropriate parameter name. See the list of possible shader inputs, many of which refer to the data that was stored using setShaderInput.

Shader Inputs propagate down the scene graph, and accumulate as they go. For example, if you store setShaderInput("x",1) on a node, and setShaderInput("y",2) on its child, then the child will contain both values. If you store setShaderInput("z",1) on a node, and setShaderInput("z",2) on its child, then the latter will override the former. The method setShaderInput accepts a third parameter, priority, which defaults to zero. If you store setShaderInput("w",1,1000) on a node, and setShaderInput("w",2,500) on the child, then the child will contain ("w"==1), because the priority 1000 overrides the priority 500.

Shader Render Attributes

The functions nodePath.setShader and nodePath.setShaderInput are used to apply a shader to a node in the scene graph. Internally, these functions manipulate a render attribute of class ShaderAttrib on the node.

In rare occasions, it is necessary to manipulate ShaderAttrib objects explicitly. The code below shows how to create a ShaderAttrib and apply it to a camera, as an example.

myShaderAttrib = ShaderAttrib.make()
myShaderAttrib = myShaderAttrib.setShader(Shader.load("myshader.sha"))
myShaderAttrib = myShaderAttrib.setShaderInput("tint", Vec4(1.0,0.5,0.5,1.0))
base.cam.node().setInitialState(render.getState().addAttrib(myShaderAttrib))

Be careful: attribs are immutable objects. So when you apply a function like setShader or setShaderInput to a ShaderAttrib, you aren't modifying the attrib. Instead, these functions work by returning a new attrib (which contains the modified data).

Deferred Shader Compilation

When you create an object of class shader, it compiles the shader, checking for syntax errors. But it does not check whether or not your video card is powerful enough to handle the shader. Panda3D only does that later on, when you try to render something with the shader.

In the unusual event that your computer contains multiple video cards, the shader may be compiled more than once. It is possible that the compilation could succeed for one video card, and fail for the other.


Panda3D Manual: List of Possible Shader Inputs

Shader parameters must have names that are recognized by panda. Here is a list of the allowable parameter names:

uniform sampler2D tex_0 | The model's first texture. This requires that the model be textured in the normal manner. You may also use tex_1, tex_2, and so forth, if the model is multitextured.
sampler3D or samplerCUBE | If the model uses a 3D texture or a cubemap, use tex3D and texCUBE to access the color.
uniform sampler2D tex_0_suffix | Obtain a texture by concatenating a hyphen and the suffix to the filename of the model's first texture. For example, if tex_0 is "woman.jpg", then tex_0_normalmap is "woman-normalmap.jpg", and tex_0_specular is "woman-specular.jpg". You may also use tex_1_suffix, tex_2_suffix, and so forth.
float3 vtx_position: POSITION | Vertex Position. Vertex shader only. You may also use float4, in which case (w==1).
float3 vtx_normal: NORMAL | Vertex Normal. Vertex shader only.
float4 vtx_color : COLOR | Vertex color. Vertex shader only.
float2 vtx_texcoord0: TEXCOORD0 | Texture coordinate associated with the model's first texture. This requires that the model be textured in the normal manner. You may also use vtx_texcoord1, vtx_texcoord2, and so forth if the model is multitextured. Vertex shader only.
float3 vtx_tangent0 | Tangent vector associated with the model's first texture. This can only be used if the model has been textured in the normal manner, and if binormals have been precomputed. You may also use vtx_tangent1, vtx_tangent2, and so forth if the model is multitextured. Vertex shader only.
float3 vtx_binormal0 | Binormal vector associated with vtx_texcoord0. This can only be used if the model has been textured in the normal manner, and if binormals have been precomputed. You can also use vtx_binormal1, vtx_binormal2, and so forth if the model has been multitextured. Vertex shader only.
floatX vtx_anything | Panda makes it possible to store arbitrary columns of user-defined data in the vertex table; see GeomVertexData. You can access this data using this syntax. For example, vtx_chicken will look for a column named "chicken" in the vertex array. Vertex shader only.
uniform float4x4 trans_x_to_y | A matrix that transforms from coordinate system X to coordinate system Y. See the section on Shaders and Coordinate Spaces for more information.
uniform float4x4 tpose_x_to_y | Transpose of trans_x_to_y
uniform float4 row0_x_to_y | Row 0 of trans_x_to_y.
uniform float4 row1_x_to_y | Row 1 of trans_x_to_y.
uniform float4 row2_x_to_y | Row 2 of trans_x_to_y.
uniform float4 row3_x_to_y | Row 3 of trans_x_to_y.
uniform float4 col0_x_to_y | Col 0 of trans_x_to_y.
uniform float4 col1_x_to_y | Col 1 of trans_x_to_y.
uniform float4 col2_x_to_y | Col 2 of trans_x_to_y.
uniform float4 col3_x_to_y | Col 3 of trans_x_to_y.
uniform float4x4 mstrans_x | Model-Space Transform of X, aka trans_x_to_model
uniform float4x4 cstrans_x | Clip-Space Transform of X, aka trans_x_to_clip
uniform float4x4 wstrans_x | World-Space Transform of X, aka trans_x_to_world
uniform float4x4 vstrans_x | View-Space Transform of X, aka trans_x_to_view
uniform float4 mspos_x | Model-Space Position of X, aka row3_x_to_model
uniform float4 cspos_x | Clip-Space Position of X, aka row3_x_to_clip
uniform float4 wspos_x | World-Space Position of X, aka row3_x_to_world
uniform float4 vspos_x | View-Space Position of X, aka row3_x_to_view
uniform float4x4 mat_modelview | Modelview Matrix
uniform float4x4 inv_modelview | Inverse Modelview Matrix
uniform float4x4 tps_modelview | Transposed Modelview Matrix
uniform float4x4 itp_modelview | Inverse Transposed Modelview Matrix
uniform float4x4 mat_projection | Projection Matrix
uniform float4x4 inv_projection | Inverse Projection Matrix
uniform float4x4 tps_projection | Transposed Projection Matrix
uniform float4x4 itp_projection | Inverse Transposed Projection Matrix
uniform float4x4 mat_modelproj | Composed Modelview/Projection Matrix
uniform float4x4 inv_modelproj | Inverse ModelProj Matrix
uniform float4x4 tps_modelproj | Transposed ModelProj Matrix
uniform float4x4 itp_modelproj | Inverse Transposed ModelProj Matrix
uniform float4 k_anything | A constant vector that was stored using setShaderInput. Parameter k_anything would match data supplied by the call setShaderInput("anything", Vec4(x,y,z,w))
uniform sampler2D k_anything | A constant texture that was stored using setShaderInput. Parameter k_anything would match data supplied by the call setShaderInput("anything", myTex)
uniform float4x4 k_anything | A constant matrix that was stored using setShaderInput. Parameter k_anything would match data supplied by the call setShaderInput("anything", myNodePath). The matrix supplied is the nodepath's local transform.
uniform float4 texpad_x | X must be the name of a texture specified via shaderInput. Contains the U,V coordinates of the center of the texture. This will be (0.5,0.5) if the texture is not padded, but it will be less if the texture is padded.
uniform float4 texpix_x | X must be the name of a texture specified via shaderInput. Contains the U,V offset of a single pixel in the texture (ie, the reciprocal of the texture size).
uniform float4x4 attr_material | The contents of the material attribute. Row 0 is ambient, Row 1 is diffuse, Row 2 is emission, Row 3 is specular, with shininess in W.
uniform float4 attr_color | The contents of the color attribute. This is white unless the model has a flat color applied.
uniform float4 alight_x | X must be an AmbientLight specified via a shaderInput. Contains the color of the light.
uniform float4x4 dlight_x | X must be an DirectionalLight specified via a shaderInput. Row 0 is color, row 1 is specular, row 2 is model-space direction, row 3 is model-space pseudo half-angle.
floatX l_position: POSITION | Linearly interpolated Position, as supplied by the vertex shader to the fragment shader. Declare "out" in the vertex shader, "in" in the fragment shader.
floatX l_color0: COLOR0 | Linearly interpolated Primary color, as supplied by the vertex shader to the fragment shader. Declare "out" in the vertex shader, "in" in the fragment shader.
floatX l_color1: COLOR1 | Linearly interpolated Secondary color, as supplied by the vertex shader to the fragment shader. Declare "out" in the vertex shader, "in" in the fragment shader.
floatX l_texcoord0: TEXCOORD0 | Linearly interpolated Texture Coordinate 0, as supplied by the vertex shader to the fragment shader. You may also use l_texcoord1, l_texcoord2, and so forth. Declare "out" in the vertex shader, "in" in the fragment shader.
out floatX o_color: COLOR | Output Color, as supplied by the fragment shader to the blending units. Fragment shader only.


Panda3D Manual: Shaders and Coordinate Spaces

The Major Coordinate Spaces

When writing complex shaders, it is often necessary to do a lot of coordinate system conversion. In order to get this right, it is important to be aware of all the different coordinate spaces that panda uses. You must know what "space" the coordinate is in. Here is a list of the major coordinate spaces:

Model Space: If a coordinate is in model space, then it is relative to the center of the model currently being rendered. The vertex arrays are in model space, therefore, if you access the vertex position using vtx_position, you have a coordinate in model space. Model space is z-up right-handed.

World Space: If a coordinate is in world space, then it is relative to the scene's origin. World space is z-up right-handed.

View Space: If a coordinate is in view space, then it is relative to the camera. View space is z-up right-handed.

API View Space: This coordinate space is identical to view space, except that the axes may be flipped to match the natural orientation of the rendering API. In the case of opengl, API view space is y-up right-handed. In the case of directx, API view space is y-up left-handed.

Clip Space: Panda's clip space is a coordinate system in which (X/W, Y/W) maps to a screen pixel, and (Z/W) maps to a depth-buffer value. All values in this space range over [-1,1].

API Clip Space: This coordinate space is identical to clip space, except that the axes may be flipped to match the natural orientation of the rendering API, and the numeric ranges may be rescaled to match the needs of the rendering API. In the case of opengl, the (Z/W) values range from [-1, 1]. In the case of directx, the (Z/W) values range from [0,1].

Supplying Translation Matrices to a Shader

You can use a shader parameter named "trans_x_to_y" to automatically obtain a matrix that converts any coordinate system to any other. The words x and y can be "model," "world," "view," "apiview," "clip," or "apiclip." Using this notation, you can build up almost any transform matrix that you might need. Here is a short list of popular matrices that can be recreated using this syntax. Of course, this isn't even close to exhaustive: there are seven keywords, so there are 7x7 possible matrices, of which 7 are the identity matrix.

Desired Matrix | Syntax
The Modelview Matrix | trans_model_to_apiview
The Projection Matrix | trans_apiview_to_apiclip
the DirectX world matrix | trans_model_to_world
the DirectX view matrix | trans_world_to_apiview
gsg.getCameraTransform() | trans_view_to_world
gsg.getWorldTransform() | trans_world_to_view
gsg.getExternalTransform() | trans_model_to_view
gsg.getInternalTransform() | trans_model_to_apiview
gsg.getCsTransform() | trans_view_to_apiview
gsg.getInvCsTransform() | trans_apiview_to_view

Recommendation: Don't use API View Space or API Clip Space

The coordinate systems "API View Space" and "API Clip Space" are not very useful. The fact that their behavior changes from one rendering API to the next makes them extremely hard to work with. Of course, you have to use the composed modelview/projection matrix to transform your vertices, and in doing so, you are implicitly using these spaces. But aside from that, it is strongly recommended that you not use these spaces for anything else.

Model_of_x, View_of_x, Clip_of_x

When you use the word "model" in a trans directive, you implicitly mean "the model currently being rendered." But you can make any nodepath accessible to the shader subsystem using setShaderInput:

myhouse = loader.loadModel("myhouse")
render.setShaderInput('myhouse', myhouse)

Then, in the shader, you can convert coordinates to or from the model-space of this particular nodepath:

uniform float4x4 trans_world_to_model_of_myhouse

or, use the syntactic shorthand:

uniform float4x4 trans_world_to_myhouse

Likewise, you can create a camera and pass it into the shader subsystem. This is particularly useful when doing shadow mapping:

render.setShaderInput('shadowcam', self.shadowcam)

Now you can transform vertices into the clip-space of the given camera using this notation:

uniform float4x4 trans_model_to_clip_of_shadowcam

If you transform your model's vertices from model space into the clip space of a shadow camera, the resulting (X/W,Y/W) values can be used as texture coordinates to projectively texture the shadow map onto the scene (after rescaling them), and the (Z/W) value can be compared to the value stored in the depth map (again, after rescaling it).

Panda does support the notation "trans_x_to_apiclip_of_y", but again, our recommendation is not to use it.

You can transform a vertex to the view space of an alternate camera, using "view of x." In fact, this is exactly identical to "model of x," but it's probably good form to use "view of x" when x is a camera.


Panda3D Manual: Known Shader Bugs and Limitations

Known Bugs in the Shader Subsystem

Here is a list of known bugs, with workarounds:

Problem: Register Allocation.

Problem: nVidia's Cg compiler tries to assign registers to parameters. Under a variety of circumstances, the Cg compiler will assign the same register to two parameters, or to a parameter and to a constant in the program.

Workaround: We have found that if you manually allocate registers by supplying a semantic string for each parameter, this problem is bypassed.

Problem: Bad Target Languages.

Problem: nVidia's Cg compiler will choose one of several different "target" languages to translate the Cg program into. When the Cg compiler tries to translate the program into the VP40/FP40 language, it often produces incorrect output.

Workaround: We have discovered that translation into ARBVP1/ARBFP1 seems to work reliably. Since that language is supported on essentially every video card, it is usually safe to translate into that language. We have provided a config-variable that you can use to suppress bleeding edge stuff:

basic-shaders-only #t

This variable is enabled by default.

At some point, when functionality that is currently flaky becomes reliable, we may expand the definition of what constitutes 'basic' shaders.

Problem: Cg program too complex for driver.

Problem: Panda will translate the shader into the ARBVP1/ARBFP1 profile by default, for the reason stated above. If instructions are used that are not supported by these profiles, this error will occur.

Workaround: You need to disable the basic-shaders-only flag to allow Panda to translate the shaders into profiles that do support the used instructions:

basic-shaders-only #f

Note that by doing so you might run into the problem above, and it is not recommended to do so unless you really need it.

Problem: Untested/Unfinished DirectX Support.

Problem: Shader development is currently being done in OpenGL. The DirectX support typically lags behind, and is often less fully-tested.

Workaround: The default setting for Panda is to use OpenGL, not DirectX. For now, when using shaders, do not change this setting.


Panda3D Manual: The Shader Generator

The Shader Generator

As of version 1.5.0, panda supports several new features:

  • per-pixel lighting
  • normal mapping
  • gloss mapping
  • glow mapping
  • high-dynamic range rendering
  • cartoon shading

It's not that these things weren't possible before: they were. But previously, you had to write shaders to accomplish these things. This is no longer necessary. As of version 1.5.0, all that has to happen is for the artist to apply a normal map, gloss map, or glow map in the 3D modeling program. Then, the programmer gives permission for shader generation, and Panda3D handles the rest.

A few of these features do require minimal involvement from the programmer: for instance, high-dynamic range rendering requires the programmer to choose a tone-mapping operator from a small set of options. But that's it: the amount of work required of the programmer is vastly less than before.

Many of these features are complementary with image postprocessing operations, some of which are now nearly-automatic as well. For example, HDR combines very nicely with the bloom filter, and cartoon shading goes very well with cartoon inking.

Individually, these features are not documented in this chapter of the manual. Instead, they're documented in the portion of the manual where they make the most sense. For example, normal mapping, gloss mapping, and glow mapping are all documented in the section on Texturing. HDR and cartoon shading are documented under Render Attributes in the subsection on Light Ramps.

However, to enable any of these features, you need to tell Panda3D that it's OK to automatically generate shaders and send them to the video card. The call to do this is:

nodepath.setShaderAuto()

If you don't do this, none of the features listed above will have any effect. Panda will simply ignore normal maps, HDR, and so forth if shader generation is not enabled. It would be reasonable to enable shader generation for the entire game, using this call:

render.setShaderAuto()

Sample Programs

Three of the sample programs demonstrate the shader generator in action:

  • Sample Programs: Cartoon Shader
  • Sample Programs: Glow Filter
  • Sample Programs: Normal Mapping

In each case, the sample program provides two versions: Basic and Advanced. The Basic version relies on the shader generator to make everything automatic. The Advanced version involves writing shaders explicitly.

Per-Pixel Lighting

Simply turning on setShaderAuto causes one immediate change: all lighting calculations are done per-pixel instead of per-vertex. This means that models do not have to be highly tesselated in order to get nice-looking spotlights or specular highlights.

Of course, the real magic of setShaderAuto is that it enables you to use powerful features like normal maps and the like.

Known Limitations

The shader generator replaces the fixed-function pipeline with a shader. To make this work, we have to duplicate the functionality of the entire fixed function pipeline. That's a lot of stuff. We haven't implemented all of it yet. Here's what's supported:

  • flat colors and vertex colors
  • lighting
  • normal maps
  • gloss maps
  • glow maps
  • materials, but not updates to materials
  • 2D textures
  • texture stage modes: modulate, decal, add
  • light ramps (for cartoon shading)

Here's what's known to be missing:

  • 3D textures, cube textures
  • texture stage modes: replace, blend, combine
  • color scale attrib
  • texgen
  • texmatrix
  • fog

These functions are high on the list of priorities, and will probably be coming sometime in the next month or so.

How the Shader Generator Works

When panda goes to render something marked setShaderAuto, it synthesizes a shader to render that object. In order to generate the shader, it examines all the attributes of the object: the lights, the material, the fog setting, the color, the vertex colors... almost everything. It takes into account all of these factors when generating the shader. For instance, if the object has a material attrib, then material color support is inserted into the shader. If the object has lights, then lighting calculations are inserted into the shader. If the object has vertex colors, then the shader is made to use those.

Caching and the Shader Generator

If two objects are rendered using the same RenderState (ie, the exact same attributes), then the shader is only generated once. But even a single change to the RenderState will cause the shader to be regenerated. This is not entirely cheap. Making changes to the RenderState of an object should be avoided when shader generation is enabled, because this necessitates regeneration of the shader.

A few alterations don't count as RenderState modifications: in particular, changing the positions and colors of the lights doesn't count as a change to the RenderState, and therefore, does not require shader regeneration. This can be useful: if you just want to tint an object, apply a light to it then change the color of the light.

There is a second level of caching. If the system generates a shader, it will then compare that shader to the other shaders it has generated previously. If it matches a previously-generated shader, it will not need to compile the shader again.

So, to save the full cost, use the same RenderState. To save most of the cost, use two RenderStates that are similar. By "similar," I mean having the same general structure: ie, two models that both have a texture and a normal map, and both have no vertex colors and neither has a material applied.

Combining Automatic Shaders with Manual Shaders

Sometimes, you will want to write most of a game using panda's automatic shader generation abilitites, but you'll want to use a few of your own shaders. A typical example would be a scene with some houses, trees, and a pond. You can probably do the houses and trees using panda's built-in abilities. However, Panda doesn't contain anything that particularly looks like pond-water: for that, you'll probably need to write your own shader.

When you use render.setShaderAuto(), that propagates down the scene graph just like any other render attribute. If you assign a specific shader to a node using nodepath.setShader(myshader), that overrides any shader assignment propagated down from above, including an Auto shader assignment from above. So that means it is easy, in the example above, to enable auto shader generation for the scene as a whole, and then override that at the pond-nodepath.

Creating your own Shader Generator

We anticipate that people who are writing full-fledged commercial games using Panda3D might want to write their own shader generators. In this way, you can get any effect you imagine without having to give up the convenience and elegance of being able to simply apply a normal map or a gloss map to a model, and having it "just work."

To create your own shader generator, you will need to delve into Panda3D's C++ code. Class ShaderGenerator is meant to be subclassed, and a hook function is provided to enable you to turn on your own generator.

Retrieving the generated shader

In case you want to use the shader generator manually, for example when you want to grab the output of the shaders it generates, you can get a handle to the default ShaderGenerator like this:

from pandac.PandaModules import ShaderGenerator
sg = ShaderGenerator.getDefault()
shaderAttrib = sg.synthesizeShader(renderstate)

The synthesizeShader method generates a shader based on the given renderstate. (For example, you could specify render.getState() as parameter.) The function returns a ShaderAttrib instance on which you can retrieve the shader using the getShader() method. For example, if you want to print out the generated shader for render, you could do:

sg = ShaderGenerator.getDefault()
shader = sg.synthesizeShader(render.getState()).getShader()
print shader.getText()

Note, however, that this does not work for postprocessing effects, since these are handled by a different class, and also require additional setup like buffers.


Panda3D Manual: Cg Shader Tutorial

Cg Shader Tutorials

Panda has the ability to process shaders written in the Cg shader language. In this tutorial series, you will learn how to write shaders for Panda in Cg. While there are panda specific things in this tutorial you should be able to learn how to write Cg shaders that can be used in other engines.

Pre-requisites

You should be able to write a simple math program in C/C++ or C#. Do not underestimate this. If you start writing your own shaders without any prior knowledge about a C like language it is as hard as starting to write a Panda3D application without knowing anything about Python. Some resources you might want to look at:

  • C Programming Wikibook
  • C# Programming Wikibook

You should have a solid grasp of Panda basics. Read the hello world tutorial and understand the "Solar System" sample that comes with the panda distribution. You should also have a good understanding of how the scene graph works. If you're interested in writing a shadow mapping shader, it helps to have a solid understanding of how the depth buffer works.

Read the first chapter from NVIDIA's Cg Tutorial. You do not have to understand it fully, but at least you know who invented Cg and what it is for. The NVIDIA and ATI developer sites also have plenty of useful information about shaders.

You should also have the List of Possible Shader Inputs page handy.

Tutorials

  1. Baseline Panda application
  2. The Simplest Possible Shader. So simple that its useless.
  3. The simplest possible useful shader.
  4. Applying colors to the model as defined in the model file.
  5. Applying colors with the vertex and fragment shaders. Pass information between the vertex and fragment shader.
  6. Passing inputs to the shader and controlling it from Panda
  7. Applying one texture to your models, disregarding colors.
  8. Applying two textures to your models.
  9. Explaining details about diffuse lighting.
  10. Per vertex diffuse lighting with shaders. The concept of spaces is introduced here.
  11. Per pixel lighting with one point light. Normalization problems that may arise are explained here.
  12. Per pixel lighting with multiple point lights and attenuation.


Panda3D Manual: Cg Tutorial Part 1

Cg Tutorial Part 1: The Baseline Panda App

We'll start by setting up the tutorial project folder. Create a new folder for the tutorial and download the tutorial media archive then unzip the files in the new folder. The zip file contains all the models and textures you will need to follow this tutorial series.

For this first lesson we'll just create a basic Panda python script. We will be modifying this python script as we continue the other lessons. Copy the script below and save it to the folder.

# Lesson1.py
import sys

import direct.directbase.DirectStart
from direct.interval.LerpInterval import LerpFunc
from pandac.PandaModules import Texture, TextureStage

base.setBackgroundColor(0.0, 0.0, 0.0)
base.disableMouse()

base.camLens.setNearFar(1.0, 50.0)
base.camLens.setFov(45.0)

camera.setPos(0.0, -20.0, 10.0)
camera.lookAt(0.0, 0.0, 0.0)

root = render.attachNewNode("Root")
root.setPos(0.0, 0.0, 0.0)

textureArrow = loader.loadTexture("arrow.png")
textureArrow.setWrapU(Texture.WMClamp)
textureArrow.setWrapV(Texture.WMClamp)

stageArrow = TextureStage("Arrow")
stageArrow.setSort(1)

textureCircle = loader.loadTexture("circle.png")
textureCircle.setWrapU(Texture.WMClamp)
textureCircle.setWrapV(Texture.WMClamp)

stageCircle = TextureStage("Circle")
stageCircle.setSort(2)

modelCube = loader.loadModel("cube.egg")

cubes = []
for x in [-3.0, 0.0, 3.0]:
    cube = modelCube.copyTo(root)
    cube.setPos(x, 0.0, 0.0)
    cubes += [ cube ]

base.accept("escape", sys.exit)

base.accept("o", base.oobe)

def animate(t):
    for i in range(len(cubes)):
        cubes[i].setH(t * (2.0 ** i))

interval = LerpFunc(animate, 5.0, 0.0, 360.0)

base.accept("i", interval.start)

def move(x, y, z):
    root.setX(root.getX() + x)
    root.setY(root.getY() + y)
    root.setZ(root.getZ() + z)

base.accept("d", move, [1.0, 0.0, 0.0])
base.accept("a", move, [-1.0, 0.0, 0.0])
base.accept("w", move, [0.0, 1.0, 0.0])
base.accept("s", move, [0.0, -1.0, 0.0])
base.accept("e", move, [0.0, 0.0, 1.0])
base.accept("q", move, [0.0, 0.0, -1.0])

run()

If you run that script, you'll get the following output below. The controls are q, w, e, a, s, d for moving the camera; 'o' for moving the camera via the mouse, 'i' to start the cubes rotating and 'esc' to quit. You will be modifying this script as you follow this tutorial series.

3D Models, Shaders and Hardware

Now that we have the basic script, lets take a look at how 3d models, shaders and the 3d hardware interact with each other. Please open the file cube.egg with a text editor. Egg files are human readable. We will need this information later on to understand how the vertex shader and the fragment shader works. You can also see how the model looks like in panda by using the pview model viewer.

// A vertex entry in an egg file
<Group> {
  <VertexPool> Cube {
    <Vertex> 0 {
      1.0 1.0 -1.0
      <UV> { 1.0 1.0 }
      <RGBA> { 1.0 0.0 0.0 1.0 }
    }
    ...

The cube has six faces. Each face has four different vertices. Therefore this cube has 24 vertices. Theoretically a cube only needs eight vertices with each vertex being shared by three faces. The problem with this is that each vertex can only have one color, but what happens if we want each of the six faces to be a different color? This is impossible if the cube is only defined with eight vertices. There are more disadvantages if we only define the cube with eight vertices, which we will talk about later on. The only advantage of having less vertices is that we have to send less vertices to the graphic card but in almost all applications vertices are not a limiting factor. The memory consumption of vertices in comparison to the memory consumption of textures is negligible. Besides the color entry for a vertex, a vertex also has one UV entry associated with it.

Next look at the colors defined in the egg file. If you compare all the color entries, you will only find eight unique colors in the egg file. Why does the model have thousands of colors when viewed in the model viewer then? This is because of linear interpolation, where a value is generated between two different values based on a "distance". Today graphic cards are very good at linear interpolation with the ability to do billions of linear interpolations per second. The downside is that sometimes the graphic card can ONLY do linear interpolation and you can't change that, even with a shader.

Back to the colors. If you have a red color (1.0, 0.0, 0.0) on one vertex and a dark blue color (0.0, 0.0, 0.5) on the other vertex the graphic card simply interpolates the color for every pixel between this two vertices, even without shaders (only if requested, but Panda3D ask the graphic card to do this). The graphic card doesn't know that a color comes in three parts: Red, Green and Blue. It only knows that it is manipulating values, in this case adjusting the constituent values for Red Green and Blue. Here is an example of how the graphic card interpolates:

Red Vertex | Blue Vertex | Color value
100% | 0% | (1.0, 0.0, 0.0)
75% | 25% | (0.75, 0.0, 0.125)
50% | 50% | (0.5, 0.0, 0.25)
25% | 75% | (0.25, 0.0, 0.375)
0% | 100% | (0.0, 0.0, 0.5)

A simplified version of how the graphic card draws the model (in reality it does not work exactly like this but the result is the same): If the graphic card needs to draw a pixel on a screen it first looks if this pixel is on a vertex. If it is, it can take the color of the vertex and draw a pixel with this color. If not, the graphic card looks at which triangle this pixel belongs. Then it looks at where the vertices of this triangle are and calculates the distance to each of the vertices. Based on this distance and the color of the vertices, it interpolates all color components and draws a pixel with this color.

We've already seen that the graphic card does not care about the fact that a color consists of the three parts R, G and B. The good thing about this is that the graphic card can do the calculations for R independent of the other parts, as is the case for G and B. You may ask, "why should I care"? The advantage is that the graphic card can do calculations for each part in parallel. A graphic card is in general extremely specialized in parallel computing. This is also true for vertex shaders and pixel shaders. Each calculation for a vertex or pixel is done individually. A vertex never knows how what his neighbor looks like and a pixel never knows what his neighbor's color is. This is a reason why graphic card vendors can improve the performance of GPUs faster then CPUs. Vertex and pixel shaders are inherently parallel. The disadvantage of this is that if you need to do some calculations with respect to the neighboring pixel or vertex, you have to create a complex setup that often (but not always) is not fast enough for 60+ FPS games.

A blur filter (like in the glow example) is an example of such a setup. You need at least two passes to create such an effect.

Modifying the Script

We will now modify the script to see how the normal 3D pipeline blends the vertex colors with textures. In the tutorial media, there are two textures, 'arrow.png' and 'circle.png'. We will apply these to the cubes using only Panda.

Place one of the following lines in the script after the cubes are placed in the scenegraph:

root.setTexture(stageArrow, textureArrow)
root.setTexture(stageCircle, textureCircle)

You will notice that the textures get applied to all of the cubes. Now try placing the textures on individual cubes:

cubes[0].setTexture(stageArrow, textureArrow)
cubes[1].setTexture(stageCircle, textureCircle)
cubes[2].setTexture(stageArrow, textureArrow)
cubes[2].setTexture(stageCircle, textureCircle)

Now that we have a general idea of how 3D hardware and models work, lets move on to using shaders.

Part 2: The simplest possible shader


Panda3D Manual: Cg Tutorial Part 2

Cg Tutorial Part 2: The Simplest Shader

In this part of the tutorial, we will introduce the barest shader possible and then modify the python application to load this shader to verify that it works. How would we know that it works? Well, this shader is so simple that it basically makes our application useless and all we will see is a black screen.

The Shader

//Cg
/* lesson2.sha */

void vshader(
    out float4 l_position : POSITION)
{
    l_position = float4(0.0, 0.0, 0.0, 0.0);
}

void fshader(
    out float4 o_color : COLOR)
{
    o_color = float4(1.0, 0.0, 1.0, 1.0);
}

We'll only go through the basics of this shader for now. Also note that not everything in this tutorial is a Cg requirement, some of the things such as variable names are Panda3d requirements.

The first important thing in this shader is the first line, the //Cg comment. Removing it will cause the shader to fail compilation. This is a panda requirement, telling the engine that the shader being loaded is a Cg shader. Second, you see two functions; vshader and fshader. These are the respective entry points for the vertex and fragment shaders.

The only requirements for a vertex shader is that it has to generate a position. In Panda, objects move in three dimensions so having an output of 3 positions should be enough but on reading the vertex shader you can see that its output is a float4. A float4 is an array of four float values, so what possible use is the fourth value to the vertex shader? With this fourth value, you can do some fancy stuff that you cannot do with a float3. In later examples we will multiply the output with a float4 matrix, in other words 16 float values. A float3 cannot be multiplied by a float4 matrix. You can think of the fourth value in the same way as the fourth component in an RGBA color, the alpha component. Its not often necessary but it allows you to do interesting things.

In this vertex shader, the only thing that we are doing is assigning the variable l_position a constant float4. In C/C++/C# you cannot assign arrays to arrays the way we do it here, but in Cg this is possible for fixed sized arrays. The l_position variable cannot be renamed as it is a requirement of Panda3d. Look up this variable name in the List of Possible Shader Inputs.

Why do we have to write "out" in front of l_position? In C/C++/C# every function has at most one return value but shaders often need to return more than one value. The NVIDIA guys added an "in" keyword and an "out" keyword to Cg. "out" means that it is a return value. "in" means that is an input value. This basic shader has no inputs at all and this is one of the main reasons it cannot produce any useful results. Here we only output l_position but in later examples we will have more then one output value.

The "POSITION" keyword is a hint for the GPU. The GPU then knows that is should assign l_position to an internal POSITION register (register is a simplification, this term might not be precise). This tells the GPU that it will later have to draw the output of POSITION on the screen. Currently the GPU does not know what color the pixels will have but it can calculate a position on the screen for every given POSITION. The GPU itself is not as smart as one might think, we need to do some not so simple math first to help the GPU calculate the correct position on the screen.

More or less everything that was said about the vertex shader is true for the fragment shader. The minimum requirement is that a fragment shader has to create a color, to do this we have to assign a float4 to o_color. See List of Possible Shader Inputs what the name o_color means.

Here the GPU needs the keyword "COLOR" for o_color. This is hint for the GPU that we would like to assign a float4 to the color buffer of your screen. As you may know, the range of a color component is 0 - 255 for R, G and B. That is a fact the GPU knows and it translates the floating point values to integers. The advantage of writing shaders in floating point is that if 48 bit color displays become common, we do not have to change our shader and neither would we have to change our shader if we only have a 16 bit color depth.

The Python Script

The only thing we will change in our panda script is to load our shader file and assign the shader on our root node. Run the script and you will see a black screen, so theres no point in providing a screenshot for this one!

#Lesson2.py

import sys
import direct.directbase.DirectStart

base.setBackgroundColor(0.0, 0.0, 0.0)
base.disableMouse()

base.camLens.setNearFar(1.0, 50.0)
base.camLens.setFov(45.0)

camera.setPos(0.0, -20.0, 10.0)
camera.lookAt(0.0, 0.0, 0.0)

root = render.attachNewNode("Root")

modelCube = loader.loadModel("cube.egg")

cubes = []
for x in [-3.0, 0.0, 3.0]:
    cube = modelCube.copyTo(root)
    cube.setPos(x, 0.0, 0.0)
    cubes += [ cube ]

# Load the shader from the file.
shader = loader.loadShader("lesson2.sha")
# Assign the shader to work on the root node
# If you remove the line below, you will see
# that panda is actually rendering our scene.
root.setShader(shader)

base.accept("escape", sys.exit)
base.accept("o", base.oobe)

def move(x, y, z):
    root.setX(root.getX() + x)
    root.setY(root.getY() + y)
    root.setZ(root.getZ() + z)

base.accept("d", move, [1.0, 0.0, 0.0])
base.accept("a", move, [-1.0, 0.0, 0.0])
base.accept("w", move, [0.0, 1.0, 0.0])
base.accept("s", move, [0.0, -1.0, 0.0])
base.accept("e", move, [0.0, 0.0, 1.0])
base.accept("q", move, [0.0, 0.0, -1.0])

run()

Modifying the Shader

Let's modify the shader to get some idea of what we can do in Cg. We still won't be producing any output yet but it will give you a good overview of how to write in Cg.

First lets see what happens when we try to use a float3 as the shader output. Try changing the vertex shader to the sample below and then running the panda script. Examine the console output carefully.

void vshader(
    out float4 l_position : POSITION)
{
    l_position = float3(0.0, 0.0, 0.0, 0.0);
}

Now this next vertex shader does exactly the same thing as the original shader but shows how you can assign fixed length arrays to other fixed length arrays in Cg.

void vshader(
    out float4 l_position : POSITION)
{
    float4 zero = float4(0.0, 0.0, 0.0, 0.0);
    l_position = zero;
}

Finally lets do some useless maths in the vertex shader. Also note how you can assign a value to l_position more than once. You should try making your own modifications to the shader and see if it can compile

void vshader(
    out float4 l_position : POSITION)
{
    float4 zero = float4(0.0, 0.0, 0.0, 0.0);
    zero = zero * float4(1.0, 2.0, 3.0, 4.0);
    zero = zero * 5.0;
    l_position = zero;
    l_position = float4(0.0, 0.0, 0.0, 0.0);
}

After modifying the vertex shader, try to modify the fragment shader on your own. In the next tutorial, we will improve the shader's usefulness.

Part 3: The Simplest Possible Useful Shader


Panda3D Manual: Camera Control

Panda3D's camera is considered a PandaNode. It can therefore be manipulated as any other node.

The actual camera is defined in ShowBase as a NodePath named base.cam. There is also a plain node above the camera, which is a NodePath called base.camera. Generally you want to control the base.camera NodePath with your code.


Panda3D Manual: The Default Camera Driver

By default, panda runs a task that enables you to move the camera using the mouse. This task will conflict with any code you write to move the camera. The task controls the camera by updating the camera's position every frame to where the mouse thinks it should be. This means that any other code that directly controls the camera will not seem to work, because it will be fighting the mouse for control.

If you want to move the camera directly under show control, you must first disable the camera control task and then the camera will move as expected.

base.disableMouse()

The ShowBase class contains some handy methods to allow the user control over the camera. The useDrive() command enables keyboard and mouse control. Both control systems move only on the x and y axes, so moving up and down along the z axis is impossible with these systems.

The keyboard system uses the arrow keys. Up moves the camera forward, and down move it back. The left and right arrows turn the camera.

The mouse system responds whenever any button is held. If the pointer is towards the top of the sceen, the camera moves forward. If it is towards the bottom, the camera moves backwards. If it is on either side, the camera rotates to that direction. The speed the camera moves is determined by how far from the center the mouse pointer is. Additionally, there is another command that allows control based on trackball mice.

base.useDrive()
base.useTrackball()

ShowBase also provides the method oobe() to give you to control of the basic camera node (base.cam) with the mouse/trackball while the code continues to move the camera node (base.camera). This can be useful for debugging purposes. The word stands for "out-of-body experience" and is handy for giving you a God's-eye view of your application at any point in development. The method is a toggle; call it once to enable OOBE mode, and then again to disable it.

base.oobe()

oobeCull() is a variant on oobe(), and it works similarly, except that it still culls the scene as if the camera were still in its original position, while drawing the scene from the point of view of your camera's new position. So now you can view the scene from your "out of body" placement, and walk around, and you can see things popping in and out of view as your view frustum moves around the world.


Panda3D Manual: Lenses and Field of View

Every Camera has a Lens object that defines the properties of its view. For simple applications, you do not need to think about the lens; you will probably be happy with the default lens properties. However, you will occasionally want to adjust some properties of the lens, such as its field of view, and there are several interfaces to do this, depending on how you want to think about the lens.

When you start Panda3D, a default camera and lens are created for you automatically. The default camera object is stored in base.cam (although by convention, if you want to move the default camera you should manipulate base.camera instead), and the default lens is base.camLens.

This default lens will almost always be a perspective lens--that is, an instance of the class PerspectiveLens--unless you have done something to change it to another kind of lens. A perspective lens is by far the most common kind of lens used, and it behaves the same way the physical lens in a camera works, or for that matter the same way the lenses in our eyes work:

Lens tutorial, perspective view

The illustration above shows a camera with an ordinary perspective lens observing a model in the world. The camera can only see the part of the world that falls within the black lines; this area is called the lens frustum.

In the picture, you can also see the image that the lens is capturing (and that image is shown upside-down, just as it would be in a real, physical camera). This image is just for the purposes of illustration; it isn't really part of a Panda3D camera. It is included to help show the relationship between a Panda3D lens and a real, physical lens.

There are several different properties that can be set on a PerspectiveLens. Not all of them are independent; setting some properties will change the values of other properties. Here is an illustration:

Lens tutorial, top view

A. This point is the nodal point or eyepoint of the lens. It is also (usually) the origin, that is, the (0, 0, 0) point of the camera that holds the lens. Normally (in a default Z-up coordinate system), the lens will be looking down the +Y axis, so in the above illustration the +Y axis extends to the right from point A. The plane containing the nodal point, perpendicular the viewing direction (that is, the plane corresponding to the vertical line through point A), is called the camera plane.

Although it is possible to change the nodal point or view direction of a lens to some point other than (0, 0, 0) or some direction other than down the +Y axis, it is usually simplest and best just to move the entire camera using the basic NodePath operations like setPos() and setHpr().

B. This angle is the field of view, or fov, of the lens. You can easily change this by setting a new value in degrees with lens.setFov(angle). Making the field of view smaller will bring things in closer, like a telephoto lens; it will also diminish the visible effects of perspective. Making the field of view larger will open up the view to more objects, like a wide-angle lens; it will also increase the visible distortion of perspective. The field of view must be greater than 0 degrees and less than 180, but values greater than 90 will seem extremely distorted. (In the real world, perspective lenses rarely go wider than 80 degrees, and that's pretty wide.) The default field of view is 40 degrees, which is usually a pretty comfortable viewing angle.

There is actually a separate horizontal field of view and vertical field of view, both of which may be independently controlled with the two-parameter form of setFov: lens.setFov(horizontalAngle, verticalAngle). Using the two-parameter form will change the aspect ratio of the lens (see below). Normally, you would set the field of view using only the one-parameter form, which sets the horizontal field of view directly, and automatically recomputes the vertical field of view to preserve the same aspect ratio.

C. This distance is called the near distance or near plane of the lens. Objects that are closer than this to the camera plane will not be rendered. You may set the near distance as small as you like, but it must be greater than 0; and the smaller you set it, the greater the likelihood that you will observe an artifact called Z-fighting, a shimmering of objects that are off in the distance. The default near distance is 1.0, which for many scenes is a reasonable compromise. Of course, the most appropriate value for your scene depends on the nature of the scene (as well as the measurement units in which your scene is modeled).

You may change the near distance at any time with the call lens.setNear(distance).

D. This is the far distance or far plane of the lens. Similar to the near distance, objects that are farther than this from the camera plane will not be rendered. You may set this as large as you like, but like the near distance, setting it too large may result in Z-fighting. (However, the near distance value has a much greater impact on Z-fighting than the far distance value, because of the nature of the math involved.) The default far distance is 1000.0, which is appropriate for small scenes; you may need to set it larger if you have a large scene.

You may change the far distance with the call lens.setFar(distance). Since the near distance and far distance are often changed at the same time, there is a convenience function to set then both: lens.setNearFar(nearDistance, farDistance).

E. This size is the film size of the lens. This is only an abstract concept in Panda3D; it is designed to simulate the actual film size of a physical lens. In a real, physical camera, the lens casts light onto a piece of film behind the lens, and the size of the film impacts the effective field of view of the lens via a mathematical formula that every photographer knows (and which I won't repeat here). In Panda3D, you will probably ignore the film size, unless you are a photographer, or you want to set up a virtual lens that exactly matches the properties of some real, physical lens.

You can specify the film size with lens.setFilmSize(width) or lens.setFilmSize(width, height). Like field of view, the film size has two components, a horizontal film size and a vertical film size. Also like field of view, if you specify both components at once it will change the aspect ratio of the lens, but if you set only the width, Panda will automatically compute the height to keep the aspect ratio the same.

Setting the film size defines the units to be used for some of the other advanced lens properties, such as the focal length (below) and the lens offset. For instance, a 35mm camera exposes a rectangle on the film about 24mm x 36mm, so if you wanted to simulate a 35mm camera, you would use lens.setFilmSize(24, 36). This establishes that your film units are in millimeters, so you could then specify a lens with a focal length of 50mm using lens.setFocalLength(50). (Setting both the film size and the focal length like this would automatically calculate the field of view; see below.)

F. This distance is the focal length of the lens. Like film size, this is only an abstract concept in Panda3D, but it is a very important concept in a real, physical camera. Technically, it is the distance between a lens's nodal point or camera plane and its focal plane or film plane, and it affects the field of view of the lens. In real photography, lenses are typically described by their focal length, rather than by their field of view. You can set the focal length via lens.setFocalLength(distance).

G (not pictured). The final important property of a lens is its aspect ratio. This is the ratio of the width to the height of the image produced by the lens. It is almost, but not quite, the same as the ratio of the horizontal field of view to the vertical field of view. (It is not quite this, because a perspective lens is not linear in proportion to the angle.) Normally, you will want the aspect ratio of the lens to match the aspect ratio of your window; if it is something different, the image may seem stretched or squashed.

You can set the aspect ratio explicitly via lens.setAspectRatio(ratio). For instance, if you open a window that is 800 pixels wide and 300 pixels tall, you might want to call lens.setAspectRatio(800.0 / 300.0).

Interplay of lens properties

Note that, as mentioned above, several of these properties are interrelated. In particular, the field of view, focal length, and film size are closely tied together. Setting any two of these three properties will implicitly define the third one.

Panda will let you set all three of these properties as often as you like, but only the last two properties you set will be important. That is, if you set field of view and film size, Panda will calculate the focal length. If you set film size and focal length, Panda will calculate the field of view. If you set focal length and field of view, Panda will calculate the film size.

Also, the aspect ratio can be set either implicitly, by using the two-parameter setFov() or setFilmSize() methods, or explicitly, by directly specifying it with setAspectRatio(). If you set the aspect ratio explicitly, Panda will recompute your vertical field of view and vertical film size to match.


Panda3D Manual: Orthographic Lenses

The previous page described the PerspectiveLens class, and the various properties of a perspective lens, especially its field of view. There is another kind of lens that is frequently used in 3-D rendering, but it doesn't have a field of view in the same sense at all. This is an orthographic lens.

Lens tutorial, orthographic lenses

In an orthographic lens, there is no perspective--parallel lines viewed by the lens don't converge; instead, they remain absolutely parallel in the image. While a PerspectiveLens closely imitates the behavior of a real, physical camera lens, there is no real lens that does what an OrthographicLens does. An OrthographicLens, therefore, is most useful for special effects, where you want that unnatural look, or to emulate the so-called 2½-D look of several popular real-time strategy games, or strictly to render 2-d objects that shouldn't have any perspective anyway. In fact, the default camera created for the render2d scene graph, which is used to draw all of the onscreen GUI elements in Panda, uses an OrthographicLens.

Since an orthographic lens doesn't have a field of view angle, the lens.setFov() method does nothing. To adjust the amount that the orthographic lens sees, you must adjust its film size. And unlike a PerspectiveLens, the film size units are not arbitrary--for an OrthographicLens, the film size should be specified in spatial units, the same units you used to model your scene. For instance, the film size of the OrthographicLens in the above illustration was set with the call lens.setFilmSize(20, 15), which sets the film size to 20 feet by 15 feet--because the scene is modeled in feet, and the panda is about 12 feet tall.

Another nice property of an orthographic lens is that the near distance does not have to be greater than zero. In fact, it can be negative--you can put the near plane behind the camera plane, which means the camera will see objects behind itself. The OrthographicLens for render2d is set up with setNearFar(-1000, 1000), so it will render any objects with a Z value between -1000 and 1000. (Of course, in render2d almost all objects have a Z value of 0, so it doesn't matter much.)

If you like, you can change the default camera to use an orthographic lens with something like this:

lens = OrthographicLens()
lens.setFilmSize(20, 15)  # or whatever is appropriate for your scene
base.cam.node().setLens(lens)

Note that using an orthographic lens can be nonintuitive at times--for instance, objects don't get larger as you come closer to them, and they don't get smaller as you get farther away--so it may be impossible to tell your camera is even moving!


Panda3D Manual: Sound

To play audio in your game, Panda3D can offer you the following three choices of audio libraries:

  • FMOD: this is a very capable multi-platform sound engine. FMOD supports various types of sound files - MP3, WAV, AIFF, MIDI, MOD, WMA, OGG Vorbis. There is only one drawback: the license restricts you from using it for commercial purposes, unless you actually buy yourself an FMOD license. But if your program is not intended to make any money, and is not charged for in any way, then you may use FMOD in it for free.
  • OpenAL: this is a well-known and popular open-source audio library, also supported by Panda3D. Although it runs on windows without problems, panda's support for OpenAL is not yet stable on linux. If you are experiencing crashes you might want to consider switching to fmod, or using a different library like pyOpenAL.
  • Miles: this sound system is not included in the downloadable binaries of Panda3D, in order to use this you will need to purchase Miles and compile Panda3D from scratch using the ppremake system.

If these choices are not enough for you, you can always try other non-builtin sound libraries such as Pygame, or PyOpenAL.

To configure Panda3D to use a specific sound system, you will need to change your Config.prc configuration. You should look up the variable audio-library-name and change the value to one of p3openal_audio, p3fmod_audio, or miles_audio. (The names of the libraries may differ with the way Panda3D was built.)
If you don't want to use FMOD (due to the license) in your program, remove the fmod.dll and libp3fmod_audio.dll files from the panda \bin folder.

Note: If you are using FMOD and are using 64-bits, you might run into a strange assertion error or even a crash if the memory address of the sound exceeds the 4 billion. This is because of a bug in FMOD, until that bug is fixed you'll just need to make sure your memory usage doesn't exceed the 4GB (which is not likely, and even impossible on 32-bits systems). This bug has been fixed in Panda3D versions 1.6.0 and later.

For a full tutorial on getting started with audio functions in Panda3D, go here:

http://aamesxdavid.p3dp.com/PandaAudioTut.zip


Panda3D Manual: Loading and Playing Sounds and Music

Architecture

The implementation of the sound system in Panda3d allows for a division of audio into two categories - Sound Effects and Music. This division is only a convenience for programmers as Panda3d allows these two audio groups to be treated individually. These differences are explained on the next page.

Basics

Loading a Sound

Loading sound is done through the Loader class. (Loading sounds through the 'base' builtin is deprecated and is only present for backwards compatibility.)

In a normal Panda3D environment, loader is a builtin when you import DirectStart like this:

import direct.directbase.DirectStart

Load the sound, by supplying the path to the sound. Here's an example:

mySound1 = loader.loadSfx("SoundFile.wav")

These will return an object of the type AudioSound. It is necessary to put the extension in the sound filename.

Playing a Sound

To play sounds you can do the following:

mySound.play()

Stopping a Sound

To stop a sound:

mySound.stop()

Querying Sound Status

To check the status of a sound:

mySound.status()

status() returns 1 if it isn't currently playing and 2 if it is playing.

Setting Volume

The volume can be set between 0 and 1 and will linearly scale between these.

mySound.setVolume(0.5)

Panning a Sound

You can change the balance of a sound. The range is between -1.0 to 1.0. Hard left is -1.0 and hard right is 1.0.

mySound.setBalance(-0.5)

NOTE !!!

If running Panda from interactive prompt you must call the Update() command, after you play a sound.

base.sfxManagerList[n].update()

This is because the update() command is called every frame to reset a sound's channel.

In interactive mode Panda's frame update is suspended, and does not run automatically.


Panda3D Manual: Manipulating Sounds

Looping a Sound

To cause a sound to loop [IE Cause it to repeat once it is finished playing] do the following:

mySound.setLoop(True)
mySound.play()

To stop a sound from looping pass False in the setLoop() function.

mySound.setLoop(False)

Sounds can also be looped for a certain number of times:

mySound.setLoopCount(n)

Where 'n' can be any positive integer. 0 will cause a sound to loop forever. 1 will cause a sound to play only once. >1 will cause a sound to loop that many times.

NOTE Setting a sound's loop count will automatically set a sound's loop flag to 0 or >1 will automatically setLoop() to TRUE.

Notes on Looping Sounds Seamlessly

Looping a sound seamlessly should be as simple as loading the sound, then calling setLoop and play. However, occassionally Panda users have had difficulty getting sounds to loop seamlessly. The problems have been traced to three(!) different causes:

  1. Some MP3 encoders contain a bug where they add blank space at the end of the sound. This causes a skip during looping. Try using a wav instead.
  2. Some have tried using Sound Intervals to create a loop. Unfortunately, sound intervals depend on Panda's Thread to restart the sound, and if the CPU is busy, there's a skip. This is not a seamless method, in general. Use setLoop instead.
  3. There is a bug in Miles sound system, which requires a workaround in Panda3D. At one time, the workaround was causing problems with fmod, until we devised a new workaround. This bug no longer exists, you can ignore it.

So the easiest way to get a reliable looping sound is to use wav files, and to use setLoop, not sound intervals. Of course, when it comes time to ship your game, you can convert your sounds to mp3, but before you do, test your mp3 encoder to see if it contains the blank-space bug.

Cueing Time

There are getTime(), setTime() and length() functions for sounds. These will respectively, report the current time position, set the current time position and report the length. All these are in seconds.

mySound.length()

Will return the length of a sound file in seconds.

mySound.getTime()

Will get the current time the 'playback head' of a sound is at in seconds.

mySound.setTime(n)

Will set the 'playhead head' of a sound to n (where is seconds).

NOTE Sounds will start playing IMMEDIATELY after the command is issued. & Calling play() will cause the sound to start over from the beginning.

Changing Playback speed

You can change the rate at which a sound plays back.

To change a sounds playback speed use....

mySound.setPlayRate(N)

Where N is any float.

NOTE!!! Negative numbers will play a sound backwards. 0 is 'Pause' a sound.

You can also get a sound's play rate with..

mySound.getPlayRate()


Panda3D Manual: DSP Effects

DSP, or Digital Signal Processing, allows you to apply effect filters to your ingame audio. As of now, DSP effects are only available in Panda if you use the FMOD audio library. By default, Panda ships using the OpenAL audio library but FMOD can be enabled simply by editing Config.prc. Please keep in mind that using FMOD in a commercial application will require purchasing a license.

You will need to change this line in your Config.prc: (some versions of Panda might already be set to FMOD)

audio-library-name p3openal_audio

To this:

audio-library-name p3fmod_audio

The FilterProperties Object

Any DSP you add to your sound will require the use of "FilterProperties", which is a list of filters and their coefficients. Start with this import:

from pandac.PandaModules import FilterProperties

This will allow you to create lists of filters, like this one:

fp = FilterProperties()

This just adds a blank filter list, called "fp". From here we can add whatever filter we'd like. To stay consistent with the example below, we'll add a reverb effect.

fp.addReverb(0.6, 0.5, 0.1, 0.1, 0.1)

All this really does is add this particular reverb to our filter list - sound is not yet affected. To apply these filters to our audio output, use:

base.sfxManagerList[0].configureFilters(fp)

Depending on the sound you use, reverb may be very or only slightly noticeable; try using a quick sound at first, like a clap.

You can add more than just reverb to your sound. The full list of available filters is here:

http://panda3d.org/apiref.php?page=FilterProperties

Below is a sample program for adding a reverb effect

# This is just to ensure that we are using FMOD. In your application,
# please edit the Config.prc file that you distribute
from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "audio-library-name p3fmod_audio")

import direct.directbase.DirectStart
from pandac.PandaModules import FilterProperties

mySound = loader.loadSfx("models/audio/sfx/GUI_rollover.wav")
mySound.setLoop(True)
mySound.play()

fp = FilterProperties()
fp.addReverb(0.6, 0.5, 0.1, 0.1, 0.1)
base.sfxManagerList[0].configureFilters(fp)

run()


Panda3D Manual: 3DAudio

As we said Sound Effects and Music are handled separately in Panda3D. Each can handle 16 different sounds. This value is actually set as the audio-cache-limit in the panda config.prc (found in your install directory) and can be changed. There are times where either sound effects, music, or both should be disabled and later enabled. These commands affect entire categories of sounds. Passing True or False in the last 2 functions will disable or enable the respective groups.

base.disableAllAudio()
base.enableAllAudio()
base.enableMusic(bEnableMusic)
base.enableSoundEffects(bEnableSoundEffects)

Explained below is some advanced audio processing routines. Sound Effects and Music in code are implemented as AudioManager objects. You can access these audio managers with the following code.

soundEffectsMgr = base.sfxManagerList[0]
musicMgr = base.sfxManagerList[1]

Notice that sound effects are the first item in the sfxManagerList and the music is next.

A few things can be controlled with AudioManager objects. Setting the doppler factor, dropoff factor can be set through these objects. Positional audio is implemented through these objects. A wrapper Audio3DManager class has been implemented to help do positional audio. Audio3DManager takes as input an audioManager and a listener for the sound. A listener is the point of reference from where the sound should be heard. For a player in a Panda3D session, this could be the camera. Sounds further away from the camera will not be loud. Objects nearer to the camera will be loud.

from direct.showbase import Audio3DManager
audio3d = Audio3DManager.Audio3DManager(base.sfxManagerList[0], camera)

To create a sound that is positional, you need to use the loadSfx() function on the Audio3DManager rather than the normal loader.loadSfx() which is for non-positional sounds. e.g.

mySound = audio3d.loadSfx('blue.wav')

Sounds can be attached to objects such that when they move, the sound source will move along with them.

audio3d.attachSoundToObject( mySound, teapot )

You can use the Audio3DManager's setSoundVelocity() and setListenerVelocity() to set the velocity of sounds or the listener to get the doppler pitch shifting of moving objects. If you would like the Audio3DManager to help you adjust the velocity of moving objects automatically like it does with their position, you can call setSoundVelocityAuto() or setListenerVelocityAuto() like this:

audio3d.setSoundVelocity(sound,velocityVector)
audio3d.setListenerVelocity(velocityVector)

base.cTrav = CollisionTraverser()
audio3d.setSoundVelocityAuto(sound)
audio3d.setListenerVelocityAuto()

Currently, for the latter to work, a CollisionTraverser must be attached to base.cTrav as you see in the example. If you already have one assigned to do collision detection that will be sufficient.

The attenuation of moving sounds by distance and the doppler shift are based the way sound works in the real world. By default it assumes a scale of 1 panda unit equal to 1 foot. If you use another scale you'll need to use setDistanceFactor to adjust the scale.

audio3d.setDistanceFactor(scale)

You can adjust the rate that sounds sounds attenuate by distance. If you want to position the sounds but don't want the volume to be effected by their distance, you can set the drop off factor to 0.

audio3d.setDropOffFactor(scale)


Panda3D Manual: Multi-Channel

Summary

To Enable FMOD to use 5.1 Surround Sound uncomment/add the following line in teh config.prc file

fmod-use-surround-sound true

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Intervals

Panda3D's Interval system is a sophisticated mechanism for playback of scripted actions. With the use of Intervals, you can build up a complex interplay of animations, sound effects, or any other actions, and play the script on demand.

The core of system is the Interval class. There are several different kinds of Intervals, which will be discussed in detail in the following pages, but all of them have in common the following property: each Interval represents an action (or a series of actions) that occur over a specific, finite interval of time (hence the name).

The real power of the Interval system comes from Sequences and Parallels, which are a special kind of Interval that can contain nested Intervals of any kind (including additional Sequences and/or Parallels). By using these grouping Intervals, you can easily assemble complex scripts from the basic atoms.

Using Intervals

In any Panda3D module that uses Intervals, you should first import the interval module:

from direct.interval.IntervalGlobal import *

There are a handful of methods that all Intervals have in common.

To start an Interval playing, use one of the following:

interval.start()
interval.start(startT, endT, playRate)
interval.loop()
interval.loop(startT, endT, playRate)

The three parameters are optional. The startTime and endTime parameters define the subset of the interval to play; these should be given as times in seconds, measured from the start of the interval. The playRate, if specified, allows you play the interval slower or faster than real time; the default is 1.0, to play at real time.

Normally, an Interval will play to the end and stop by itself, but you can stop a playing Interval prematurely:

interval.finish()

This will stop the interval and move its state to its final state, as if it had played to the end. This is a very important point, and it allows you to define critical cleanup actions within the interval itself, which are guaranteed to have been performed by the time the interval is finished.

You can also temporarily pause and resume an interval:

interval.pause()
interval.resume()

If you pause an interval and never resume or finish it, the remaining actions in the interval will not be performed.

And you can jump around in time within an interval:

interval.setT(time)

This causes the interval to move to the given time, in seconds since the beginning of the interval. The interval will perform all of the actions between its current time and the new time; there is no way to skip in time without performing the intervening actions.

It is legal to set the time to an earlier time; the interval will do its best to reset its state to the previous state. In some cases this may not be possible (particularly if a Function Interval is involved).

Finally, there are a handful of handy query methods:

interval.getDuration()

Returns the length of the interval in seconds.

interval.getT()

Returns the current elapsed time within the interval, since the beginning of the interval.

interval.isPlaying()

Returns true if the interval is currently playing, or false if it was not started, has already finished, or has been explicitly paused or finished.

interval.isStopped()

Returns true if the interval has not been started, has already played to its completion, or has been explicitly stopped via finish(). This is not quite the same this as (not interval.isPlaying()), since it does not return true for a paused interval.


Panda3D Manual: Lerp Intervals

The LerpIntervals are the main workhorse of the Interval system. The word "lerp" is short for "linearly interpolate", and means to smoothly adjust properties, such as position, from one value to another over a period of time. You can use LerpIntervals to move and rotate objects around in your world.

The LerpIntervals are also the most complex of all of the intervals, since there are many different parameters that you might want to specify to control the lerp.

An overview of the NodePath-based LerpIntervals

Most LerpIntervals adjust the various transform properties of a NodePath, such as pos, hpr, and scale, and they all have a similar form. Consider the LerpPosInterval, which will smoothly move a model from one point in space to another:

i = LerpPosInterval(model, duration, pos, startPos = None,
                    other = None, blendType = 'noBlend',
                    bakeInStart = 1, fluid = 0, name = None)

The only required parameters are the model whose position is being changed, the length of time to apply the move, and the model's new position. The remaining parameters are all optional, and are often omitted.

Here is a breakdown of what each parameter means:

model | The model whose position is being changed. This should be a NodePath.
duration | The duration of the lerp, in seconds.
pos | The model's target position (the new position it will move to). Usually, this is a Point3(x, y, z), but as a special advanced feature, it might be a Python function that, when called, returns a Point3. If it is a function, it will be called at the time the lerp actually begins to play.
startPos | The starting position of the model at the beginning of the lerp. If this is omitted, the model will start from its current position. As with pos, above, this might be a Python function, which will be called at the time the lerp actually begins. Note that if you intend to move an object from its current position, it is better to omit this parameter altogether rather than try to specify it explicitly with something like startPos=object.getPos(), since the latter will be evaluated at the time the interval is created, not when it is played. This is especially true if you plan to embed a series of consecutive LerpIntervals within a Sequence.
other | Normally, this is set to None, to indicate a normal lerp. If a NodePath is passed in, however, it indicates that this is a relative lerp, and the pos and startPos will be computed as a relative transform from that NodePath. The relative transform is recomputed each frame, so if the other NodePath is animating during the lerp, the animation will be reflected here. For this reason, you should not attempt to lerp a model relative to itself.
blendType | This specifies how smoothly the lerp starts and stops. It may be any of the following values:
'easeIn' | The lerp begins slowly, ramps up to full speed, and stops abruptly.
'easeOut' | The lerp begins at full speed, and then slows to a gentle stop at the end.
'easeInOut' | The lerp begins slowly, ramps up to full speed, and then slows to a gentle stop.
'noBlend' | The lerp begins and ends abruptly.
bakeInStart | This is an advanced feature. Normally, this is 1, which means the original, starting position of the model is determined when the interval starts to play, and saved for the duration of the interval. You almost always want to keep it that way. If you pass this as 0, however, the starting position is cleverly re-inferred at each frame, based on the model's current position and the elapsed time in the lerp; this allows your application to move the model even while it is being lerped, and the lerp will adapt. This has nothing to do with controlling when the startPos parameter is evaluated.
fluid | If this is 1, then the lerp uses setFluidPos() rather than setPos() to animate the model. See Rapidly-Moving Objects. This is meaningful only when the collision system is currently active on the model. Since usually there is no reason to have the collision system active while a model is under direct application control, this parameter is rarely used.
name | This specifies the name of the lerp, and may be useful for debugging. Also, by convention, there may only be one lerp with a given name playing at any given time, so if you put a name here, any other interval with the same name will automatically stop when this one is started. The default is to assign a unique name for each interval.

The rest of the NodePath-based LerpIntervals

Many NodePath properties other than position may be controlled via a lerp. Here is the list of the various LerpIntervals that control NodePath properties:

LerpPosInterval(model, duration, pos, startPos)
LerpHprInterval(model, duration, hpr, startHpr)
LerpQuatInterval(model, duration, quat, startHpr, startQuat)
LerpScaleInterval(model, duration, scale, startScale)
LerpShearInterval(model, duration, shear, startShear)
LerpColorInterval(model, duration, color, startColor)
LerpColorScaleInterval(model, duration, colorScale, startColorScale)

Each of the above has a similar set of parameters as those of LerpPosInterval; they also have a similar shortcut (e.g. model.hprInterval(), etc.)

Finally, there are also a handful of combination LerpIntervals, that perform multiple lerps at the same time. (You can also achieve the same effect by combining several LerpIntervals within a Parallel, but these combination intervals are often simpler to use, and they execute just a bit faster.)

LerpPosHprInterval(model, duration, pos, hpr, startPos, startHpr)
LerpPosQuatInterval(model, duration, pos, quat, startPos, startQuat)
LerpHprScaleInterval(model, duration, hpr, scale, startHpr, startScale)
LerpQuatScaleInterval(model, duration, quat, scale, startQuat, startScale)
LerpPosHprScaleInterval(model, duration, pos, hpr, scale, startPos, startHpr, startScale)
LerpPosQuatScaleInterval(model, duration, pos, quat, scale, startPos, startQuat, startScale)
LerpPosHprScaleShearInterval(model, duration, pos, hpr, scale, shear, startPos, startHpr, startScale, startShear)
LerpPosQuatScaleShearInterval(model, duration, pos, quat, scale, shear, startPos, startQuat, startScale, startShear)

Other types of LerpInterval

Beyond animating NodePaths, you can create a LerpInterval that blends any parameter of any object over time. This can be done with a LerpFunctionInterval:

def myFunction(t):
  ... do something based on t ...

i = LerpFunc(myFunction, fromData = 0, toData = 1, duration = 0.0,
             blendType = 'noBlend', extraArgs = [], name = None)

This advanced interval has many things in common with all of the above LerpIntervals, but instead of directly animating a value, it instead calls the function you specify, passing a single floating-point parameter, t, that ranges from fromData to toData over the duration of the interval.

It is then up to your function to set whatever property of whatever object you like according to the current value of t.


Panda3D Manual: Function Intervals

Function intervals are different from function lerp intervals. While the function lerp interval passes data to a function over a period of time, a function interval will simply execute a function when called. As such, a function interval’s use really appears when combined with sequences and parallels. The function interval’s format is simple.

intervalName = Func(myFunction)

You pass the function without parentheses (i.e. you pass Func a function pointer). If myFunction takes arguments than pass them as arguments to Func as follows:

def myFunction(arg1,arg2):
   blah

intervalName = Func(myFunction, arg1, arg2)

Functions cannot be called on their own in sequences and parallels, so it is necessary to wrap them in an interval in order to call them. Since function intervals have no duration, they complete the moment they are called.


Panda3D Manual: Actor Intervals

Actor intervals allow actor animations to be played as an interval, which allows them to be combined with other intervals through sequences and parallels.

The subrange of the animation to be played may be specified via frames (startFrame up to and including endFrame) or seconds (startTime up to and including endTime). It may also be specified with a startFrame or startTime in conjunction with the duration, in seconds. If none of these is specified, then the default is to play the entire range of the animation.

If endFrame is before startFrame, or if the play rate is negative, then the animation will be played backwards.

You may specify a subrange that is longer than the actual animation, but if you do so, you probably also want to specify either loop = 1 or constrainedLoop = 1; see below.

The loop parameter is a boolean value. When it is true, it means that the animation restarts and plays again if the interval extends beyond the animation's last frame. When it is false, it means that the animation stops and holds its final pose when the interval extends beyond the animation's last frame. Note that, in neither case, will the ActorInterval loop indefinitely: all intervals always have a specific, finite duration, and the duration of an ActorInterval is controlled by either the duration parameter, the startTime/endTime parameters, or the startFrame/endFrame parameters. Setting loop=1 has no effect on the duration of the ActorInterval, it only controls what the actor does if you try to play past the end of the animation.

The parameter constrainedLoop works similarly to loop, but while loop = 1 implies a loop within the entire range of animation, constrainedLoop = 1 implies a loop within startFrame and endFrame only. That is, if you specify loop = 1 and the animation plays past endFrame, in the next frame it will play beginning at frame 0; while if you specify constrainedLoop = 1 instead, then the next frame after endFrame will be startFrame again.

All parameters other than the animation name are optional.

from direct.interval.ActorInterval import ActorInterval
myInterval = myactor.actorInterval(
    "Animation Name",
    loop= <0 or 1>,
    contrainedLoop= <0 or 1>,
    duration= D,
    startTime= T1,
    endTime= T2,
    startFrame= N1,
    endFrame= N2,
    playRate = R
    partName = PN,
    lodName = LN,
)


Panda3D Manual: Sound Intervals

See Loading and Playing Sounds and Music for basic information on how to load and play sounds.

Sound intervals play sounds from inside an interval. Like actor intervals, sound intervals have a loop parameter and the ability to be paused. Sound intervals also have volume and start time parameters.

mySound=loader.loadSfx("mySound.wav")

myInterval = SoundInterval(
    mySound,
    loop = 0 or 1,
    duration = myDuration,
    volume = myVolume,
    startTime = myStartTime
)

The looping provided by the sound interval is not clean. There will be a pause between loops of roughly a tenth of a second. See Loading and Playing Sounds and Music for a better way to loop sounds.


Panda3D Manual: Motion Path and Particle Intervals

Motion paths are an advanced feature of Panda3D, and they are discussed later. Still, motion paths have their own intervals. A motion path interval is much like a function interval in that there are no additional parameters other than the motion path and the NodePath it is affecting.

intervalName = MopathInterval(<Motion Path Name>,NodePath,‘<Name>’)

Particle effects can be run from inside intervals as well:

intervalName = ParticleInterval(
    <Particle Effect Name>,
    <Parent>,
    worldRelative = 1,
    loop = 0 or 1,
    duration = myDuration
)


Panda3D Manual: Sequences and Parallels

You will need to have this include statement to use Sequences and Parallels.

from direct.interval.IntervalGlobal import *

Sequences and Parallels can control when intervals are played. Sequences play intervals one after the other, effectively a “do in order” command. Parallels are a “do together,” playing all intervals at the same time. Both have simple formats, and every kind of interval may be used.

mySequence = Sequence(<Interval>,…,<Interval>, name = "Sequence Name")
myParallel = Parallel(<Interval>,…,<Interval>, name = "Parallel Name")

To add to sequences or parallels after creating them, use the append() method.

mySequence.append(<Interval>)
myParallel.append(<Interval>)

Sequences and Parallels may also be combined for even greater control. Also, there is a wait interval that can add a delay to Sequences. While it can be defined beforehand, it does not have to be.

delay = Wait(2.5)
pandaWalkSeq = Sequence(Parallel(pandaWalk,pandaWalkAnim),delay,
    Parallel(pandaWalkBack,pandaWalkAnim), Wait(1.0),Func(myFunction,arg1))

In the above example, a wait interval is generated. After that, a Sequence is made that uses a Parallel, the defined wait interval, another Parallel, and a wait interval, and a call to the function function myFunction is generated in the Sequence. Such Sequences can get very long very quick, so it may be prudent to define the internal Parallels and Sequences before creating the master Sequence.

One can do very powerful things with Sequences and Parallels. Examine this Sequence:

s=OnscreenImage('wav_is_playing.png')
s.reparentTo(aspect2d)
s.setTransparency(1)
fadeIn=s.colorScaleInterval( 3,Vec4(1,1,1,1),Vec4(1,1,1,0) )
fadeOut=s.colorScaleInterval( 3,Vec4(1,1,1,0) )
sound = loader.loadSfx('sound.wav')
Sequence( fadeIn,
          SoundInterval(sound),
          fadeOut
        ).start()
run()

It fades an image in, plays a sound, waits till sounds stops and then fades the image out. Doing this conventional way would require a class to store state, a task to check timings, and produce messy code.


Panda3D Manual: Position, Rotation and Scale Intervals

Panda3D can automatically generate intervals for position to a certain point or a rotation to a certain Hpr value. You can do this by calling the posInterval and hprInterval on the object.

#this lets the actor move to point 10,10,10
myInterval1=myActor.posInterval(1.0,Point3(10,10,10))

#twice as fast
myInterval2=myActor.posInterval(2.0,Point3(8,-5,10))

#you can specify a starting position, too.
myInterval3=myActor.posInterval(1.0,Point3(2,-3,8),startPos=Point3(2,4,1))

#rotate it
myInterval4=myActor.hprInterval(1.0,Vec3(180,90,0))

You can easily create Sequences and Parallels from these intervals:

mySequence=Sequence(myInterval2,myInterval4)
mySequence.start()
myParallel=Parallel(myInterval3,myInterval1)
myParallel.loop()

scaleInterval, posHprInterval, posScaleInterval, hprScaleInterval, and posHprScaleInterval work similar.

Note: The physics engine won't affect a Node that is moved using posInterval()!


Panda3D Manual: Projectile Intervals

Projectile intervals are used to move a nodepath through the trajectory of a projectile under the influence of gravity.

myInterval = ProjectileInterval(<Node Path>,
  startPos = Point3(X,Y,Z), endPos = Point3(X,Y,Z),
  duration = <Time in seconds>, startVel = Point3(X,Y,Z),
  endZ = Point3(X,Y,Z), gravityMult = <multiplier>, name = <Name>)

All parameters don't have to be specified. Here are a combination of parameters that will allow you to create a projectile interval. (If startPos is not provided, it will be obtained from the node's position at the time that the interval is first started. Note that in this case you must provide a duration.)

  • startPos, endPos, duration - go from startPos to endPos in duration seconds
  • startPos, startVel, duration - given a starting velocity, go for a specific time period
  • startPos, startVel, endZ - given a starting velocity, go until you hit a given Z plane

In addition you may alter gravity by providing a multiplier in 'gravityMult'. '2' will make gravity twice as strong, '.5' half as strong.'-1' will reverse gravity.

Here's a little snippet of code that will demonstrate projectile intervals:

camera.setPos(0,-45,0)

# load the ball model
self.ball = loader.loadModel("smiley")
self.ball.reparentTo(render)
self.ball.setPos(-15,0,0)

# setup the projectile interval
self.trajectory = ProjectileInterval(self.ball,
                                     startPos = Point3(-15,0,0),
                                     endPos = Point3(15,0, 0), duration = 1)
self.trajectory.loop()


Panda3D Manual: Tasks and Event Handling

Tasks are subroutines that you write that get called by Panda every frame. Event handlers are subroutines that you write that get called by Panda when certain special events occur. Together, these two mechanisms enable you to update your panda world between rendering steps.


Panda3D Manual: Tasks

Tasks are special functions that are called once, each frame, while your application executes. They are similar in concept to threads, but in Panda, tasks are not generally separate threads; instead, all tasks are run cooperatively, one at a time, within the main thread. This design simplifies game programming considerably by removing the requirement to protect critical sections of code from mutual access. (But see Task Chains in the next section if you really want to use threading.)

When you start Panda3D by importing DirectStart, a handful of tasks are created by default, but you are free to add as many additional tasks as you like.

The Task Function

A task is defined with a function or class method; this function is the main entry point for the task and will be called once per frame while the task is running. By default, the function receives one parameter, which is the task object; the task object carries information about the task itself, such as the amount of time that the task has been running.

Your task function should return when it has finished processing for the frame. Because all tasks are run in the same thread, you must not spend too much time processing any one task function; the entire application will be locked up until the function returns.

The task function may return either Task.cont to indicate that the task should be called again next frame, or Task.done to indicate that it should not be called again. If it returns None (which is to say, it does not return anything), then the default behavior is to stop.

You can check how long your task has been running by checking task.time in your task function. You can also check how many times the task function has been run by using task.frame.

The below example imports the Task module and shows a function used as a task.

from direct.task import Task

#This task runs for two seconds, then prints done
def exampleTask(task):
  if task.time < 2.0:
    return Task.cont
  print 'Done'
  return Task.done

Task Return Values

The value returned from a task affects how the task manager handles that task going forward.

Variable | Purpose
Task.done | Specifies that a task is finished and removes it from the task manager.
Task.cont | Perform the task again next frame.
Task.again | Perform the task again, using the same delay as initially specified.
None | Same as done.

The Do-Later Task

A useful special kind of task is the do-later: this is similar to a task, but rather than being called every frame it will be called only once, after a certain amount of time (in seconds) has elapsed. You can, of course, implement a do-later task with a regular task that simply does nothing until a certain amount of time has elapsed (as in the above example), but using a do-later is a much more efficient way to achieve the same thing, especially if you will have many such tasks waiting around.

taskMgr.doMethodLater(delayTime, myFunction, 'Task Name')

In this example myFunction must accept a task variable. If you wish to use a function that does not accept a task variable:

taskMgr.doMethodLater(delayTime, myFunction, 'Task Name', extraArgs = [variables])

Note: if you wish to call a function which takes no variables simply pass extraArgs = []

Do-Later task's can be repeated from the task function by returning task.again. You can also change the delay of the Do-Later task by changing task.delayTime, but changing this will not have any affect on the task's actual delay time until the next time it gets added to the do-later list, for instance by returning Task.again.

# This task increments itself so that the delay between task executions
# gradually increases over time. If you do not change task.delayTime
# the task will simply repeat itself every 2 seconds
def myFunction(task):
    print "Delay:",task.delayTime
    print "Frame:",task.frame
    task.delayTime += 1
    return task.again

myTask = taskMgr.doMethodLater(2, myFunction, 'tickTask')

If you wish to change the delayTime outside of the task function itself, and have it make an immediate affect, you can remove and re-add the task by hand, for instance:

taskMgr.remove(task)
task.delayTime += 1
taskMgr.add(task)

Although there is a public member task.wakeTime which stores the time at which the task should wake up, you should not attempt to modify this. Doing so may appear to work in some simple cases, but will actually invalidate the Task Manager's internal priority queue, potentially causing other tasks to wake up later or sooner than they are supposed to. (In Panda version 1.6 and later, changing this value is specifically disallowed.)

The Task Object

The task object is passed into all Task Functions. There are several variables accessible in the func object, these are:

Variable | Returns
task.time | A float that indicates how long this task function has been running since the first execution of the function. The timer is running even when the task function is not being executed.
task.frame | An integer that counts the number of elapsed frames since this function was added. Count may start from 0 or 1.
task.id | An integer that gives the unique id assigned to this task by the Task Manager.
task.name | The task name assigned to the task function.

The Task Manager

All tasks are handled through the global Task Manager object, called taskMgr in Panda3D. The Task Manager keeps a list of all currently-running tasks. To add your task function to the task list, call taskMgr.add() with your function and an arbitrary name for the task. taskMgr.add() returns a Task which can be used to remove the task later on.

taskMgr.add(exampleTask, 'MyTaskName')

You can add extra arguments to the call through the extraArgs parameter. When you add extraArgs, the task parameter is no longer sent to your function by default. If you still want it, make sure to set appendTask to true. appendTask=True makes the task the last argument sent to the function.

taskMgr.add(exampleTask, 'MyTaskName', extraArgs=[a,b,c], appendTask=True)

Although normally each task is given a unique name, you may also create multiple different tasks with the same name. This can be conventient for removing many task functions at the same time. Each task remains independent of the others, even if they have the same name; this means that a task function returning Task.done will not affect any other task functions.

taskMgr.add(taskFunc,'Existing TaskName')

To remove the task and stop it from executing, call taskMgr.remove(). You can pass in either the name of the task, or the task object (which was returned by taskMgr.add(), above).

taskMgr.remove('MyTaskName')

You may add a cleanup function to the task function with the uponDeath parameter. Similar to task functions, the uponDeath function has a task object as a parameter. The cleanup function is called whenever the task finishes, for instance by return Task.done, or when it is explicitly removed via taskMgr.remove().

taskMgr.add(exampleTask,'TaskName',uponDeath=cleanupFunc)

To print the list of tasks currently running, simply print out taskMgr. Among your own tasks, you may see the following system tasks listed:

dataloop | Processes the keyboard and mouse inputs
tkloop | Processes Tk GUI events
eventManager | Processes events generated by C++ code, such as collision events
igloop | Draws the scene

There also is graphical interface for managing tasks. This is very useful for having a look at the tasks while your application is running.

taskMgr.popupControls()

Task timing

To see the specific timing information for each task when you print taskMgr, add the following line to your Config.prc file

task-timer-verbose #t

(see The Configuration File for config syntax)

Examples

uponDeath

taskAccumulator = 0

def cleanUp(task):
  print "Task func has accumulated",taskAccumulator
  #Reset the accumulator
  taskAccumulator = 0

#A task that runs forever
def taskFunc(task):
  taskAccumulator += 1
  return Task.cont

def taskStop(task):
  taskMgr.remove('Accumulator')

#Add the taskFunc function with an uponDeath argument
taskMgr.add(taskFunc,'Accumulator',uponDeath=cleanUp)
#Stops the task 2 seconds later
taskMgr.doMethodLater(2, taskStop, 'Task Stop')


Panda3D Manual: Event Handlers

Events occur either when the user does something (such as clicking a mouse or pressing a key) or when sent by the script using messenger.send(). When an event occurs, Panda's "messenger" will check to see if you have written an "event handler" routine. If so, your event handler will be called. The messenger system is object-oriented, to create an event handler, you have to first create a class that inherits from DirectObject. Your event handler will be a method of your class.

Defining a class that can Handle Events

The first step is to import class DirectObject:

from direct.showbase import DirectObject

With DirectObject loaded, it is possible to create a subclass of DirectObject. This allows the class to inherit the messaging API and thus listen for events.

class myClassName(DirectObject.DirectObject):

The sample below creates a class that can listen for events. The "accept" function notifies panda that the printHello method is an event handler for the mouse1 event. The "accept" function and the various event names will be explained in detail later.

class Hello(DirectObject.DirectObject):
  def __init__(self):
    self.accept('mouse1',self.printHello)
  def printHello(self):
    print 'Hello!'
h = Hello()

Event Handling Functions

Events first go to a mechanism built into panda called the "Messenger." The messenger may accept or ignore events that it receives. If it accepts an event, then an event handler will be called. If ignored, then no handler will be called.

An object may accept an event an infinite number of times or accept it only once. If checking for an accept within the object listening to it, it should be prefixed with self. If the accept command occurs outside the class, then the variable the class is associated with should be used.

myDirectObject.accept('Event Name',myDirectObjectMethod)
myDirectObject.acceptOnce('Event Name',myDirectObjectMethod)

Specific events may be ignored, so that no message is sent. Also, all events coming from an object may be ignored.

myDirectObject.ignore('Event Name')
myDirectObject.ignoreAll()

Finally, there are some useful utility functions for debugging. The messenger typically does not print out when every event occurs. Toggling verbose mode will make the messenger print every event it receives. Toggling it again will revert it to the default. A number of methods exist for checking to see what object is checking for what event, but the print method will show who is accepting each event. Also, if accepts keep changing to the point where it is too confusing, the clear method will start the messenger over with a clear dictionary.

messenger.toggleVerbose()
print messenger
messenger.clear()

Sending Custom Events

Custom events can be sent by the script using the code

messenger.send('Event Name')

A list of parameters can optionally be sent to the event handler. Parameters defined in accept() are passed first, and then the parameters defined in send(). for example this would print out "eggs sausage foo bar":

class Test(DirectObject):
    def __init__(self):
        self.accept('spam',self.OnSpam,['eggs','sausage'])
    def OnSpam(self,a,b,c,d):
        print a,b,c,d
Test()
messenger.send('spam',['foo','bar'])
run()

A Note on Object Management

When a DirectObject accepts an event, the messenger retains a reference to that DirectObject. To ensure that objects that are no longer needed are properly disposed of, they must ignore any messages they are accepting.

For example, the following code may not do what you expect:

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from pandac.PandaModules import *

class Test(DirectObject.DirectObject):
    def __init__(self):
        self.accept("FireZeMissiles",self._fireMissiles)

    def _fireMissiles(self):
        print "Missiles fired! Oh noes!"

foo=Test() # create our test object

del foo    # get rid of our test object

messenger.send("FireZeMissiles") # oops! Why did those missiles fire?
run()

Try the example above, and you'll find that the missiles fire even though the object that would handle the event had been deleted.

One solution (patterned after other parts of the Panda3d architecture) is to define a "destroy" method for any custom classes you create, which calls "ignoreAll" to unregister from the event-handler system.

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from pandac.PandaModules import *

class Test(DirectObject.DirectObject):
    def __init__(self):
        self.accept("FireZeMissiles",self._fireMissiles)

    def _fireMissiles(self):
        print "Missiles fired! Oh noes!"

    # function to get rid of me
    def destroy(self):
        self.ignoreAll()

foo=Test() # create our test object

foo.destroy()    # get rid of our test object
del foo

messenger.send("FireZeMissiles") # No missiles fire
run()


Panda3D Manual: Main Loop

A typical form of a Panda program might look like:

from direct.showbase.DirectObject import DirectObject # To listen for Events

class World(DirectObject):
   __init__(self):
      #initialize instance self. variables here
   method1():
      # Panda source goes here

w = World()
run() # main loop

run is a function that never returns. It is the main loop.

For an alternative, run() could not be called at all. Panda doesn't really need to own the main loop.

Instead, taskMgr.step() can be called intermittently, which will run through one iteration of Panda's loop. In fact, run() is basically just an infinite loop that calls Task.step() repeatedly.

taskMgr.step() must be called quickly enough after the previous call to taskMgr.step(). This must be done quick enough to be faster than the frame rate.

This may useful when an imported third party python module that also has its own event loop wants and wants to be in control of program flow. A third party example may be Twisted, the event-driven networking framework.

The solution to this problem is to let Panda3D's loop be controlled entirely by twisted's event loop. You will need to use the LoopingCall method to add Panda's taskMgr.step() method to twisted's event loop. Then, you need to call reactor.run() instead of Panda3D's run() method to run twisted's event loop. Here's an example on how this will work:

from twisted.internet.task import LoopingCall
from twisted.internet import reactor

LoopingCall(taskMgr.step).start(1 / Desired_FPS)
reactor.run()

You will need to replace Desired_FPS by the desired framerate, that is, how much times you want Panda3D to redraw the frame per second. Please note that reactor.run() is blocking, just like Panda's run() method.

Another third party example is wxPython GUI, that is a blending of the wxWidgets C++ class library with the Python programming language. Panda's run() function, and wx's app.MainLoop() method, both are designed to handle all events and never return. They are each supposed to serve as the one main loop of the application. Two main loops can not effectively run an application.

wxPython also supplies a method that can be called occasionally, instead of a function that never returns. In wx's case, it's app.Dispatch().

A choice can be made whether or not to make wx handle the main loop, and call taskMgr.step() intermittently, or whether or not to make Panda handle the main loop, and call app.Dispatch() intermittently. The better performance choice is to have panda handle the main loop.

In the case that Panda handles the main loop, a task needs to be started to call app.Dispatch() every frame, if needed. Instead of calling wxPython's app.MainLoop(), do something like the following:

app = wx.App(0)

def handleWxEvents(task):
   while app.Pending():
      app.Dispatch()
   return Task.cont

taskMgr.add(handleWxEvents, 'handleWxEvents')
run()  # panda handles the main loop

In the case that wxPython handles the main loop using app.MainLoop(), to keep the framerate quick and reduce the CPU, add sleep(0.001) in the body of the program. This will yield to panda. After the sleep is over, control will return to wxPython. wxPython can then check for user events. wxPython's user generated callback events are generally generated only at infrequent intervals (based on when the user is interacting with the window). This is appropriate for a 2-D application that is completely response-driven, but not very useful for a 3-D application that continues to be active even when a user is not interacting with it.


Panda3D Manual: Text and Image Rendering

Panda includes support for easily rendering dynamic text onscreen or in the 3-d world. It supports full use of the Unicode character set, so it can easily render international languages (including Asian languages, when used with an appropriate font).

There are three interfaces for creating text, depending on your requirements: the TextNode, which is the fundamental text-rendering class and serves as the implementation for the other two, OnscreenText, a simple high-level wrapper around TextNode, OnscreenImage, the same as OnscreenText but now for images, and DirectLabel, which integrates with the rest of the DirectGUI system.

International character sets

By default, Panda assumes the text strings you give it are formatted in the iso8859 character set, also called latin-1 on Linux. This is a standard character set that uses one byte per character and supports most Western European languages, and is likely to be the character set you're using anyway. If you have need for more characters than are supported by iso8859, you must use a more advanced encoding system like utf-8 (which may use one, two, or three bytes per character). To use utf-8, put the following in your Config.prc file:

text-encoding utf8

And then make sure the strings you pass as text strings are encoded using the "utf-8" encoding method, for instance by saving your Python source files in "utf-8" (this can actually be tricky to do properly in Windows; we recommend you use a non-Microsoft editor such as Emacs or Eclipse to do this).


Panda3D Manual: Text Fonts

Panda3D can render text using a variety of fonts. If your version of Panda3D has been compiled with support for the FreeType library (the default distribution of Panda3D has been), then you can load any TTF file, or any other font file type that is supported by FreeType, directly:

font = loader.loadFont('arial.ttf')

The named file is searched for along the model-path, just like a regular egg file. You can also give the full path to the font file if you prefer (but remember to observe the Panda Filename Syntax).

It is also possible to pre-generate a font with the egg-mkfont command-line utility:

egg-mkfont -o arial.egg arial.ttf

This will generate an egg file (arial.egg in the above example) and an associated texture file that can then be loaded as if it were a font:

font = loader.loadFont('arial.egg')

There are several options you can specify to the egg-mkfont utility; use "egg-mkfont -h" to give a list.

The advantages to pre-generating a font are (a) the resulting egg file can be used by a version of Panda that does not include support for FreeType, and (b) you can apply some painterly effects to the generated texture image using Photoshop or a similar program. On the other hand, you have to decide ahead of time which characters you will want to use from the font; the default is the set of ASCII characters.

There are three default font files supplied with the default distribution of Panda3D in the models subdirectory; these are "cmr12.egg", a Roman font, "cmss12.egg", a Sans-Serif font, and "cmtt12.egg", a Teletypewriter-style fixed-width font. These three fonts were generated from free fonts provided with the Metafont utility (which is not a part of Panda3D). There is also a default font image which is compiled into Panda if you do not load any other font.


Panda3D Manual: Text Node

The most fundamental way to render text in Panda3D is via the TextNode interface. This may be a little more work than the OnscreenText or DirectLabel objects, but it gives you a lot more control over the appearance of the text.

To use a TextNode, simply create one and call setText() to set the actual text to display, and then parent the TextNode wherever you like (you can put it under aspect2d to make a 2-d onscreen text, or you can put it in the 3-d world for in-the-world text). Note that if you parent the text to render2d or aspect2d, you will probably need to give it a fairly small scale, since the coordinate space of the whole screen in render2d is in the range (-1, 1).

text = TextNode('node name')
text.setText("Every day in every way I'm getting better and better.")
textNodePath = aspect2d.attachNewNode(text)
textNodePath.setScale(0.07)

A simple TextNode example

Note that the TextNode constructor takes a string name, which is not related to the text that is to be displayed. Also note that the default text color is white; we show it as black in these examples to make it easier to see on the white background.

There are a large number of properties that you can specify on the TextNode to control the appearance of your text.

Font

cmr12 = loader.loadFont('cmr12.egg')
text.setFont(cmr12)

You may use any font you like, including a TTF file; see Text Fonts.

Small Caps

text.setSmallCaps(1)

setSmallCaps() accepts a boolean true or false value; set it true to enable small caps mode. In this mode, instead of rendering lowercase letters, the TextNode renders capital letters that are a bit smaller than the true capital letters. This is an especially useful feature if your font of choice doesn't happen to include lowercase letters.

You can also specify the relative scale of the "lowercase" letters:

text.setSmallCapsScale(0.4)

Where 1.0 is exactly the same size as the capital letters, and 0.5 is half the size. The default is 0.8.

Slant

text.setSlant(0.3)

Slant can be used to give an effect similar to italicizing. The parameter value is 0.0 for no slant, or 1.0 for a 45-degree rightward slant. Usually values in the range 0.2 to 0.3 give a pleasing effect. You can also use a negative number to give a reverse slant.

Color

text.setTextColor(1, 0.5, 0.5, 1)

The color is specified with its r, g, b, a components. Note that if a is not 1, the text will be slightly transparent.

Shadow

text.setShadow(0.05, 0.05)
text.setShadowColor(0, 0, 0, 1)

A shadow is another copy of the text, drawn behind the original text and offset slightly to the right and down. It can help make the text stand out from its background, especially when there is not a high contrast between the text color and the background color. (The text color in this example is exactly the same pink color used in the example above, but note how much clearer it is with the shadow.) The downside of a shadow is that it doubles the number of polygons required to render the text.

Setting a shadow requires two calls: setShadow() accepts a pair of numbers indicating the distance to shift the shadow right and down, respectively, in screen units; these are usually very small numbers like 0.05. setShadowColor() accepts the r, g, b, a color of the shadow; the default is white.

Wordwrap

By default, text will be formatted on one line, unless it includes newline characters. Enabling wordwrap will automatically break the text into multiple lines if it doesn't fit within the specified width.

text.setWordwrap(15.0)

The parameter to setWordwrap() should be the maximum width of each line, in screen units.

Alignment

Text is left-aligned by default; that is, it starts at the position you specify with textNodePath.setPos() and goes out to the right from there. If you have multiple lines of text, you may prefer to center the text or right-align it instead:

text.setAlign(TextNode.ACenter)

The parameter to setAlign() should be one of TextNode.ALeft, TextNode.ACenter, or TextNode.ARight. Note that changing the alignment of the text will shift its position relative to the starting point.

Frame

You can specify that a thin frame should be drawn around the entire text rectangle:

text.setFrameColor(0, 0, 1, 1)
text.setFrameAsMargin(0.2, 0.2, 0.1, 0.1)

As with the shadow, specifying a frame requires two calls; one to specify the color, and another to specify the dimensions of the frame. The call setFrameAsMargin() specifies four parameters, which represent the amount of space to insert between the edge of the text and the frame on the left, right, bottom, and top edges, respectively. All four parameters can be 0.0 to tightly enclose the text (although some fonts render a little bit outside their reported boundaries).

Card

Finally, you can draw a solid card behind the text rectangle:

text.setCardColor(1, 1, 0.5, 1)
text.setCardAsMargin(0, 0, 0, 0)

This can also help to make the text easier to read when it is against a similar-colored background. Often, you will want the card to be semitransparent, which you can achieve by specifying an alpha value of 0.2 or 0.3 to the setCardColor() method.

The parameters to setCardAsMargin() are the same as those for setFrameAsMargin(), above: the distance to extend the card beyond the left, right, bottom, and top edges, respectively. (In this example, we have both the card and the frame on at the same time, and you can see that the card exactly fits the text, while the frame extends a little bit beyond--showing the effects of the slightly different parameters passed to setFrameAsMargin() and setCardAsMargin() in this example.)

Picking a Text Node

Strictly speaking, a TextNode has no geometry, so you can't pick it.

There are two possible workarounds.

(1) Create your own card to go behind the TextNode, using e.g. CardMaker. You should be able to say cardMaker.setFrame(textNode.getFrameActual()) to set the card to be the same dimensions as the text's frame. Then you will need to either offset the text a few inches in front of the card to prevent Z-fighting, or explicitly decal the text onto the card, with something like this:

card = NodePath(cardMaker.generate())
tnp = card.attachNewNode(textNode)
card.setEffect(DecalEffect.make())

(2) Instead of parenting the TextNode directly to the scene, parent the node returned by textNode.generate() instead. This will be a static node that contains the polygons that render the text. If the text changes in the future, it won't automatically update the geometry in this node; you will have to replace this node with the new result of textNode.generate(). But this node will be 100% pickable. In particular, if you have specified textNode.setCardDecal(1), then the first child of the node should be the card geometry.


Panda3D Manual: OnscreenText

The OnscreenText object is a convenience wrapper around TextNode. You can use it as a quick way to put text onscreen without having to go through the trouble of creating a TextNode and setting properties on it. However, it doesn't have the full range of rendering options that you can get with TextNode directly; and it doesn't support the DirectGUI features of a DirectLabel. Use an OnscreenText whenever you want a quick way to display some ordinary text without a lot of fancy requirements.

from direct.gui.OnscreenText import OnscreenText
textObject = OnscreenText(text = 'my text string', pos = (-0.5, 0.02), scale = 0.07)

The OnscreenText object inherits from NodePath, so all of the standard NodePath operations can be used on the text object. When you are ready to take the text away, use:

textObject.destroy()

The following keyword parameters may be specified to the constructor:

text | the actual text to display. This may be omitted and specified later via setText() if you don't have it available, but it is better to specify it up front.
style | one of the pre-canned style parameters defined at the head of OnscreenText.py. This sets up the default values for many of the remaining parameters if they are unspecified; however, a parameter may still be specified to explicitly set it, overriding the pre-canned style.
pos | the x, y position of the text on the screen.
scale | the size of the text. This may either be a single float (and it will usually be a small number like 0.07) or it may be a 2-tuple of floats, specifying a different x, y scale.
fg | the (r, g, b, a) foreground color of the text. This is normally a 4-tuple of floats or ints.
bg | the (r, g, b, a) background color of the text. If the fourth value, a, is nonzero, a card is created to place behind the text and set to the given color.
shadow | the (r, g, b, a) color of the shadow behind the text. If the fourth value, a, is nonzero, a little drop shadow is created and placed behind the text.
frame | the (r, g, b, a) color of the frame drawn around the text. If the fourth value, a, is nonzero, a frame is created around the text.
align | one of TextNode.ALeft, TextNode.ARight, or TextNode.ACenter.
wordwrap | either the width to wordwrap the text at, or None to specify no automatic word wrapping.
font | the font to use for the text.
parent | the NodePath to parent the text to initially; the default is aspect2d.
mayChange | pass true if the text or its properties may need to be changed at runtime, false if it is static once created (which leads to better memory optimization). The default is false.

OnscreenImage works similar, but it shows an image on the screen instead of text.


Panda3D Manual: OnscreenImage

Just like OnscreenText, you can use OnscreenImage as a quick way to put an image onscreen. Use an OnscreenImage whenever you want a quick way to display an ordinary image without a lot of fancy requirements.

from direct.gui.OnscreenImage import OnscreenImage
imageObject = OnscreenImage(image = 'myImage.jpg', pos = (-0.5, 0, 0.02))

If you want, you can change the image into another one using setImage():

imageObject.setImage('myImage2.jpg')

When you want to take the image away, use:

imageObject.destroy()

The following keyword parameters may be specified to the constructor:

image | the actual geometry to display or a file name. This may be omitted and specified later via setImage() if you don't have it available.
pos | the x, y, z position of the geometry on the screen. This maybe a 3-tuple of floats or a vector. y should be zero
scale | the size of the geometry. This may either be a single float, a 3-tuple of floats, or a vector, specifying a different x, y, z scale. y should be 1
hpr | the h, p, r of the geometry on the screen. This maybe a 3-tuple of floats or a vector.
color | the (r, g, b, a) color of the geometry. This is normally a 4-tuple of floats or ints.
parent | the NodePath to parent the text to initially; the default is aspect2d.

NOTE: To enable transparency in images, you must first set the TransparencyAttrib, otherwise the transparent parts of the image will be shown black:

from pandac.PandaModules import TransparencyAttrib
self.myImage=OnscreenImage(image = 'myImage.png', pos = (0, 0, 0))
self.myImage.setTransparency(TransparencyAttrib.MAlpha)

Since GIF's are not supported you should use PNG or TGA if you need transparency.


Panda3D Manual: Embedded Text Properties

It's possible to change text properties in the middle of a paragraph. To do this, you must first define the different kinds of text properties you might want to change to, and give each one a name; then you can embed special characters in your text string to switch these pre-defined text properties in and out.

Defining your text properties

You can create any number of TextProperties objects. Each of these can store a different set of text properties, any of the text properties that you can set directly on a TextNode. These include the per-character attributes such as font, color, shadow, and slant, as well as per-line formatting properties such as alignment and wordwrap.

tpRed = TextProperties()
tpRed.setTextColor(1, 0, 0, 1)
tpSlant = TextProperties()
tpSlant.setSlant(0.3)
tpRoman = TextProperties()
tpRoman.setFont(cmr12)

You can set as many or as few different attributes on any one TextProperties object as you like. Only the attributes you specify will be applied to the text string; any attributes you don't mention will remain unchanged when you apply the TextProperties. In the above example, applying the tpRed structure to a particular text string will only change the text color to red; other properties, such as slant, shadow, and font, will remain whatever they were previously. Similarly for tpSlant, which only changes the slant, and tpRoman, which only changes the font.

Registering the new TextProperties objects

You will need a pointer to the global TextPropertiesManager object:

tpMgr = TextPropertiesManager.getGlobalPtr()

After you have created your TextProperties objects, you must register each one with the TextPropertiesManager, under a unique name:

tpMgr.setProperties("red", tpRed)
tpMgr.setProperties("slant", tpSlant)
tpMgr.setProperties("roman", tpRoman)

Referencing the TextProperties in text strings

Now you're ready to put the special characters in your text string to activate these mode changes. To do this, you will use the special character '\1', or the ASCII 0x01 character. You use the \1 character twice, as a kind of quotation mark before and after the name you have used above to register your TextProperties object, e.g. '\1red\1' to activate tpRed, or '\1slant\1' to activate tpSlant.

The sequence '\1red\1' acts as a push operation. It applies tpRed to the current text properties, but also remembers the previous properties. To go back to the previous properties, use the character '\2' by itself. You can nest property changes like this; each '\2' will undo the most recent '\1name\1' that is still in effect.

The following text string:

text.setText("Every day in \1slant\1every way\2 I'm \1red\1getting \1roman\1better \1slant\1and\2 better.\2\2")

Looks like this:

Every day in every way I'm getting better and better

You can use these special characters in any Panda construct that generates text, including TextNode, OnscreenText, and any DirectGui object.


Panda3D Manual: DirectGUI

Panda3D comes with a set of tools for the creation of a graphical interface for any program. The DirectGui system is used to create buttons, labels, text entries, and frames within the program. All of these items can be decorated with text, images, and 3D graphics. Commands may be associated with these items as well. Since these objects inherit from the NodePath class, anything done to a NodePath may be done to them, such as show()/hide(), setPos(), posInterval(), and so on. Also, since DirectGui objects are by default parented to the node aspect2d, they will stay on the screen no matter how the user navigates through the world.

The direct-gui-edit option in the Config.prc file allows the user to use the middle mouse button to move around widgets, and resize them while holding the control key; this is very useful to lay a screen out during development. If you need to turn this ability off for an individual object, set its enableEdit keyword parameter to False.

All of the DirectGui objects are constructed in a similar way:

from direct.gui.DirectGui import *
myObject = Directxxxxxx(keyword=value, keyword=value, ...)

Each DirectGui object may contain any of four fundamental pieces that determine its appearance. There may be an optional text, an optional geom, an optional image, and an optional frame.

A DirectGui's text label may be any arbitrary text string, and whatever text string you supply is automatically created using the OnscreenText interface and centered on the object. You can specify the text string using the text keyword. You can also specify further parameters to control the appearance or placement of the text using the form text_parameter, where parameter is any valid keyword to the OnscreenText constructor.

A DirectGui's geom can be any NodePath that you design, to represent the appearance of the gui object. Typically, this will be a model that you created via the command egg-texture-cards, based on a texture that you painted by hand. Using this interface, you can completely customize the look of the DirectGui object to suit your needs. You can specify the geom object using the geom keyword, and like the text parameter, you can also control the geom's placement using keywords like geom_parameter.

The image is less often used. It is the filename of a texture image (or an already-loaded Texture object). It is intended for displaying a simple texture image for which you don't already have a model created via egg-texture-cards. A default card will be created to display this texture, with a bounding box of (-1, 0, -1) to (1, 0, 1); that is, a square with sides of length 2 units, centered on the origin. You can position and scale this card with the keywords image_pos and image_scale. See also OnscreenImage

Finally, the DirectGui may have a frame created for it. This is typically a gray rectangular background with an optional bevel. There are a handful of different frame styles; you can use the relief keyword to select from one of the available styles; your choices are SUNKEN, RAISED, GROOVE, or RIDGE. You can also specify relief = None to avoid creating a frame polygon altogether (this is commonly done when you have specified your own geom object with the geom keyword).

The overall size of the DirectGui object is controlled with the frameSize keyword. This is a four-tuple of floating-point numbers of the form (left, right, bottom, top), which specifies the bounding box region of the DirectGui object. That is, the lower-left corner will be at position (left, 0, bottom), and the upper-right will be at (right, 0, top). Note that these values represent coordinates from the origin of the frame. Setting the frameSize to (-0.1, 0.1, -0.1, 0.1), for instance, will create a box, 0.2 units wide and 0.2 units in height, with 0,0 being the center of the frame located at pos on the screen.

The frameSize keyword is optional. If you omit it, the default frameSize is computed based on the bounding box of the text, geom, and/or image that you have specified.

The following is a list of keywords that are typically available to DirectGui objects of all kinds. Individual kinds of DirectGui objects may add more options to this list, but these keywords are not repeated on each of the following pages, for brevity:

Keyword | Definition | Value
text | Text to be displayed on the object | String
text_bg | Background color of the text on the object | (R,G,B,A)
text_fg | Color of the text | (R,G,B,A)
text_pos | Position of the displayed text | (x,z)
text_roll | Rotation of the displayed text | Number
text_scale | Scale of the displayed text | (sx,sz)
text_* | Parameters to control the appearance of the text | Any keyword parameter appropriate to OnscreenText.
frameSize | Size of the object | (Left,Right,Bottom,Top)
frameVisibleScale | Relative scale of the visible frame to its clickable bounds. Useful for creating things like the paging region of a slider, which is visibly smaller than the acceptable click region (the height of the thumb). | (hscale, vscale)
frameColor | Color of the object's frame | (R,G,B,A)
relief | Relief appearance of the frame | SUNKEN, RAISED, GROOVE, RIDGE, FLAT, or None
invertedFrames | If true, switches the meaning of SUNKEN and RAISED | 0 or 1
borderWidth | If relief is SUNKEN, RAISED, GROOVE, or RIDGE, changes the size of the bevel | (Width,Height)
image | An image to be displayed on the object | image filename or Texture object
image_pos | Position of the displayed image | (x,y,z)
image_hpr | Rotation of the displayed image | (h,p,r)
image_scale | Scale of the displayed image | (sx,sy,sz)
geom | A geom to represent the object's appearance | NodePath
geom_pos | Position of the displayed geom | (x,y,z)
geom_hpr | Rotation of the displayed geom | (h,p,r)
geom_scale | Scale of the displayed geom | (sx,sy,sz)
pos | Position of the object | (X,Y,Z)
hpr | Orientation of the object | (H,P,R)
scale | Scale of the object | Number
pad | When frameSize is omitted, this determines the extra space around the geom or text's bounding box by which to expand the default frame | (Width,Height)
state | The initial state of the object | NORMAL or DISABLED
frameTexture | Texture applied directly to the frame generated when relief is FLAT | image filename or Texture object
enableEdit | Affects direct-gui-edit functionality | 0 or 1
suppressKeys | If 1, suppresses triggers of global keyboard-related Panda events (not part of the GUI system) | 0 or 1
suppressMouse | If 1, suppresses triggers of global mouse-related Panda events (e.g. camera controls) | 0 or 1
sortOrder | Specifies render order for overlapping objects. Higher numbers are drawn in front of lower numbers. | Number
textMayChange | Whether the text of an object can be changed after creation | 0 or 1

Remember that the axes for Panda3D use x for left and right, y for in and out of the screen, and z for up and down. An object's frame is always in the background of the object. The geom, if any, is shown in front of the frame, and text is shown in front of the geom.

It is possible to change most of these values after object creation, using:

myDirectObject['keyword'] = value

Most properties can be updated in this way, although position and other transform-related values cannot be updated via the keyword parameters--attempts to update them will silently fail. Instead, use the NodePath methods to change the object's transform.

Some types of updates, such as changing the text or the geom, may also change the size of the object. If you change any of these properties after the object has been created, it is necessary to tell the object to re-determine its size:

myDirectObject.resetFrameSize()

If you don't do this, you may find, for example, that a button isn't clickable because it believes it has a zero-width frame.

To permanently remove a DirectGUI object, you should use the method:

myDirectObject.destroy()

It is not sufficient to simply call removeNode(), since the DirectGUI system adds a number of messenger hooks that need to be cleaned up. However, if you have a hierarchy of DirectGUI objects, for instance a number of buttons parented to a frame, it is sufficient to call destroy() only on the topmost object; it will propagate downwards.


Panda3D Manual: DirectButton

DirectButton is a DirectGui object that will respond to the mouse and can execute an arbitrary function when the user clicks on the object. This is actually implemented by taking advantage of the "state" system supported by every DirectGui object.

Each DirectGui object has a predefined number of available "states", and a current state. This concept of "state" is completely unrelated to Panda's FSM object. For a DirectGui object, the current state is simply as an integer number, which is used to select one of a list of different NodePaths that represent the way the DirectGui object appears in each state. Each DirectGui object can therefore have a completely different appearance in each of its states.

Most types of DirectGui objects do not use this state system, and only have one state, which is state 0. The DirectButton is presently the only predefined object that has more than one state defined by default. In fact, DirectButton defines four states, numbered 0 through 3, which are called ready, press, rollover, and disabled, in that order. Furthermore, the DirectButton automatically manages its current state into one of these states, according to the user's interaction with the mouse.

With a DirectButton, then, you have the flexibility to define four completely different NodePaths, each of which represents the way the button appears in a different state. Usually, you want to define these such that the ready state is the way the button looks most of the time, the press state looks like the button has been depressed, the rollover state is lit up, and the disabled state is grayed out. In fact, the DirectButton interfaces will set these NodePaths up for you, if you use the simple forms of the constructor (for instance, if you specify just a single text string to the text parameter).

Sometimes you want to have explicit control over the various states, for instance to display a different text string in each state. To do this, you can pass a 4-tuple to the text parameter (or to many of the other parameters, such as relief or geom), where each element of the tuple is the parameter value for the corresponding state, like this:

b = DirectButton(text = ("OK", "click!", "rolling over", "disabled"))

The above example would create a DirectButton whose label reads "OK" when it is not being touched, but it will change to a completely different label as the mouse rolls over it and clicks it.

Another common example is a button you have completely customized by painting four different texture maps to represent the button in each state. Normally, you would convert these texture maps into an egg file using egg-texture-cards like this:

egg-texture-cards -o button_maps.egg -p 240,240 button_ready.png button_click.png button_rollover.png button_disabled.png

And then you would load up the that egg file in Panda and apply it to the four different states like this:

maps = loader.loadModel('button_maps.egg')
b = DirectButton(geom = (maps.find('**/button_ready'),
                         maps.find('**/button_click'),
                         maps.find('**/button_rollover'),
                         maps.find('**/button_disabled')))

You can also access one of the state-specific NodePaths after the button has been created with the interface myButton.stateNodePath[stateNumber]. Normally, however, you should not need to access these NodePaths directly.

The following are the DirectGui keywords that are specific to a DirectButton. (These are in addition to the generic DirectGui keywords described on the previous page.)

Keyword | Definition | Value
command | Command the button performs when clicked | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
commandButtons | Which mouse button must be clicked to do the command | LMB, MMB, or RMB
rolloverSound | The sound made when the cursor rolls over the button | AudioSound instance
clickSound | The sound made when the cursor clicks on the button | AudioSound instance
pressEffect | Whether or not the button sinks in when clicked | <0 or 1>
state | Whether or not the button is disabled | DGG.ENABLED or DGG.DISABLED

Like any other DirectGui widget, you can change any of the properties by treating the element as a dictionary:

button["state"] = DGG.DISABLED

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *

from pandac.PandaModules import TextNode

#add some text
bk_text = "This is my Demo"
textObject = OnscreenText(text = bk_text, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def setText():
        bk_text = "Button Clicked"
        textObject.setText(bk_text)

#add button
b = DirectButton(text = ("OK", "click!", "rolling over", "disabled"),scale=.05,command=setText)

#run the tutorial
run()

Note that you will not be able to set the text unless the mayChange flag is 1. This is an optimisation, which newbies might miss.


Panda3D Manual: DirectCheckButton

DirectCheckButtons are similar to buttons, except they represent a binary state that is toggled when it is clicked. Their usage is almost identical to regular buttons, except that the text area and box area can be modified separately.

Keyword | Definition | Value
text_scale | Scale of the displayed text | (sx,sz)
indicatorValue | The initial boolean state of the checkbox | 0 or 1
boxImage | Image on the checkbox | Image Path
boxImageColor | Color of the image on the box | (R,G,B,A)
boxImageScale | Scale of the displayed image | Number
boxPlacement | Position of the box relative to the text area | 'left','right'
boxRelief | Relief appearance of the checkbox | SUNKEN or RAISED
boxBorder | Size of the border around the box | Number
command | Command the button performs when clicked(0 or 1 is passed, depending on the state) | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
commandButtons | Which mouse button must be clicked to do the command | LMB, MMB, or RMB
rolloverSound | The sound made when the cursor rolls over the button | Sound File Path
clickSound | The sound made when the cursor clicks on the button | Sound File Path
pressEffect | Whether or not the button sinks in when clicked | <0 or 1>

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

#add some text
bk_text = "This is my Demo"
textObject = OnscreenText(text = bk_text, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def setText(status):
    if(status):
        bk_text = "Checkbox Selected"
    else:
        bk_text = "Checkbox Not Selected"
    textObject.setText(bk_text)

#add button
b = DirectCheckButton(text = "CheckButton" ,scale=.05,command=setText)

#run the tutorial
run()

A note on programatically changing the indicatorValue

If you programatically want to change the checkbutton's indicatorValue, you need to call setIndicatorValue afterwards to update the checkbutton, like:

b["indicatorValue"] = True
b.setIndicatorValue()

A note on boxImage and other box* keywords

Just as DirectButton may be passed a 4-tuple of values to be used in the four button states, the box* keyword arguments may be supplied with multiple entries to denote the unchecked and checked state. To supply arguments to be used in the two states of the checkbox, construct a 3-tuple of values with a 'None' in the final entry, i.e. (unchecked, checked, None). For example, to set two different images for the unchecked and checked states:

boxImage = ("pathToDisabledImage.jpg","pathToEnabled.jpg",None)


Panda3D Manual: DirectRadioButton

This feature is only available in Panda3D versions 1.5.3 and higher

DirectRadioButtons are similar to check buttons, except only one button is selected when it is clicked. Their usage is almost identical to regular buttons, except that the text area and box area can be modified separately.

Keyword | Definition | Value
text_scale | Scale of the displayed text | (sx,sz)
indicatorValue | The initial boolean state of the radiobutton | 0 or 1
variable | The variable whose value will be set by radiobutton. Since we can not use call by reference for int or string variables in python, I used a list insteads. | define a list and pass it
value | The value to be set to the variable. Since we are using a list, we can define multiple values. But the length of value list must be same as length of variable list. | a list of values
others | The list of radio button instances sharing same variable. This must be set by using setOthers() after all radiobutton is created. | a list of radioButton instances
boxImage | BG Image of the radio button | Image Path
boxImageColor | Color of the BG image | (R,G,B,A)
boxImageScale | Scale of the BG image | Number
boxGeom | FG Image on the radio button | Image Path
boxGeomColor | Color of the FG image | (R,G,B,A)
boxGeomScale | Scale of the FG image | Number
boxPlacement | Position of the box relative to the text area | 'left','right', 'above', 'below'
boxBorder | Size of the border around the box | Number
command | Command the button performs when clicked (In version 1.6 and higher, no parameter is passed unless extraArgs are defined. Before, a single argument was passed by default.) | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
commandButtons | Which mouse button must be clicked to do the command | LMB, MMB, or RMB
rolloverSound | The sound made when the cursor rolls over the button | Sound File Path
clickSound | The sound made when the cursor clicks on the button | Sound File Path
pressEffect | Whether or not the button sinks in when clicked | <0 or 1>

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

v = [0]
#add some text
bk_text = "This is my Demo"
textObject = OnscreenText(text = bk_text, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def setText(status=None):
    bk_text = "CurrentValue : %s"%v
    textObject.setText(bk_text)

#add button
buttons = [
    DirectRadioButton(text = 'RadioButton0', variable=v, value=[0], scale=0.05, pos=(-0.4,0,0), command=setText),
    DirectRadioButton(text = 'RadioButton1', variable=v, value=[1], scale=0.05, pos=(0,0,0), command=setText),
    DirectRadioButton(text = 'RadioButton2', variable=v, value=[2], scale=0.05, pos=(0.4,0,0), command=setText)
]

for button in buttons:
    button.setOthers(buttons)

#run the tutorial
run()


Panda3D Manual: DirectDialog

DirectDialog objects are popup windows to alert or interact with the user. It is invoked just like the other DirectGUI objects, but it also has some unique keywords. Integral to DirectDialog are dialogName, buttonTextList, buttonImageList, and buttonValueList. The dialogName should ideally be the name of the NodePath created to hold the object. The button lists contain the various properties of the buttons within the dialog box. No maximum number of buttons needs to be declared.

Panda3D contains a number of shortcuts for common dialog options. For example, rather than specifying the rather common text list ("Yes","No"), there is a YesNoDialog that functions exactly like a normal dialog but has buttonTextList already defined. The other similar dialogs are OkCancelDialog, OkDialog, RetryCancelDialog, and YesNoCancelDialog.

Keyword | Definition | Value
dialogName | Name of the dialog | String
buttonTextList | List of text to show on each button | [Strings]
buttonGeomList | List of geometry to show on each button | [NodePaths]
buttonImageList | List of images to show on each button | [Image Paths]
buttonValueList | List of values sent to dialog command for each button. If value is [] then the ordinal rank of the button is used as its value | [Numbers]
buttonHotKeyList | Shortcut key for each button (the button must have focus) | [Characters]
buttonSize | 4-tuple used to specify custom size for each button (to make bigger then geom/text for example) | (Left,Right,Bottom,Top)
topPad | Extra space added above text/geom/image | Number
midPad | Extra space added between text/buttons | Number
sidePad | Extra space added to either side of text/buttons | Number
buttonPadSF | Scale factor used to expand/contract button horizontal spacing | Number
command | Callback command used when a button is pressed. Value supplied to command depends on values in buttonValueList | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
fadeScreen | If 1, fades screen to black when the dialog appears | 0 or 1

YesNo Dialog Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from direct.task import Task
from direct.actor import Actor
from direct.interval.IntervalGlobal import *
from pandac.PandaModules import *

#add some text
bk_text = "DirectDialog- YesNoDialog Demo"
textObject = OnscreenText(text = bk_text, pos = (0.85,0.85),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#add some text
output = ""
textObject = OnscreenText(text = output, pos = (0.95,-0.95),
 scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def itemSel(arg):
    if(arg):
        output = "Button Selected is: Yes"
    else:
        output = "Button Selected is: No"
    textObject.setText(output)

#create a frame
dialog = YesNoDialog(dialogName="YesNoCancelDialog", text="Please choose:", command=itemSel)

base.camera.setPos(0,-20,0)
#run the tutorial
run()

Note: the OkDialog causes an error if beeing created a scond time after destroying it with myOkDialog.destroy() . To solve this you can use:

myOkDialog.cleanup()


Panda3D Manual: DirectEntry

The DirectEntry creates a field that accepts text entered by the user. It provides a blinking cursor and support for backspace and the arrow keys. It can accept either a single line of text, with a fixed width limit (it doesn't scroll), or it can accept multiple word-wrapped lines.

Keyword | Definition | Value
initialText | Initial text to load in the field | String
entryFont | Font to use for text entry | Font object
width | Width of field in screen units | Number
numLines | Number of lines in the field | Integer
cursorKeys | True to enable the use of cursor keys (arrow keys) | 0 or 1
obscured | True to hide passwords, etc. | 0 or 1
command | Function to call when enter is pressed(the text in the field is passed to the function) | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
rolloverSound | The sound made when the cursor rolls over the field | Sound File Path
clickSound | The sound made when the cursor inside the field | Sound File Path
focus | Whether or not the field begins with focus (focusInCommand is called if true) | 0 or 1
backgroundFocus | If true, field begins with focus but with hidden cursor, and focusInCommand is not called | 0 or 1
focusInCommand | Function called when the field gains focus | Function
focusInExtraArgs | Extra arguments to the function specified in focusInCommand | [Extra Arguments]
focusOutCommand | Function called when the field loses focus | Function
focusOutExtraArgs | Extra arguments to the function specified in focusOutCommand | [Extra Arguments]

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

#add some text
bk_text = "This is my Demo"
textObject = OnscreenText(text = bk_text, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def setText(textEntered):
    textObject.setText(textEntered)

#clear the text
def clearText():
    b.enterText('')

#add button
b = DirectEntry(text = "" ,scale=.05,command=setText,
initialText="Type Something", numLines = 2,focus=1,focusInCommand=clearText)

#run the tutorial
run()

This example implements a text entry widget typically seen in web pages.


Panda3D Manual: DirectFrame

A frame is a container object for multiple DirectGUI objects. This allows for the control over several objects that are reparented to the same frame. When DirectGUI objects are parented to a frame, they will be positioned relative to the frame.

DirectFrame has no unique keywords, since it is simply used to arrange other objects.


Panda3D Manual: DirectLabel

Labels are like buttons, but they do not respond to mouse-clicks. This means a DirectLabel is basically just a text string, and in that respect is similar to OnscreenText, except that the DirectLabel integrates better with the rest of the DirectGUI system (and the constructor accepts more DirectGUI-like options).

If you are making a text label to appear on a DirectFrame or in conjunction with DirectGUI somehow, you should probably use a DirectLabel. For all other uses of text, you would probably be better off using OnscreenText or a making a plain Text Node instead.

DirectLabel's only unique keyword can be used if you want to create a label with multiple states. If you set the value of activeState to a nonexistent state, the label will disappear, since the default state is undefined.

Keyword | Definition | Value
activeState | The "active" or normal state of the label | Number


Panda3D Manual: DirectOptionMenu

The DirectOptionMenu class models a popup menu with an arbitrary number of items. It is composed of the menu bar, the popup marker, and the popup menu itself. The popup menu appears when the menu is clicked on and disappears when the user clicks again; if the click was inside the popup, the selection changes. By default, the text on the menu changes to whatever item is currently selected. The attributes that affect the appearance of the menu bar don't apply to the popup. Make sure to specify the items option or it may crash.

Keyword | Definition | Value
textMayChange | Whether the text on the menu changes with the selection | 0 or 1
initialitem | The index of the item that appears next to the cursor when the popup appears | Number
items | List of items in the popup menu | [Strings]
command | Function called when an item is selected (the item is passed in as a parameter) | Function
commandButtons | Which mouse button must be clicked to open the popup | LMB, MMB, or RMB
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
highlightColor | Color of highlighted text | (R,G,B,A)
highlightScale | Scale of highlighted text | (Width,Height)
rolloverSound | The sound made when the cursor rolls over the button | Sound File Path
clickSound | The sound made when the cursor clicks on the button | Sound File Path
popupMarkerBorder | Use width to change the size of the border around the popup marker | (Width,Height)
popupMarker_image | Set the state images of the popupMarker | (see directButton: image)
popupMarker_scale | Set the scale of the popupMarker | scale=x or scale=(x,y,z)
popupMarker_pos | Set the poition of the popupMarker relative to the parent optionMenu | pos=(x,y,z)
popupMarker_relief | Set the relief value of the popupMarker, the depth of the beveled edge | relief=x (for int value) or relief=None (for no relief)

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

#add some text
bk_text = "DirectOptionMenu Demo"
textObject = OnscreenText(text = bk_text, pos = (0.85,0.85),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#add some text
output = ""
textObject = OnscreenText(text = output, pos = (0.95,-0.95),
 scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def itemSel(arg):
    output = "Item Selected is: "+arg
    textObject.setText(output)

#create a frame
menu = DirectOptionMenu(text="options", scale=0.1,items=["item1","item2","item3"],initialitem=2,
highlightColor=(0.65,0.65,0.65,1),command=itemSel)

#run the tutorial
run()

This is a simple demonstration of the DirectOptionMenu.

Dynamic Updating of a Menu

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

#add some text
bk_text = "DirectOptionMenu Demo"
textObject = OnscreenText(text = bk_text, pos = (0.85,0.85),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#add some text
output = ""
textObject = OnscreenText(text = output, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def itemSel(arg):
    if(arg != "Add"): #no need to add an element
        output = "Item Selected is: "+arg
        textObject.setText(output)
    else: #add an element
        tmp_menu = menu['items']
        new_item = "item"+str(len(tmp_menu))
        tmp_menu.insert(-1,new_item) #add the element before add
        menu['items'] = tmp_menu
        #set the status message
        output = "Item Added is: "+new_item
        textObject.setText(output)

#create a frame
menu = DirectOptionMenu(text="options", scale=0.1,items=["item1","item2","item3","Add"],
initialitem=2,highlightColor=(0.65,0.65,0.65,1),command=itemSel,textMayChange=1)

#procedurally select a item
menu.set(0)

#run the tutorial
run()

In this example we add an item to the menu whenever the Add item is selected.


Panda3D Manual: DirectScrolledList

DirectScrolledLists create a list of DirectGuiWidgets. Each object is created individually and can then be added to the list. Some useful methods are:

addItem(item, refresh)
getItemIndexForItemID(self, itemID)
getSelectedIndex(self)
getSelectedText(self)
removeItem(self, item, refresh)
scrollBy(self, delta)
scrollTo(self, index, centered)
scrollToItemID(self, itemID, centered)
selectListItem(self, item)

In the above methods, item is a new item, either a string or a DirectGUI element, and itemID is an arbitrary identification number for each item (but not necessarily a zero-based index number). The itemID for a new item is the return value of addItem(). The centered parameter is a boolean; if true, the list scrolls so that the given index is centered, otherwise it scrolls so that the index is on top of the list.

The items option should either be a list of DirectGUI items or of strings. If strings are used, the itemMakeFunction (and possibly itemMakeExtraArgs) option should be defined to point to a function that will take the supplied string, the index, and the extra args as parameters and return a DirectGUI object to insert into the list. If items is a list of strings and itemMakeFunction is not specified, it will create a list of DirectLabels. itemMakeFunction is redundant if a list of DirectGUI objects is passed into items to begin with.

DirectScrolledLists come with two scroll buttons for navigating through the list. By default, they both start at (0,0,0) relative to the list with size 0, and their positions and size need to be set explicitly. You can set any of the values except relief appearance as you initialize the list:

myScrolledList = DirectScrolledList(incButton_propertyName = value,
decButton_propertyName = value)

incButton scrolls forward through the list; decButton backward. Note that this only works for initialization. To change a property of the scroll buttons later in the program, you must use:

myScrolledList.incButton['propertyName'] = value
myScrolledList.decButton['propertyName'] = value

Unlike the first method, this does not work with NodePath options like position; use setPos(...) for that.

For example, the following creates a scrolled list and resizes and moves the buttons appropriately.

myScrolledList = DirectScrolledList(
   incButton_pos= (.5,0,0), incButton_text = "Inc",
   decButton_pos= (-.5,0,0), decButton_text = "Dec")
myScrolledList.incButton['frameSize'] = (0, 0.2, 0, 0.2)
myScrolledList.decButton['frameSize'] = (0, 0.2, 0, 0.2)
myScrolledList.incButton['text_scale'] = .2
myScrolledList.decButton['text_scale'] = .2

Keyword | Definition | Value
command | Function called when the list is scrolled | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
text_scale | Scale of the displayed text | (sx,sz)
items | List of the objects to appear in the ScrolledList | [DirectGUI items] or [Strings]
numItemsVisible | Number of items visible at a time | Number
forceHeight | Forces the height of the list to be a given number | Number
itemMakeFunction | Function that makes DirectGUI items out of strings | Function
itemMakeExtraArgs | Extra arguments to the function in itemMakeFunction | [Extra Arguments]

A small example on how to use it:

from direct.directbase import DirectStart
from direct.gui.DirectGui import *
from pandac.PandaModules import *

b1 = DirectButton(text = ("Button1", "click!", "roll", "disabled"),
                  text_scale=0.1, borderWidth = (0.01, 0.01),
                  relief=2)

b2 = DirectButton(text = ("Button2", "click!", "roll", "disabled"),
                  text_scale=0.1, borderWidth = (0.01, 0.01),
                  relief=2)

l1 = DirectLabel(text = "Test1", text_scale=0.1)
l2 = DirectLabel(text = "Test2", text_scale=0.1)
l3 = DirectLabel(text = "Test3", text_scale=0.1)

numItemsVisible = 4
itemHeight = 0.11

myScrolledList = DirectScrolledList(
    decButton_pos= (0.35, 0, 0.53),
    decButton_text = "Dec",
    decButton_text_scale = 0.04,
    decButton_borderWidth = (0.005, 0.005),

    incButton_pos= (0.35, 0, -0.02),
    incButton_text = "Inc",
    incButton_text_scale = 0.04,
    incButton_borderWidth = (0.005, 0.005),

    frameSize = (0.0, 0.7, -0.05, 0.59),
    frameColor = (1,0,0,0.5),
    pos = (-1, 0, 0),
    items = [b1, b2],
    numItemsVisible = numItemsVisible,
    forceHeight = itemHeight,
    itemFrame_frameSize = (-0.2, 0.2, -0.37, 0.11),
    itemFrame_pos = (0.35, 0, 0.4),
    )

myScrolledList.addItem(l1)
myScrolledList.addItem(l2)
myScrolledList.addItem(l3)

for fruit in ['apple', 'pear', 'banana', 'orange']:
    l = DirectLabel(text = fruit, text_scale=0.1)
    myScrolledList.addItem(l)

run()


Panda3D Manual: DirectWaitBar

DirectWaitBars are similar to status bars; use them to indicate a slow process gradually completing (e.g. a loading screen). It has various options for both the background bar and the loading bar that fills up as the process progresses. You can call finish() to automatically fill up the bar, or use:

myWaitBar['value'] = number

to set the value (it ranges from 0 to 100 by default).

Keyword | Definition | Value
value | Initial value of the loading bar (from 0 to 100) | Number
range | The maximum value of the loading bar | Number
barColor | The color of the loading bar | (R,G,B,A)
barRelief | The relief appearance of the loading bar | SUNKEN or RAISED
barBorderWidth | If barRelief is SUNKEN, RAISED, GROOVE, or RIDGE, changes the size of the loading bar's bevel | (Width,Height)
relief | The relief appearance of the background bar | SUNKEN or RAISED

Example

import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import *

#add some text
bk_text = "This is my Demo"
textObject = OnscreenText(text = bk_text, pos = (0.95,-0.95),
scale = 0.07,fg=(1,0.5,0.5,1),align=TextNode.ACenter,mayChange=1)

#callback function to set  text
def incBar(arg):
    bar['value'] +=	arg
    text = "Progress is:"+str(bar['value'])+'%'
    textObject.setText(text)

#create a frame
frame = DirectFrame(text="main",scale=0.001)
#add button
bar = DirectWaitBar(text = "" , value=50,pos=(0,.4,.4))

#create 4 buttons
button_1 = DirectButton(text="+1",scale=0.05,pos=(-.3,.6,0), command=incBar,extraArgs = [1])
button_10 = DirectButton(text="+10",scale=0.05,pos=(0,.6,0), command=incBar,extraArgs = [10])
button_m1 = DirectButton(text="-1",scale=0.05,pos=(0.3,.6,0), command=incBar,extraArgs = [-1])
button_m10 = DirectButton(text="-10",scale=0.05,pos=(0.6,.6,0), command=incBar,extraArgs = [-10])

#run the tutorial
run()


Panda3D Manual: DirectSlider

Use a DirectSlider to make a slider, a widget that allows the user to select a value between a bounded interval. DirectSlider is available beginning in Panda3D 1.1.

A DirectSlider consists of a long bar, by default horizontal, along with a "thumb", which is a special button that the user may move left or right along the bar. The normal DirectGui parameters such as frameSize, geom, and relief control the look of the bar; to control the look of the thumb, prefix each of these parameters with the prefix "thumb_", e.g. thumb_frameSize.

If you want to get (or modify) the current value of the slider (by default, the range is between 0 and 1), use mySlider['value'].

Keyword | Definition | Value
value | Initial value of the slider | Default is 0
range | The (min, max) range of the slider | Default is (0, 1)
pageSize | The amount to jump the slider when the user clicks left or right of the thumb | Default is 0.1
orientation | The orientation of the slider | HORIZONTAL or VERTICAL
command | Function called when the value of the slider changes (takes no arguments) | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
thumb_geom, thumb_relief, thumb_text, thumb_frameSize, etc. | Parameters to control the look of the thumb | Any parameters appropriate to DirectButton

Example

import direct.directbase.DirectStart
from direct.gui.DirectGui import *

def showValue():
        print slider['value']

slider=DirectSlider(range=(0,100), value=50, pageSize=3, command=showValue)

run()

"range" sets values between 0 and 100 "value" sets initial value to 50 "pageSize" sets the step between mouseclicks to 3 (approximately) "command" calls the showValue-function implemented above


Panda3D Manual: DirectScrollBar

A DirectScrollBar is similar to the "scroll bar" widget commonly used by the user to page through a large document. DirectScrolledBar is available beginning in Panda3D 1.1. It consists of a long trough, a thumb that slides along the trough, and a pair of buttons on either side of the trough to scroll one line at a time. A DirectScrollBar can be oriented either vertically or horizontally.

The DirectScrollBar is similar in function to DirectSlider, but it is specifically designed for scrolling through a large window. In fact, a pair of DirectScrollBars is used to implement the DirectScrolledFrame, which manages this scrolling functionality automatically. (Because DirectScrolledFrame exists, you will probably not need to create a DirectScrollBar directly, unless you have some custom purpose that requires a scroll bar.)

DirectScrollBar has many things in common with DirectSlider. Like DirectSlider, the normal DirectGui parameters such as frameSize, geom, and relief control the look of the trough. You can control the look of the thumb by prefixing each of these parameters with the prefix "thumb_", e.g. thumb_frameSize; similarly, you can control the look of the two scroll buttons by prefixing these with "incButton_" and "decButton_". You can retrieve or set the current position of the thumb with myScrollBar['value'].

Keyword | Definition | Value
value | Initial position of the thumb | Default is 0
range | The (min, max) range of the thumb | Default is (0, 1)
pageSize | The amount to jump the thumb when the user clicks left or right of the thumb; this also controls the width of the thumb when resizeThumb is True | Default is 0.1
scrollSize | The amount to move the thumb when the user clicks once on either scroll button | Default is 0.01
orientation | The orientation of the scroll bar | HORIZONTAL or VERTICAL
manageButtons | Whether to automatically adjust the buttons when the scroll bar's frame is changed | True or False
resizeThumb | Whether to adjust the width of the thumb to reflect the ratio of pageSize to the overall range; requires manageButtons to be True as well | True or False
command | Function called when the position of the thumb changes (takes no arguments) | Function
extraArgs | Extra arguments to the function specified in command | [Extra Arguments]
thumb_geom, thumb_relief, thumb_text, thumb_frameSize, etc. | Parameters to control the look of the thumb | Any parameters appropriate to DirectButton
incButton_geom, incButton_relief, incButton_text, incButton_frameSize, etc. | Parameters to control the look of the lower or right scroll button | Any parameters appropriate to DirectButton
decButton_geom, decButton_relief, decButton_text, decButton_frameSize, etc. | Parameters to control the look of the upper or left scroll button | Any parameters appropriate to DirectButton


Panda3D Manual: DirectScrolledFrame

The DirectScrolledFrame is a special variant of DirectFrame that allows the user to page through a larger frame than would otherwise fit onscreen. The DirectScrolledFrame consists of a small onscreen frame which is actually a window onto a potentially much larger virtual canvas; the user can scroll through this canvas through the use of one or two DirectScrollBars on the right and bottom of the frame. DirectScrolledFrame is available beginning with Panda3D version 1.1.

The frameSize parameter controls the size and placement of the visible, onscreen frame; use the canvasSize parameter to control the size of the larger virtual canvas.

You can then parent any widgets you like to the NodePath returned by myFrame.getCanvas(). The DirectGui items you attach to this canvas NodePath will be visible through the small window; you should position them within the virtual canvas using values within the coordinate range you established via the canvasSize parameter.

By default, the scroll bars are automatically created with the DirectScrolledFrame and will be hidden automatically when they are not needed (that is, if the virtual frame size is equal to or smaller than the onscreen frame size). You can adjust either frame size at runtime and the scroll bars will automatically adjust as needed. If you would prefer to manage the scroll bars yourself, you can set one or both of manageScrollBars and autoHideScrollBars to False.

Keyword | Definition | Value
canvasSize | Extents of the virtual canvas | (Left, right, bottom, top)
manageScrollBars | Whether to automatically position and scale the scroll bars to fit along the right and bottom of the frame | True or False
autoHideScrollBars | Whether to automatically hide one or both scroll bars when not needed | True or False
scrollBarWidth | Specifies the width of both scroll bars at construction time | Default is 0.08
verticalScroll_relief, verticalScroll_frameSize, etc. | Parameters to control the look of the vertical scroll bar | Any parameters appropriate to DirectScrollBar
horizontalScroll_relief, horizontalScroll_frameSize, etc. | Parameters to control the look of the horizontal scroll bar | Any parameters appropriate to DirectScrollBar


Panda3D Manual: Render Effects

There are a number of special render effects that may be set on scene graph nodes to change the way they render. This includes BillboardEffect, Compass Effect, DecalEffect, PolylightEffect, and ShowBoundsEffect.

List of All Render Effects:

  • BillboardEffect: Dicates that geometry at this node should automatically rotate to face the camera, or any other arbitrary node.

  • CharacterJointEffect: The effect binds the node back to the character, so that querying the relative transform of the affected node will automatically force the indicated character to be updated first.

  • PolylightEffect: A PolylightEffect can be used on a node to define a LightGroup for that node. A LightGroup contains PolylightNodes which are essentially nodes that add color to the polygons of a model based on distance. PolylightNode is a cheap way to get lighting effects specially for night scenes

  • CompassEffect: In its purest form, a CompassEffect is used to keep the node's rotation fixed relative to the top of the scene graph, despite other transforms that may exist above the node. Hence the name: the node behaves like a magnetic compass, always pointing in the same direction.

  • ShowBoundsEffect: Applied to a GeomNode to cause a visible bounding volume to be drawn for this node. This is generally used only during development to help identify bounding volume issues.

  • DecalEffect: Applied to a GeomNode to indicate that the children of this GeomNode are coplanar and should be drawn as decals (eliminating Z-fighting).

  • TexProjectorEffect : This effect automatically applies a computed texture matrix to the specified texture stage, according to the relative position of two specified nodes.

    RenderEffect represents render properties that must be applied as soon as they are encountered in the scene graph, rather than propagating down to the leaves. This is different from RenderAttrib, which represents properties like color and texture that don't do anything until they propagate down to a GeomNode.

    You should not attempt to create or modify a RenderEffect directly; instead, use the make() method of the appropriate kind of effect you want. This will allocate and return a new RenderEffect of the appropriate type, and it may share pointers if possible. Do not modify the new RenderEffect if you wish to change its properties; instead, create a new one.

    Once you have created a render Effect, you need to decide what it should affect. If you have an effect that should affect evereything in the scene the nodepath in the next line of code is "render". If you only want it to affect specific objects, choose the appropriate place in the scene graph.

    nodePath.node().setEffect(<Render Effect>)


Panda3D Manual: Compass Effects

A CompassEffect causes a node to inherit its rotation (or pos or scale, if specified) from some other reference node in the graph, or more often from the root.

In its purest form, a CompassEffect is used to keep the node's rotation fixed relative to the top of the scene graph, despite other transforms that may exist above the node. Hence the name: the node behaves like a magnetic compass, always pointing in the same direction.

As an couple of generalizing extensions, the CompassEffect may also be set up to always orient its node according to some other reference node than the root of the scene graph. Furthermore, it may optionally adjust any of pos, rotation, or scale, instead of necessarily rotation; and it may adjust individual pos and scale components. (Rotation may not be adjusted on an individual component basis, that's just asking for trouble.)

Be careful when using the pos and scale modes. In these modes, it's possible for the CompassEffect to move its node far from its normal bounding volume, causing culling to fail. If this is an issue, you may need to explicitly set a large (or infinite) bounding volume on the effect node.

effect=CompassEffect.make(nodePath)


Panda3D Manual: Billboard Effects

A billboard is a special effect that causes a node to rotate automatically to face the camera, regardless of the direction from which the camera is looking. It is usually applied to a single textured polygon representing a complex object such as a tree. Judicious use of billboards can be an effective way to create a rich background environment using very few polygons.

Panda indicates that a node should be billboarded to the camera by storing a BillboardEffect on that node. Normally, you do not need to create a BillboardEffect explicitly, since there are a handful of high-level methods on NodePath that will create one for you:

myNodePath.setBillboardAxis()
myNodePath.setBillboardPointWorld()
myNodePath.setBillboardPointEye()

Each of the above calls is mutually exclusive; there can be only one kind of billboard effect on a node at any given time. To undo a billboard effect, use:

myNodePath.clearBillboard()

The most common billboard type is an axial billboard, created by the setBillboardAxis() method. This kind of billboard is constrained to rotate around its vertical axis, so is usually used to represent objects that are radially symmetric about the vertical axis (like trees).

Less often, you may need to use a point billboard, which is free to rotate about any axis. There are two varieties of point billboard. The world-relative point billboard always keeps its up vector facing up, i.e. along the Z axis, and is appropriate for objects that are generally spherical and have no particular axis of symmetry, like clouds. The eye-relative point billboard, on the other hand, always keeps its up vector towards the top of the screen, no matter which way the camera tilts, and is usually used for text labels that float over objects in the world.

There are several more options available on a BillboardEffect, but these are rarely used. If you need to take advantage of any of these more esoteric options, you must create a BillboardEffect and apply it to the node yourself:

myEffect=BillboardEffect.make(
  upVector= vec3,
  eyeRelative= bool,
  axialRotate= bool,
  offset= float,
  lookAt= nodepath,
  lookAtPoint= point3
)
myNodePath.node().setEffect(myEffect)


Panda3D Manual: Finite State Machines

A "Finite State Machine" is a concept from computer science. Strictly speaking, it means any system that involves a finite number of different states, and a mechanism to transition from one state to another.

In Panda3D, a Finite State Machine, or FSM, is implemented as a Python class. To define a new FSM, you should define a Python class that inherits from the FSM class. You define the available states by writing appropriate method names within the class, which define the actions the FSM takes when it enters or leaves certain states. Then you can request your FSM to transition from state to state as you need it to.

You may come across some early Panda3D code that creates an instance of the ClassicFSM class. ClassicFSM is an earlier implementation of the FSM class, and is now considered deprecated. It is no longer documented here. We recommend that new code use the FSM class instead, which is documented on the following pages.


Panda3D Manual: FSM Introduction

In Panda3D, FSM's are frequently used in game code to automatically handle the cleanup logic in game state changes. For instance, suppose you are writing a game in which the avatar spends most of his time walking around, but should go into swim mode when he enters the water. While he is walking around, you want certain animations and sound effects to be playing, and certain game features to be active; but while he is swimming, there should be a different set of animations, sound effects, and game features (this is just an example, of course):

Walk state

  • Should be playing "walk" animation
  • Should hear footsteps sound effect
  • Collision detection with doors should be active

Swim state

  • Should be playing "swim" animation
  • Should hear underwater sound effect
  • Should have fog on camera
  • Should have an air timer running

So, when your avatar switches from walking to swimming, you would need to stop the footsteps sound effect, disable the door collisions, start playing the "swim" animation, start the underwater sound effect, enable the fog on the camera, and start the air timer.

You could do all this by hand, of course. But using an FSM can make it easier. In this simple model, you could define an FSM with two states, "Walk" and "Swim". This might be represented graphically like this:

Walk

←→

Swim

To implement this as a Panda3D FSM, you would declare an new class that inherits from FSM.FSM, and within this class you would define four methods: enterWalk(), exitWalk(), enterSwim(), and exitSwim(). This might look something like this:

from direct.fsm import FSM

class AvatarFSM(FSM.FSM):
    def __init__(self):#optional because FSM already defines __init__
        #if you do write your own, you *must* call the base __init__ :
        FSM.FSM.__init__(self,'avatarFSM')
        ##do your init code here

    def enterWalk(self):
        avatar.loop('walk')
        footstepsSound.play()
        enableDoorCollisions()

    def exitWalk(self):
        avatar.stop()
        footstepsSound.stop()
        disableDoorCollisions()

    def enterSwim(self):
        avatar.loop('swim')
        underwaterSound.play()
        render.setFog(underwaterFog)
        startAirTimer()

    def exitSwim(self):
        avatar.stop()
        underwaterSound.stop()
        render.clearFog()
        stopAirTimer()

myfsm = AvatarFSM()

Keep in mind this is just an imaginary example, of course; but it should give you an idea of what an FSM class looks like.

Note that each enter method activates everything that is important for its particular state, and--this is the important part--the corresponding exit method turns off or undoes everything that was turned on by the enter method. This means that whenever the FSM leaves a particular state, you can be confident that it will completely disable anything it started when it entered that state.

Now to switch from Walk state to Swim state, you would just need to request a transition, like this:

myfsm.request('Swim')

This FSM is a very simple example. Soon you will find the need for more than two states. For instance, you might want to play a transition animation while the avatar is moving from Walk state to Swim state and back again, and these can be encoded as separate states. There might be a "drowning" animation if the avatar stays too long underwater, which again might be another state. Graphically, this now looks like this:

↗ | Walk2Swim | ↘ |
Walk |  | Swim | → | Drowning
↖ | Swim2Walk | ↙ |

In a real-world example, you might easily find you have a need for dozens of states. This is when using the FSM class to manage all of these transitions for you can really make things a lot simpler; if you had to keep all of that cleanup code in your head, it can very quickly get out of hand.


Panda3D Manual: Simple FSM Usage

A Panda3D FSM is implemented by defining a new Python class which inherits from the class direct.fsm.FSM.FSM (normally imported as FSM.FSM or simply FSM), and defining the appropriate enter and exit methods on the class.

FSM states are represented by name strings, which should not contain spaces or punctuation marks; by Panda3D convention, state names should begin with a capital letter. An FSM is always in exactly one state a time; the name of the current state in stored in fsm.state. When it transitions from one state to another, it first calls exitOldState(), and then it calls enterNewState(), where OldState is the name of the previous state, and NewState is the name of the state it is entering. While it is making this transition, the FSM is not technically in either state, and fsm.state will be None--but you can find both old and new state names in fsm.oldState and fsm.newState, respectively.

To define a possible state for an FSM, you only need to define an enterStateName() and/or exitStateName() method on your class, where StateName is the name of the state you would like to define. The enterStateName() method should perform all the necessary action for entering your new state, and the corresponding exitStateName() method should generally undo everything that was done in enterStateName(), so that the world is returned to a neutral state.

An FSM starts and finishes in the state named "Off". When the FSM is created, it is already in "Off"; and when you destroy it (by calling fsm.cleanup()), it automatically transitions back to "Off".

To request an FSM to transition explicitly to a new state, use the call fsm.request('StateName'), where StateName is the state you would like it to transition to.

Arguments to enterStateName methods

Normally, both enterStateName() and exitStateName() take no arguments (other than self). However, if your FSM requires some information before it can transition to a particular state, you can define any arguments you like to the enterStateName method for that state; these arguments should be passed in to the request() call, following the state name.

from direct.fsm import FSM

class AvatarFSM(FSM.FSM):
    def enterWalk(self, speed, doorMask):
        avatar.setPlayRate(speed, 'walk')
        avatar.loop('walk')
        footstepsSound.play()
        enableDoorCollisions(doorMask)

    def exitWalk(self):
        avatar.stop()
        footstepsSound.stop()
        disableDoorCollisions()

myfsm = AvatarFSM()
myfsm.request('Walk', 1.0, BitMask32.bit(2))

Note that the exitStateName method must always take no arguments.

Allowed and disallowed state transitions

By default, every state transition request is allowed: the call fsm.request('StateName') will always succeed, and the the FSM will be left in the new state. You may wish to make your FSM more robust by disallowing certain transitions that you don't want to happen.

For instance, consider the example FSM described previously, which had the following state diagram:

↗ | Walk2Swim | ↘ |
Walk |  | Swim | → | Drowning
↖ | Swim2Walk | ↙ |

In this diagram, the arrows represent legal transitions. It is legal to transition from 'Walk' to 'Walk2Swim', but not from 'Walk' to 'Swim2Walk'. If you were to request the FSM to enter state 'Swim2Walk' while it is currently in state 'Walk', that's a bug; you might prefer to have the FSM throw an exception, so you can find this bug.

To enforce this, you can store self.defaultTransitions in the FSM's __init__() method. This should be a map of allowed transitions from each state. That is, each key of the map is a state name; for that key, the value is a list of allowed transitions from the indicated state. Any transition not listed in defaultTransitions is considered invalid. For example:

class AvatarFSM(FSM.FSM):
    def __init__(self):
        FSM.FSM.__init__(self)
        self.defaultTransitions = {
            'Walk' : [ 'Walk2Swim' ],
            'Walk2Swim' : [ 'Swim' ],
            'Swim' : [ 'Swim2Walk', 'Drowning' ],
            'Swim2Walk' : [ 'Walk' ],
            'Drowning' : [ ],
            }

If you do not assign anything to self.defaultTransitions, then all transitions are legal. However, if you do assign a map like the above, then requesting a transition that is not listed in the map will raise the exception FSM.RequestDenied.


Panda3D Manual: FSM with input

Another common use for FSM's is to provide an abstraction for AI state. For this purpose, you would like to supply an "input" string to the FSM, and let the FSM decide which state it should transition to, rather than explicitly specifying the target state name.

Consider the following FSM state diagram:

↷ straight |  | ↶ straight
North | ← left | East
↓ left |  | ↑ left
West | → left | South
↺ straight |  | ↻ straight

Here the text next to an arrow represents the "input" string given to the FSM, and the direction of the arrow represents the state transition that should be made for that particular input string, from the indicated starting state.

In this example, we have encoded a simple FSM that determines which compass direction a character will be facing after either turning left or continuing straight. The input will be either "left" or "straight", and the result is a transition to a new state that represents the new compass direction, based on the previous compass direction. If we request "left" from state North, the FSM transitions to state West. On the other hand, if we request "left" from state South, the FSM transitions to state East. If we request "straight" from any state, the FSM should remain in its current state.

To implement this in Panda3D, we define a number of filter functions, one for each state. The purpose of this function is to decide what state to transition to next, if any, on receipt of a particular input.

A filter function is created by defining a python method named filterStateName(), where StateName is the name of the FSM state to which this filter function applies. The filterStateName method receives two parameters, a string and a tuple of arguments (the arguments contain the optional additional arguments that might have been passed to the fsm.request() call; it's usually an empty tuple). The filter function should return the name of the state to transition to. If the transition should be disallowed, the filter function can either return None to quietly ignore it, or it can raise an exception. For example:

class CompassDir(FSM.FSM):
    def filterNorth(self, request, args):
        if request == 'straight':
            return 'North'
        elif request == 'left':
            return 'West'
        else:
            return None

    def filterWest(self, request, args):
        if request == 'straight':
            return 'West'
        elif request == 'left':
            return 'South'
        else:
            return None

    def filterSouth(self, request, args):
        if request == 'straight':
            return 'South'
        elif request == 'left':
            return 'East'
        else:
            return None

    def filterEast(self, request, args):
        if request == 'straight':
            return 'East'
        elif request == 'left':
            return 'North'
        else:
            return None

Note that input strings, by convention, should begin with a lowercase letter, as opposed to state names, which should begin with an uppercase letter. This allows you to make the distinction between requesting a state directly, and feeding a particular input string to an FSM. To feed input to this FSM, you would use the request() call, just as before:

myfsm.request('left')
myfsm.request('left')
myfsm.request('straight')
myfsm.request('left')

If the FSM had been in state North originally, after the above sequence of operations it would now be in state East.

The defaultFilter method

Although defining a series of individual filter methods gives you the most flexibility, for many FSM's you may not need this much explicit control. For these cases, you can simply define a defaultFilter method that does everything you need. If a particular filterStateName() method does not exist, then the FSM will call the method named defaultFilter() instead; you can put any logic here that handles the general case.

For instance, we could have defined the above FSM using just the defaultFilter method, and a lookup table:

class CompassDir(FSM.FSM):
    nextState = {
        ('North', 'straight') : 'North',
        ('North', 'left') : 'West',
        ('West', 'straight') : 'West',
        ('West', 'left') : 'South',
        ('South', 'straight') : 'South',
        ('South', 'left') : 'East',
        ('East', 'straight') : 'East',
        ('East', 'left') : 'North',
        }

    def defaultFilter(self, request, args):
        key = (self.state, request)
        return self.nextState.get(key)

The base FSM class defines a defaultFilter() method that implements the default FSM transition rules (that is, allow all direct-to-state (uppercase) transition requests unless self.defaultTransitions is defined; in either case, quietly ignore input (lowercase) requests).

In practice, you can mix-and-match the use of the defaultFilter method and your own custom methods. The defaultFilter method will be called only if a particular state's custom filter method does not exist. If a particular state's filterStateName method is defined, that method will be called upon a new request; it can do any custom logic you require (and it can call up to the defaultFilter method if you like).


Panda3D Manual: Advanced FSM Tidbits

request vs. demand

As stated previously, you normally request an FSM to change its state by calling either fsm.request('NewState', arg1, arg2, ...), or fsm.request('inputString', arg1, arg2, ...), where arg1, arg2, ... represent optional arguments to the destination state's enter function (or to the filter function). The call to request() will either succeed or fail, according to what the filter function for the current state does. If it succeeds, it will return the tuple ('NewState', arg1, arg2), indicating the new state it has transitioned to. If it fails, it will simply return None (unless the filter function was written to throw an exception on failure).

If you request an FSM to make a transition, and the request fails, you might consider this an error condition, and you might prefer to have your code to stop right away rather than continuing. In this case, you should call fsm.demand() instead. The syntax is the same as that for request(), but instead of returning None on failure, it will always raise an exception if the state transition is denied. There is no return value from demand(); if it returns, the transition was accepted.

FSM.AlreadyInTransition

An FSM is always in exactly one state, except while it is in the process of transitioning between states (that is, while it is calling the exitStateName method for the previous state, followed by the enterStateName method for the new state). During this time, the FSM is not considered in either state, and if you query fsm.state it will contain None.

During this transition time, it is not legal to call fsm.request() to request a new state. If you try to do this, the FSM will raise the exception FSM.AlreadyInTransition. This is a particularly common error if some cleanup code that is called from the exitStateName method has a side-effect that triggers a transition to a new state.

However, there's a simple solution to this problem: call fsm.demand() instead. Unlike request(), demand() can be called while the FSM is currently in transition. When this happens, the FSM will queue up the demand, and will carry it out as soon as it has fully transitioned into its new state.

forceTransition()

There is also a method fsm.forceTransition(). This is similar to demand() in that it never fails and does not have a return value, but it's different in that it completely bypasses the filter function. You should therefore only pass an uppercase state name (along with any optional arguments) to forceTransition, never a lowercase input string. The FSM will always transition to the named state, even if it wouldn't otherwise be allowed. Thus, forceTransition() can be useful in special cases to skip to another state that's not necessarily connected to the current state (for instance, to handle emergency cleanup when an exception occurs). Be careful that you don't overuse forceTransition(), though; consider whether demand() would be a better choice. If you find yourself making lots of calls to forceTransition(), it may be that your filter functions (or your defaultTransitions) are poorly written and are disallowing what should be legitimate state transitions.

Filtering the optional arguments

The filterStateName method receives two parameters: the string request, and a tuple, which contains the additional arguments passed to the request (or demand) call. It then normally returns the state name the FSM should transition to, or it returns None to indicate the transition is denied.

However, the filter function can also return a tuple. If it returns a tuple, it should be of the form ('StateName', arg1, arg2, ...), where arg1, arg2, ... represent the optional arguments that should be passed to the enterStateName method. Usually, these are the same arguments that were passed to the filterStateName method (in this case, you can generate the return value tuple with the python syntax ('StateName',) + args).

The returned arguments are not necessarily the same as the ones passed in, however. The filter function is free to check, modify, or rearrange any of them; or it might even make up a completely new set of arguments. In this way, the filter function can filter not only the state transitions themselves, but also the set of data passed along with the request.


Panda3D Manual: Terrain

Panda3D also provides classes for terrain generation. Those classes take a height field image, and generate visible geometry from the provided height field.

If you work with very large terrains, the renderer may not be able to handle such a great amount of triangles. To solve that issue, Panda3D provides LOD (Level of Detail), this means that it will generate the terrain at high quality near the focal point (which is usually the camera), but will provide lower quality terrain - that is, less triangles - because those far away parts are not as much visible as the closer parts.

Panda3D provides two classes for terrain generation and LOD handling:

  • The HeightfieldTesselator. This class can take a grayscale height field image and generate a terrain. It uses a linear LOD system, and, of course, because when the camera moves away from the focal point, the terrain needs to be regenerated to 'focus' on the new focal point. On extremely large terrains, this might lead to complications, because regenerating the entire terrain takes time, and the player might experience some lag when it is regenerated. That is why this class is not very suitable for extremely large terrains.
  • The GeoMipTerrain, formerly known as PGMM. This is a community-contributed terrain system and has been included since Panda3D 1.5.1. This algorithm takes a height field and also converts it into geometry, but it splits the terrain up in smaller chunks, so when the focal point changes, not all chunks have to be regenerated. This is to prevent lagging when the focal point moves. For smaller terrains, however, you might not need such extensive terrain calculations, and use the HeightfieldTesselator instead.

The GeoMipTerrain also provides a way to generate terrain bruteforce, that means without LOD and at full quality.

Various other terrain generators have been contributed by the community:

  • SoarX
  • chombee's terrain renderer
  • snaptotheterrain


Panda3D Manual: The Heightfield Tesselator

The HeightfieldTesselator converts a height field in the form of a greyscale image into a scene consisting of a number of GeomNodes. The tesselation uses an LOD algorithm. You supply a "focal point" (X,Y) which tells the tesselator where the bulk of the detail should be concentrated. The intent is that as the player walks around the terrain, you should occasionally move the focal point to wherever the player is. You should not move the focal point every frame: tesselation is not that fast. Also, changing the focal point may cause popping, so it is best to minimize the number of changes. There are a number of parameters that you can use to control tesselation, such as a target polygon count, and a visibility radius.

The heightfield needs to be a multiple of 128 pixels in each dimension. It does not need to be square, and it does not need to be a power of two. For example, a 384 x 640 heightfield is fine. Be aware that tesselation time is proportional to heightfield area, so if you plan to use a size larger than about 512x512, it may be desirable to benchmark.

Altering parameters, such as the poly count, the view radius, or the focal point, does not alter any GeomNodes already generated. Parameter changes only affect subsequently-generated GeomNodes. It is possible to cache many different tesselations of the same terrain.

Note: Various other algorithms for terrain LOD have been contributed by users at the Forums.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Geometrical MipMapping

The GeoMipTerrain generates terrain geometry from a heightfield image, but it does more than any bruteforce terrain generator: the GeoMipTerrain divides the terrain into multiple chunks, where each of them can have a different level of detail. The advantage of this approach is that, when the focal point (the place where the terrain has the highest quality, probably the camera) moves, not the entire terrain has to be regenerated to match the correct detail level, like the HeightfieldTesselator, but only the chunks that have a different LOD can be regenerated. Also, it improves culling and collision detection.

Basic Usage

Using the GeoMipTerrain is quite easy, it does not require to write much complicated calculations:

from pandac.PandaModules import GeoMipTerrain

terrain = GeoMipTerrain("mySimpleTerrain")
terrain.setHeightfield(Filename("yourHeightField.png"))
#terrain.setBruteforce(True)
terrain.getRoot().reparentTo(render)
terrain.generate()

First, the code creates a GeoMipTerrain instance. The setHeightfield call loads in a heightfield (8-bits greyscale) image. Preferrably this is a size of a power of two plus one (like 129, 257, 513, 1025, etc.), but if it is not, the GeoMipTerrain will automatically scale it up to the nearest correct size (which is quite slow). setHeightfield can take a PNMImage, Texture or Filename instance. The setBruteforce(True) call sets the terrain to bruteforce rendering -- this means that the terrain is created at the highest quality (the lowest detail level), and LOD is not applied. In the next section we will explain how to set a LOD level and a Focal Point. The getRoot() call returns the NodePath of the terrain. It is then reparented to render to be a part of the scene graph. You can apply Common State Changes to this NodePath. Finally, the generate() call generates the terrain geometry. Note that if the terrain is still quite flat, you will have to scale the terrain NodePath in the Z direction, because by default, the Z positions are between 0 and 1. To fix this, scale the terrain up in the Z direction: (before generating it, otherwise it might require you to regenerate it.)

terrain.getRoot().setSz(100)

Dynamic Terrains

This code shows a dynamically updated terrain with LOD:

# Set up the GeoMipTerrain
terrain = GeoMipTerrain("myDynamicTerrain")
terrain.setHeightfield(Filename("yourHeightField.png"))

# Set terrain properties
terrain.setBlockSize(32)
terrain.setFactor(100)
terrain.setFocalPoint(base.camera)

# Store the root NodePath for convenience
root = terrain.getRoot()
root.reparentTo(render)
root.setSz(100)

# Generate it.
terrain.generate()
# Add a task to keep updating the terrain
def updateTask(task):
  terrain.update()
  return task.cont
taskMgr.add(updateTask, "update")

This code shows a dynamically updated terrain, which is updated every frame with the camera as focal point. You see that a few functions are called: The blocksize is set to 32. This means that GeoMipTerrain has to divide the terrain in chunks of 32x32 quads. Next, the factor is set. This is a quality factor, where 0 is a very low quality, and if you make this level higher, the terrain chunks will have a higher quality, depending on the distance to the focal point. Also, the focal point is set to the Camera's NodePath; you can specify any NodePath you want, but also a Point2 or Point3. If you specify the latter, please note that only the X and Y positions are used to calculate the distance; the Z position is disregarded. Note that you need to experiment with those values to get a good quality terrain while still maintaining a good performance.

Next, for convenience, the terrain root is stored in a separate variable, which is scaled and placed in the scene graph. The terrain is then initially generated, and a task is created which calls terrain.update() every frame. This function calculates the new LOD levels based on the movement of the focal point and updates the chunks which have got a new LOD level.

Advanced Control

The GeoMipTerrain provides some advanced features over the terrain:

Minimum Level
You can specify a minimum LOD level to GeoMipTerrain. You can do this if you find the terrain a bit too high quality near the focal point, and this could waste your performance. If you set a minimum LOD level, you can prevent this and force the chunks to have a minimum level of detail:

terrain.setMinLevel(2)

If you make the value higher, it will decrease the quality level near the focal point.

Automatic Flattening
Since flattening the terrain root might interfere with the updating system, GeoMipTerrain provides an auto-flattening function, which can be really useful if you have too many meshes in your scene. This function calls one of NodePath's flattening functions every time the terrain is regenerated, and each time before the chunks are modified the terrain is restored from a backup node.

terrain.setAutoFlatten(GeoMipTerrain.AFMStrong)

There are multiple options: AFMStrong, for flattenStrong(), AFMMedium, for flattenMedium(), AFMLight, for flattenLight(), and AFMOff, for no flattening at all. After setting the AutoFlattenMode, GeoMipTerrain will automatically take care of it at the next update() call.

Notes

  • For a full function overview, see the API page.
  • The GeoMipTerrain generates texture coordinates between 0 and 1, making the texture stretched over the entire terrain. If you are using a shader, please do not directly base the coordinates on the vtx_position, because since the terrain can have multiple chunks the vertex position is relative to the chunk. Instead, base your shader calculations on the vtx_texcoord0 generated by the GeoMipTerrain.
  • The GeoMipTerrain class implements part of the GeoMipMapping algorithm, described in this paper by Willem H. de Boer.
  • In Panda3D versions 1.5.1 and 1.5.2 there is a bug in the bruteforce generator, which might cause your application to crash. It has been fixed in 1.5.3, please upgrade your Panda3D version if you are experiencing this crash.
  • PGMM / GeoMipTerrain is a community-contributed algorithm that is included in Panda3D from version 1.5.1. If you are using an older version of Panda3D, you can download a limited version at the original forum topic.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Advanced operations with Panda's internal structures
<<prev top next>>

The following pages provide descriptions of Panda's internal representation of vertices and the renderable geometry that uses them, as well as instructions for directly reading or manipulating this data.

This is an advanced topic of Panda3D and is not necessary for ordinary model rendering and animation, but the advanced user may find this information useful.


Panda3D Manual: How Panda3D Stores Vertices and Geometry

This section describes the structure and interconnections of Panda's internal vertex and geometry data objects, in general terms.

You should read through this section carefully, so that you have a good understanding of Panda's data structures, before attempting to read the section about generating procedural data.


Panda3D Manual: GeomVertexData

The fundamental object used to store vertex information in Panda is the GeomVertexData. This stores a list of vertices, organized conceptually as a table, where each row of the table represents a different vertex, and the columns of the table represent the different kinds of per-vertex data that may be associated with each vertex. For instance, the following table defines four vertices, each with its own vertex position, normal vector, color, and texture coordinate pair:

 | vertex | normal | color | texcoord
0 | (1, 0, 0) | (0, 0, 1) | (0, 0, 1, 1) | (1, 0)
1 | (1, 1, 0) | (0, 0, 1) | (0, 0, 1, 1) | (1, 1)
2 | (0, 1, 0) | (0, 0, 1) | (0, 0, 1, 1) | (0, 1)
3 | (0, 0, 0) | (0, 0, 1) | (0, 0, 1, 1) | (0, 0)

Vertices are always numbered beginning at 0, and continue to the number of rows in the table (minus 1).

Not all GeomVertexData objects will use these same four columns; some will have fewer columns, and some will have more. In fact, all columns, except for "vertex", which stores the vertex position, are optional.

The order of the columns is not meaningful, but the column names are. There are certain column names that are reserved for Panda, and instruct Panda what the meaning of each column is. For instance, the vertex position column is always named "vertex", and the lighting normal column, if it is present, must be named "normal". See GeomVertexFormat for the complete list of reserved column names.

You can define your own custom columns. If there are any columns that have a name that Panda does not recognize, Panda will not do anything special with the column, but it can still send it to the graphics card. Of course, it is then up to you to write a vertex shader that understands what to do with the data in the column.

It is possible to break up a GeomVertexData into more than one array. A GeomVertexArray is table of vertex data that is stored in one contiguous block of memory. Typically, each GeomVertexData consists of just one array; but it is also possible to distribute the data so that some columns are stored in one array, while other columns are stored in another array:

 | vertex | texcoord | normal | color
0 | (1, 0, 0) | (1, 0) | (0, 0, 1) | (0, 0, 1, 1)
1 | (1, 1, 0) | (1, 1) | (0, 0, 1) | (0, 0, 1, 1)
2 | (0, 1, 0) | (0, 1) | (0, 0, 1) | (0, 0, 1, 1)
3 | (0, 0, 0) | (0, 0) | (0, 0, 1) | (0, 0, 1, 1)

You might want to do this, for instance, if you have certain columns of data that are always the same between different blocks of vertices; you can put those columns in a separate array, and then use the same array within multiple different GeomVertexData objects. There is no limit to the number of different arrays you can have within one GeomVertexData; you can make each column a separate array if you like. (There may be performance implications to consider. Some graphics drivers may work better with one block of contiguous data--one array--while others may prefer many different arrays. This performance difference is likely to be small, however.)


Panda3D Manual: GeomVertexFormat

The GeomVertexFormat object describes how the columns of a GeomVertexData are ordered and named, and exactly what kind of numeric data is stored in each column. Every GeomVertexData has an associated GeomVertexFormat, which describes how the data in that object is stored.

Just as a GeomVertexData object is really a list of one or more GeomVertexArrayData objects, a GeomVertexFormat object is a list of one or more GeomVertexArrayFormat objects, each of which defines the structure of the corresponding array. There will be one GeomVertexArrayFormat object for each array in the format. Each GeomVertexArrayFormat, in turn, consists of a list of GeomVertexColumn objects, one for each column in the array. For instance, the format for a GeomVertexData with six columns, distributed over three different arrays, might look like this:

GeomVertexFormat

GeomVertexArrayFormat

GeomVertexColumn

GeomVertexColumn

GeomVertexColumn

GeomVertexArrayFormat

GeomVertexColumn

GeomVertexArrayFormat

GeomVertexColumn

GeomVertexColumn

Each GeomVertexColumn has a number of properties:

getNumComponents()

This defines the number of numeric components of the data in the column. For instance, the vertex position, which is typically an (X, Y, Z) triple, has three components: X, Y, and Z. A texture coordinate usually has two components (U, V), but sometimes has three components (U, V, W).

getNumericType()

This defines the kind of numeric data that is stored in each component. It must be one of the following symbols:

Geom.NTFloat32 | Each component is a 32-bit floating-point number. This is by far the most common type.
Geom.NTUint8 | Each component is a single 8-bit integer, in the range 0 - 255. OpenGL encodes an RGBA color value as a four-component array of 8-bit integers of this type, in R, G, B, A order.
Geom.NTUint16 | Each component is a single 16-bit integer, in the range 0 - 65535.
Geom.NTUint32 | Each component is a single 32-bit integer, in the range 0 - 4294967295.
Geom.NTPackedDcba | Each component is a 32-bit word, with four 8-bit integer index values packed into it in little-endian order (D, C, B, A), DirectX-style. This is usually used with a 1-component column (since each component already has four values). DirectX uses this format to store up to four indexes into a transform table for encoding vertex animation. (The GeomVertexReader and GeomVertexWriter classes will automatically reorder the A, B, C, D parameters you supply into DirectX's D, C, B, A order.)
Geom.NTPackedDabc | Each component is a 32-bit word, with four 8-bit integer index values packed into it in ARGB order (D, A, B, C). As above, this is normally used with a 1-component column. DirectX uses this format to represent an RGBA color value. (The GeomVertexReader and GeomVertexWriter classes will automatically reorder the R, G, B, A parameters you supply into DirectX's A, R, G, B order.)
getContents()

This defines, in a general way, the semantic meaning of the data in the column. It is used by Panda to decide how the data should be modified when a transform matrix or texture matrix is applied; it also controls the default value for the column data, as well as the way data is stored and fetched from the column.

The contents specification must be one of the following symbols:

Geom.CPoint | The data represents a point in object coordinates, either in 3-D space (if it is a 3-component value) or in 4-D homogenous space (if it is a 4-component value). When a transform matrix is applied to the vertex data, the data in this column is transformed as a point. If a 4-component value is stored into a 3-component column, the fourth component is understood to be a homogenous coordinate, and it implicitly scales the first three. Similarly, if a 4-component value is read from a 3-component column, the fourth value is implicitly 1.0.
Geom.CClipPoint | The data represents a point already transformed into clip coordinates; that is, these points have already been transformed for rendering directly. Panda will not transform the vertices again during rendering. Points in clip coordinates should be in 4-D homogeneous space, and thus usually have four components.
Geom.CVector | The data represents a 3-D vector, such as a normal, tangent, or binormal, in object coordinates. When a transform matrix is applied to the vertex data, the data in this column is transformed as a vector (that is, ignoring the matrix's translation component).
Geom.CTexcoord | The data represents a texture coordinate, either 2-D or 3-D. When a texture matrix (not a transform matrix) is applied to the vertex data, it transforms the data in this column, as a point.
Geom.CColor | The data represents an RGBA color value. If a floating-point value is used to read or write into an integer color component, it is automatically scaled from 0.0 .. 1.0 into the full integer range. Also, the default value of a color column is (1, 1, 1, 1), as opposed to any other column type, whose default value is 0.
Geom.CIndex | The data represents an integer index into some table.
Geom.CMorphDelta | The data represents an offset value that will be applied to some other column during animation.
Geom.COther | The data has some other, custom meaning; do not attempt to transform it.
getName()

The column name is the most important single piece of information to Panda. The column name tells Panda the specific meaning of the data in the column. The name is also a unique handle to the column; within a given GeomVertexFormat, there may not be two different columns with the same name.

There are a number of column names that have special meaning to Panda:

vertex | The position in space of each vertex, usually given as an (x, y, z) triple in 3-D coordinates. This is the only mandatory column for rendering geometry; all other columns are optional. The vertex is usually Geom.NTFloat32, Geom.CPoint, 3 components.
normal | The surface normal at each vertex. This is used to compute the visible effects of lighting; it is not related to the collision system, which has its own mechanism for determining the surface normal. You should have a normal column if you intend to enable lighting; if this column is not present, the object may look strange in the presence of lighting. The normal should always be Geom.NTFloat32, Geom.CVertex, 3 components.
texcoord | The U, V texture coordinate pair at each vertex, for the default coordinate set. This column is necessary in order to apply a texture to the geometry (unless you use a TexGenAttrib). It is usually a 2-D coordinate pair, but sometimes, when you are using 3-d textures or cube maps, you will need a 3-D U, V, W coordinate triple. The texcoord should be Geom.NTFloat32, Geom.CTexcoord, 2 or 3 components.
texcoord.foo | This is the U, V texture coordinate pair for the texture coordinate set with the name "foo" (where foo is any arbitrary name). It is only necessary if you need to have multiple different texture coordinate sets on a piece of geometry, in order to apply multitexturing. As with texcoord, above, it may be a 2-d or a 3-d value.
tangent binormal | These two columns work together, along with the normal column, to implement normal maps (bump maps). They define the normal map space at each vertex. Like a normal, these should be Geom.NTFloat32, Geom.CVertex, 3 components.
tangent.foo binormal.foo | These column names define a tangent and binormal for the texture coordinate set with the name "foo".
color | This defines an RGBA color value. If this column is not present, the default vertex color is white (unless it is overridden with a nodePath.setColor() call). Internally, OpenGL expects the color format to be Geom.NTUint8 (or Geom.NTFloat32), Geom.CColor, 4 components, while DirectX expects the color to be Geom.NTPackedDabc, Geom.CColor, 1 component. In fact, you may use either format regardless of your current rendering backend, and Panda will automatically convert the column as necessary.
rotate size aspect_ratio | These three columns are used when rendering sprites (that is, GeomPoints with nodePath.setRenderModeThickness() in effect). If present, they control the rotation counterclockwise in degrees, the per-vertex thickness, and the aspect ratio of the square, respectively. Each of these should be Geom.NTFloat32, Geom.COther, 1 component.
The remaining column names have meaning only to define vertex animation, for instance to implement Actors. Although these column names are documented below, vertex animation is an advanced feature of the Panda vertex representation; we recommend you let Panda take care of setting up the vertex animation tables, rather than attempting to create them yourself.
transform_blend | This is used to control vertex assignment to one or more animated transform spaces. The value in this column is an integer index into the TransformBlendTable that is associated with the GeomVertexData; each entry in the TransformBlendTable defines a different weighted combination of transform spaces, so by indexing into this table, you can associate each vertex with a different weighted combination of transform spaces.
transform_weight transform_index | These two columns work together, in a manner similar to transform_blend, but they index into the TransformTable associated with the GeomVertexData, instead of the TransformBlendTable. This is particularly suited for sending vertices to OpenGL or DirectX to do the animation, rather than performing the animation on the CPU.
column.morph.slider | Columns with names of this form define a floating-point morph offset that should be scaled by the value of the morph slider named "slider", and then added to the column named "column" (where slider and column are arbitrary names). This is used during vertex animation on the CPU.

A column may have any name (though each name must be unique within a given GeomVertexFormat). If there are additional columns with names other than those in the above table, Panda will not do anything special with the columns, but it will send the vertex data to any vertex shader that requests that data by name, using the vtx_columnname parameter name. See List of Possible Shader Inputs. </tr> <td valign="top" style="border-top: 1px solid black; padding: 5pt"></td> <td style="border-top: 1px solid black; padding: 5pt"></td> </td></tr> </table></center>

There are also additional properties associated with each GeomVertexColumn that determine its exact offset and byte-alignment within each row of the array, but normally you do not need to worry about these, unless you are designing a GeomVertexFormat that matches some already-existing block of data. See the auto-generated API specification for more details.


Panda3D Manual: GeomPrimitive

In order to use the vertices in a GeomVertexData to render anything, Panda needs to have a GeomPrimitive of some kind, which indexes into the vertex table and tells Panda how to tie together the vertices to make lines, triangles, or individual points.

There are several different kinds of GeomPrimitive objects, one for each different kind of primitive. Each GeomPrimitive object actually stores several different individual primitives, each of which is represented simply as a list of vertex numbers, indexing into the vertices stored in the associated GeomVertexData. For some GeomPrimitive types, like GeomTriangles, all the primitives must have a fixed number of vertex numbers (3, in the case of GeomTriangles); for others, like GeomTristrips, each primitive can have a different number of vertex numbers.

For instance, a GeomTriangles object containing three triangles, and a GeomTristrips containing two triangle strips, might look like this:

GeomTriangles | GeomTristrips
0 | 0
1 | 2
2 | 3
 | 5
2 | 6
1 | 1
3 |
 | 5
0 | 1
5 | 3
6 | 2

Note that the GeomPrimitive objects don't themselves contain any vertex data; they only contain a list of vertex index numbers, which is used to look up the actual vertex data in a GeomVertexData object, stored elsewhere.

GeomTriangles

This is the most common kind of GeomPrimitive. This kind of primitive stores any number of connected or unconnected triangles. Each triangle must have exactly three vertices, of course. In each triangle, the vertices should be listed in counterclockwise order, as seen from the front of the triangle.

GeomTristrips

This kind of primitive stores lists of connected triangles, in a specific arrangement called a triangle strip. You can store any number of individual triangle strips in a single GeomTristrips object, and each triangle strip can have an arbitrary number of vertices (at least three).

The first three vertices of a triangle strip define one triangle, with the vertices listed in counterclockwise order. Thereafter, each additional vertex defines an additional triangle, based on the new vertex and the preceding two vertices. The vertices go back and forth, defining triangles in a zig-zag fashion.

Note that the second triangle in a triangle strip is defined in clockwise order, the third triangle is in counterclockwise order, the fourth triangle is in clockwise order again, and so on.

On certain hardware, particularly older SGI hardware and some console games, using triangle strips is an important optimization to reduce the number of vertices that are sent to the graphics pipe, since most triangles (except for the first one) can be defined with only a single vertex, rather than three vertices for each triangle.

Modern PC graphics cards prefer to receive a group of triangle strips connected together into one very long triangle strip, by the introduction of repeated vertices and degenerate triangles. Panda will do this automatically, but in order for this to work you should ensure that every triangle strip has an even number of vertices in it.

Furthermore, since modern PC graphics cards incorporate a short vertex cache, they can generally render individual, indexed triangles as fast as triangle strips; so triangle strips are less important on PC hardware than they have been in the past. Unless you have a good reason to use a GeomTristrips, it may be easier just to use GeomTriangles.

When loading a model from an egg file, Panda will assemble the polygons into triangle strips if it can do so without making other compromises; otherwise, it will leave the polygons as individual triangles.

GeomTrifans

This is similar to a GeomTristrips, in that the primitive can contain any number of triangle fans, each of which has an arbitrary number of vertices. Within each triangle fan, the first three vertices (in counterclockwise order) define a triangle, and each additional vertex defines a new triangle. However, instead of using the preceding two vertices to define each new triangle, a triangle fan uses the previous vertex and the first vertex, which means that all of the resulting triangles fan out from a single point, like this:

Like the triangle strip, a triangle fan can be an important optimization on certain hardware. However, its use can actually incur a performance penalty on modern PC hardware, because it is impossible to send more than one triangle fan in one batch, so you probably shouldn't use triangle fans on a PC. Use GeomTriangles or GeomTristrips instead.

GeomLines

This kind of GeomPrimitive stores any number of connected or unconnected line segments. It is similar to a GeomTriangles, but it draws lines instead of triangles. Each line has exactly two vertices.

By default, line segments are one pixel wide, no matter how far away they are from the camera. You can use nodePath.setRenderModeThickness() to change this; if you specify a thickness greater than 1, this will make the lines render as thick lines, the specified number of pixels wide. However, the lines will always be the same width in pixels, regardless of how far away from the camera they are.

Thick lines are not supported by the DirectX renderer; in DirectX, the thickness parameter is ignored.

GeomLinestrips

This is the analogue of a GeomTristrips object: the GeomLinestrips object can store any number of line strips, each of which can have any number of vertices, at least two. Within a particular line strip, the first two vertices define a line segment; and thereafter, each new vertex defines an additional line segment, connected end-to-end with the previous line segment. This primitive type can be used to draw a curve approximation with many bends fairly easily.

GeomPoints

This is the simplest kind of GeomPrimitive; it stores a number of individual points. Each point has exactly one vertex.

By default, each point is rendered as one pixel. You can use nodePath.setRenderModeThickness() to change this; if you specify a thickness greater than 1, this will make the points render as squares (which always face the camera), where the vertex coordinate is the center point of the square, and the square has the specified number of pixels along each side. Each point will always be the same width in pixels, no matter how far it is from the camera. Unlike line segments, thick points are supported by DirectX.

In addition to ordinary thick points, which are always the same size no matter how far they are from the camera, you can also use nodePath.setRenderModePerspective() to enable a mode in which the points scale according to their distance from the camera. This makes the points appear more like real objects in the 3-D scene, and is particularly useful for rendering sprite polygons, for instance for particle effects. In fact, Panda's SpriteParticleRenderer takes advantage of this render mode. (This perspective mode works only for points; it does not affect line segments.)

Even though the sprite polygons are rendered as squares, remember they are really defined with one vertex, and each vertex can only supply one UV coordinate. This means each sprite normally has only one UV coordinate pair across the whole polygon. If you want to apply a texture to the face of each sprite, use nodePath.setTexGen() with the mode TexGenAttrib.MPointSprite; this will generate texture coordinates on each polygon in the range (0, 0) to (1, 1). You can then transform the texture coordinates, if you wish, using one of the methods like nodePath.setTexOffset(), setTexScale(), etc.


Panda3D Manual: Geom

The Geom object collects together a GeomVertexData and one or more GeomPrimitive objects, to make a single renderable piece of geometry. In fact, an individual Geom is the smallest piece into which Panda will subdivide the scene for rendering; in any given frame, either an entire Geom is rendered, or none of it is.

Fundamentally, a Geom is very simple; it contains a pointer to a single GeomVertexData, and a list of one or more GeomPrimitives, of various types, as needed. All the associated GeomPrimitives index into the same GeomVertexData.

Geom

GeomVertexData

GeomTriangles

GeomTriangles

GeomTristrips

The GeomVertexData pointer may be unique to each Geom, or one GeomVertexData may be shared among many different Geoms (each of which might use a different subset of its vertices). Also, although the GeomPrimitive objects are usually unique to each Geom, they may also be shared between different Geoms.

Although a Geom can have any number of GeomPrimitives associated with it, all of the GeomPrimitives must be of the same fundamental primitive type: triangles, lines, or points. A particular Geom might have GeomTriangles, GeomTristrips, and GeomTrifans; or it might have GeomLines and GeomLinestrips; or it might have GeomPoints. But no one Geom can have primitives from two different fundamental types. You can call geom.getPrimitiveType() to determine the fundamental primitive type stored within a particular Geom.


Panda3D Manual: GeomNode

Finally, GeomNode is the glue that connects Geoms into the scene graph. A GeomNode contains a list of one or more Geoms.

GeomNode

Geom

RenderState

Geom

RenderState

Geom

RenderState

The GeomNode class inherits from PandaNode, so a GeomNode can be attached directly to the scene graph like any other node; and like any node, it inherits a transform and a render state from its parents in the scene graph. This transform and state is then applied to each of the node's Geoms.

Furthermore, the GeomNode stores an additional render state definition for each Geom. This allows each Geom within a given GeomNode to have its own unique state; for instance, each Geom may have a different texture applied.

When a model is loaded from an egg file, normally all the state definitions required to render the geometry will be stored on these per-Geom state definitions, rather than at the GeomNode level. These per-Geom states will override any state that is inherited from the scene graph, unless that scene graph state has a priority higher than the default priority of zero. (This is why it is necessary to specify a second parameter of 1 to the nodePath.setTexture() call, if you want to replace a texture that was applied to a model in the egg file.)


Panda3D Manual: BoundingVolume

A BoundingVolume is a solid enclosing all the geometry of a node and its children that is used for culling and collision detection. (If the BoundingVolume for a node is not visible, there is no need to render that node.) Panda will generate "bounds" for each node automatically by creating BoundingVolumes.

Panda defines the "bounds" of a node to be a geometric bounding volume, of an arbitrary shape (but usually a sphere) that is no smaller than its enclosed geometry. The getBounds() method returns an acceptable bounding volume. It is indeed no smaller than the enclosed geometry. This makes it suitable for use in culling operations and so on. To check the size of the bounding volume, use showBounds().

Note that this BoundingVolume is not the smallest possible. For instance, a sphere of radius 1 has a BoundingSphere with radius 1.73205. Panda doesn't bother going through all the trouble it would take to compute a tight spherical bounds, because the loose bounds that it computes is good enough for Panda's needs. The extra performance gain you'd get for having a tighter culling bounds isn't worth the effort it would take to compute it.

Although it doesn't use it, Panda can create a tighter bounding box. This "tight" bounding box is the smallest axis-aligned box that is no smaller than its enclosed geometry. Thus, it satisfies its definition as a "tight" bounds, because you will not find a tighter bounding volume that is also a box. You can retrieve the bounding box using the getTightBounds method. This box can be shown with showTightBounds()

Further tweaking of the bounding volume used must be done manually

  • If all you care about is Panda's usage of bounding boxes, you can create a BoundingVolume for the node you want and tell Panda to use that one with setFinal(1).

    node.setBounds(BoundingVolume(...))
    node.setFinal(1)

    This will tell Panda to stop calculating bounds and use the one you gave it.

  • If you want to have a node with a manual bounding box set for your own nefarious purposes, set the bounds at the bottom: on the Geom within a GeomNode. This will propagate upwards, assuming there are no other nodes with bounding volumes above it.


Panda3D Manual: Procedurally Generating 3D Models

Building on the fundamental concepts introduced in the last section, the following section explains how to use Panda's basic geometry building blocks to generate your own custom geometry at runtime.


Panda3D Manual: Defining your own GeomVertexFormat

Before you can create any geometry in Panda3D, you must have a valid GeomVertexFormat. You can decide exactly which columns you want to have in your format, by building the format up one column at a time. (But you might be able to avoid this effort by taking advantage of one of the pre-defined formats listed on the next page.)

To build up your custom format, you need to first create an empty GeomVertexArrayFormat, and add columns one at a time by calling addColumn():

array = GeomVertexArrayFormat()
array.addColumn(InternalName.make('vertex'), 3,
                Geom.NTFloat32, Geom.CPoint)

The parameters to addColumn() are, in order, the column name, the number of components, the numeric type, and the contents specification. See GeomVertexFormat for a detailed description of each of these parameters and their appropriate values. You may also supply an optional fifth parameter, which specifies the byte offset within the row at which the column's data begins; but normally you should omit this to indicate that the column's data immediately follows the previous column's data.

Note that the column name should be an InternalName object, as returned by a call to InternalName.make(). This is Panda's mechanism for tokenizing a string name, to allow for fast name lookups during rendering. Other than this detail, the column name is really just an arbitrary string.

It is your responsibility to ensure that all of the parameters passed to addColumn() are appropriate for the column you are defining. The column data will be stored exactly as you specify. When rendering, Panda will attempt to convert the column data as it is stored to whatever format your graphics API (e.g. OpenGL or DirectX) expects to receive.

For instance, to define a vertex format that includes a vertex position and a (U, V) texture coordinate:

array = GeomVertexArrayFormat()
array.addColumn(InternalName.make('vertex'), 3,
                Geom.NTFloat32, Geom.CPoint)
array.addColumn(InternalName.make('texcoord'), 2,
                Geom.NTFloat32, Geom.CTexcoord)

Once you have defined the columns of your array, you should create a GeomVertexFormat to hold the array:

format = GeomVertexFormat()
format.addArray(array)

If you want your format to consist of multiple different arrays, you can create additional arrays and add them at this point as well.

Finally, before you can use your new format, you must register it. Registering a format builds up the internal tables necessary to use the vertex format for rendering. However, once you have registered a format, you can no longer add or remove columns, or modify it in any way; if you want to make changes to the format after this point, you'll have to start over with a new GeomVertexFormat object.

format = GeomVertexFormat.registerFormat(format)

You should always register a format with a syntax similar to the above: that is, you should use the return value of registerFormat as your new, registered format object, and discard the original format object. (The returned format object may be the same format object you started with, or it may be a different object with an equivalent meaning. Either way, the format object you started with should be discarded.)


Panda3D Manual: Pre-defined vertex formats

Panda3D pre-defines a handful of standard GeomVertexFormat objects that might be useful to you. If you don't have any special format needs, feel free to use any of these standard formats, which have already been defined and registered, and are ready to use for rendering.

Each of these formats includes one or more of the standard columns vertex, normal, color, and/or texcoord. For the formats that include a color column, there are two choices, since OpenGL and DirectX have competing internal formats for color (but you can use either form regardless of your current rendering API; Panda will automatically convert the format at render time if necessary).

Standard format | vertex (X, Y, Z) | normal (X, Y, Z) | color, 4-component RGBA (OpenGL style) | color, packed RGBA (DirectX style) | texcoord (U, V)
GeomVertexFormat.getV3() | ✓ |  |  |  |
GeomVertexFormat.getV3n3() | ✓ | ✓ |  |  |
GeomVertexFormat.getV3t2() | ✓ |  |  |  | ✓
GeomVertexFormat.getV3n3t2() | ✓ | ✓ |  |  | ✓
GeomVertexFormat.getV3c4() | ✓ |  | ✓ |  |
GeomVertexFormat.getV3n3c4() | ✓ | ✓ | ✓ |  |
GeomVertexFormat.getV3c4t2() | ✓ |  | ✓ |  | ✓
GeomVertexFormat.getV3n3c4t2() | ✓ | ✓ | ✓ |  | ✓
GeomVertexFormat.getV3cp() | ✓ |  |  | ✓ |
GeomVertexFormat.getV3n3cp() | ✓ | ✓ |  | ✓ |
GeomVertexFormat.getV3cpt2() | ✓ |  |  | ✓ | ✓
GeomVertexFormat.getV3n3cpt2() | ✓ | ✓ |  | ✓ | ✓


Panda3D Manual: Creating and filling a GeomVertexData

Once you have a GeomVertexFormat, registered and ready to use, you can use it to create a GeomVertexData.

vdata = GeomVertexData('name', format, Geom.UHStatic)

The first parameter to the GeomVertexData constructor is the name of the data, which is any arbitrary name you like. This name is mainly for documentation purposes; it may help you identify this vertex data later. You can leave it empty if you like.

The second parameter is the GeomVertexFormat to use for this GeomVertexData. The format specifies the number of arrays that will be created for the data, the names and formats of the columns in each array, and the number of bytes that need to be allocated for each row.

The third parameter is a usage hint, which tells Panda how often (if ever) you expect to be modifying these vertices, once you have filled them in the first time. If you will be filling in the vertices once (or only once in a while) and using them to render many frames without changing them, you should use Geom.UHStatic. The vast majority of vertex datas are of this form. Even GeomVertexDatas that include vertex animation tables should usually be declared Geom.UHStatic, since the vertex data itself will not be changing (even though the vertices might be animating).

However, occasionally you might create a GeomVertexData whose vertices you intend to adjust in-place every frame, or every few frames; in this case, you can specify Geom.UHDynamic, to tell Panda not to make too much effort to cache the vertex data. This is just a performance hint; you're not required to adhere to the usage you specify, though you may get better render performance if you do.

If you are unsure about this third parameter, you should probably use Geom.UHStatic.

Now that you have created a GeomVertexData, you should create a number of GeomVertexWriters, one for each column, to fill in the data.

vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
color = GeomVertexWriter(vdata, 'color')
texcoord = GeomVertexWriter(vdata, 'texcoord')

It is your responsibility to know which columns exist in the GeomVertexFormat you have used. It is legal to create a GeomVertexWriter for a column that doesn't exist, but it will be an error if you later attempt to use it to add data.

To add data, you can now iterate through your vertices and call one of the addData methods on each GeomVertexWriter.

vertex.addData3f(1, 0, 0)
normal.addData3f(0, 0, 1)
color.addData4f(0, 0, 1, 1)
texcoord.addData2f(1, 0)

vertex.addData3f(1, 1, 0)
normal.addData3f(0, 0, 1)
color.addData4f(0, 0, 1, 1)
texcoord.addData2f(1, 1)

vertex.addData3f(0, 1, 0)
normal.addData3f(0, 0, 1)
color.addData4f(0, 0, 1, 1)
texcoord.addData2f(0, 1)

vertex.addData3f(0, 0, 0)
normal.addData3f(0, 0, 1)
color.addData4f(0, 0, 1, 1)
texcoord.addData2f(0, 0)

Each call to addData() adds a new row (vertex) to the vertex data, if there is not already one there. The above sample code creates the following data table:

 | vertex | normal | color | texcoord
0 | (1, 0, 0) | (0, 0, 1) | (0, 0, 1, 1) | (1, 0)
1 | (1, 1, 0) | (0, 0, 1) | (0, 0, 1, 1) | (1, 1)
2 | (0, 1, 0) | (0, 0, 1) | (0, 0, 1, 1) | (0, 1)
3 | (0, 0, 0) | (0, 0, 1) | (0, 0, 1, 1) | (0, 0)

Note that there is no relationship between the different GeomVertexWriters, other than the fact that they are operating on the same table. Each GeomVertexWriter maintains its own counter of its current row. This means you must fill in the data for every row of each column, even if you don't care about writing the data for some particular column on certain rows. For instance, even if you want to allow the default color for vertex 1 and 2, you must still call color.addData4f() four times, in order to fill in the color value for vertex 3.


Panda3D Manual: Creating the GeomPrimitive objects

Now that you have a GeomVertexData with a set of vertices, you can create one or more GeomPrimitive objects that use the vertices in your GeomVertexData.

In general, you do this by first creating a GeomPrimitive of the appropriate type, and then calling addVertex() for each vertex in your primitive, followed by closePrimitive() after each primitive is complete.

Different GeomPrimitive types have different requirements for the number of vertices per primitive. For instance, for a GeomTriangles object, whose primitive type (triangle) has exactly three vertices each, you should call addVertex() three times, followed by closePrimitive() after every three vertices. For a GeomTristrips object, whose primitive type (triangle strip) may have three or more vertices, you should call addVertex() once for each vertex in your triangle strip, followed by closePrimitive() to mark the end of the triangle strip. Then you can begin adding vertices for the second triangle strip in the primitive, and so on.

For example:

prim = GeomTriangles(Geom.UHStatic)

prim.addVertex(0)
prim.addVertex(1)
prim.addVertex(2)
prim.closePrimitive()

prim.addVertex(2)
prim.addVertex(1)
prim.addVertex(3)
prim.closePrimitive()

prim.addVertex(0)
prim.addVertex(5)
prim.addVertex(6)
prim.closePrimitive()

Note that the GeomPrimitive constructor requires one parameter, which is a usage hint, similar to the usage hint required for the GeomVertexData constructor. Like that usage hint, this tells Panda whether you will frequently adjust the vertex indices on this primitive after it has been created. Since it is very unusual to adjust the vertex indices on a primitive (usually, if you intend to animate the vertices, you would operate on the vertices, not these indices), this is almost always Geom.UHStatic, even if the primitive is associated with a dynamic GeomVertexData. However, there may be special rendering effects in which you actually do manipulate this vertex index table in-place every few frames, in which case you should use Geom.UHDynamic. As with the GeomVertexData, this is only a performance hint; you're not required to adhere to the usage you specify.

If you are unsure about this parameter, you should use Geom.UHStatic.

The above sample code defines a GeomTriangles object that looks like this:

0
1
2
2
1
3
0
5
6

The actual positions of the vertices depends on the values of the vertices numbered 0, 1, 2, 3, and 5 in the associated GeomVertexData (you will associate your GeomPrimitives with a GeomVertexData in the next step, when you attach the GeomPrimitives to a Geom).

Finally, there are a few handy shortcuts for adding multiple vertices at once:

prim.addVertices(v1, v2) prim.addVertices(v1, v2, v3) prim.addVertices(v1, v2, v3, v4) | Adds 2, 3, or 4 vertices in a single call.
prim.addConsecutiveVertices(start, numVertices) | Adds numVertices consecutive vertices, beginning at vertex start. For instance, addConsecutiveVertices(5, 3) adds vertices 5, 6, 7.
prim.addNextVertices(numVertices) | Adds numVertices consecutive vertices, beginning with the next vertex after the last vertex you added, or beginning at vertex 0 if these are the first vertices. For instance, prim.addVertex(10) adds vertex 10. If you immediately call prim.addNextVertices(4), it adds vertices 11, 12, 13, 14.

None of the above shortcut methods calls closePrimitive() for you; it is still your responsibility to call closePrimitive() each time you add the appropriate number of vertices.


Panda3D Manual: Putting your new geometry in the scene graph

Finally, now that you have a GeomVertexData and one or more GeomPrimitive objects, you can create a Geom object and a GeomNode to put the new geometry in the scene graph, so that it will be rendered.

geom = Geom(vdata)
geom.addPrimitive(prim)

node = GeomNode('gnode')
node.addGeom(geom)

nodePath = render.attachNewNode(node)

The Geom constructor requires a pointer to the GeomVertexData object you will be using. There is only one GeomVertexData associated with any particular Geom. You can reset the Geom to use a different GeomVertexData later, if you like, by calling geom.setVertexData().

The GeomNode constructor requires a name, which is the name of the node and will be visible in the scene graph. It can be any name you like that means something to you.

In the above example, we have created only one Geom, and added only one GeomPrimitive to the Geom. This is the most common case when you are creating geometry at runtime, although in fact a GeomNode may include multiple Geoms, and each Geom may include multiple GeomPrimitives. (However, all of the primitives added to a Geom must have the same fundamental primitive type: triangles, lines, or points. You can add GeomTriangles and GeomTristrips to the same Geom, or you can add GeomLines and GeomLinestrips, but if you have GeomTriangles and GeomLinestrips, you must use two different Geoms.)

It is important that the range of vertex index numbers used by your GeomPrimitives is consistent with the number of vertices in your GeomVertexData (for instance, if you have 100 vertices in your GeomVertexData, your GeomPrimitives must only reference vertices numbered 0 through 99). If this is not the case, you will get an exception when you call addPrimitive().


Panda3D Manual: Other Vertex and Model Manipulation

This section shows some other ways to manipulate low-level geometry and vertex information, including dynamically creating other kinds of Panda3D objects like Textures.


Panda3D Manual: Reading existing geometry data

You can fairly easily extract and examine or operate on the vertices for an existing model, although you should be aware that the order in which the vertices appear in a model is undefined. There is no correlation between the order in which vertices are listed in an egg file, and the order in which they will appear in the resulting loaded model. Panda may rearrange the vertices, or even add or remove vertices, as needed to optimize the model for rendering performance. Even from one session to the next, the vertices might come out in a different order.

This does make certain kinds of vertex operations difficult; if you plan to write code that expects to encounter the vertices of a model in a particular order, we recommend you build up those vertices yourself using a GeomVertexWriter (as described in Creating and filling a GeomVertexData), so that you have explicit control over the vertex order.

However, if you have no need to operate on the vertices in any particular order, or if you just want to casually browse the vertices in a model, feel free to use the following instructions to read the data.

When you load a model, you have a handle to the root node of the model, which is usually a ModelRoot node. The geometry itself will be stored in a series of GeomNodes, which will be children of the root node. In order to examine the vertex data, you must visit the GeomNodes in the model. One way to do this is to walk through all the GeomNodes like this:

geomNodeCollection = model.findAllMatches('**/+GeomNode')
for nodePath in geomNodeCollection.asList():
  geomNode = nodePath.node()
  processGeomNode(geomNode)

Once you have a particular GeomNode, you must walk through the list of Geoms stored on that node. Each Geom also has an associated RenderState, which controls the visible appearance of that Geom (e.g. texture, backfacing, etc.).

def processGeomNode(geomNode):
  for i in range(geomNode.getNumGeoms()):
    geom = geomNode.getGeom(i)
    state = geomNode.getGeomState(i)
    print geom
    print state
    processGeom(geom)

Note that geomNode.getGeom() is only appropriate if you will be reading, but not modifying, the data. If you intend to modify the geom data in any way (including any nested data like vertices or primitives), you should use geomNode.modifyGeom() instead.

Each Geom has an associated GeomVertexData, and one or more GeomPrimitives. Some GeomVertexData objects may be shared by more than one Geom, especially if you have used flattenStrong() to optimize a model.

def processGeom(geom):
  vdata = geom.getVertexData()
  print vdata
  processVertexData(vdata)
  for i in range(geom.getNumPrimitives()):
    prim = geom.getPrimitive(i)
    print prim
    processPrimitive(prim, vdata)

As above, getVertexData() is only appropriate if you will only be reading, but not modifying, the vertex data. Similarly, getPrimitive() is appropriate only if you will not be modifying the primitive index array. If you intend to modify either one, use modifyVertexData() or modifyPrimitive(), respectively.

You can use the GeomVertexReader class to examine the vertex data. You should create a GeomVertexReader for each column of the data you intend to read. It is up to you to ensure that a given column exists in the vertex data before you attempt to read it (you can use vdata.hasColumn() to test this).

def processVertexData(vdata):
  vertex = GeomVertexReader(vdata, 'vertex')
  texcoord = GeomVertexReader(vdata, 'texcoord')
  while not vertex.isAtEnd():
    v = vertex.getData3f()
    t = texcoord.getData2f()
    print "v = %s, t = %s" % (repr(v), repr(t))

Each GeomPrimitive may be any of a handful of different classes, according to the primitive type it is; but all GeomPrimitive classes have the same common interface to walk through the list of vertices referenced by the primitives stored within the class.

You can use the setRow() method of GeomVertexReader to set the reader to a particular vertex. This affects the next call to getData(). In this way, you can extract the vertex data for the vertices in the order that the primitive references them (instead of in order from the beginning to the end of the vertex table, as above).

def processPrimitive(prim, vdata):
  vertex = GeomVertexReader(vdata, 'vertex')

  prim = prim.decompose()

  for p in range(prim.getNumPrimitives()):
    s = prim.getPrimitiveStart(p)
    e = prim.getPrimitiveEnd(p)
    for i in range(s, e):
      vi = prim.getVertex(i)
      vertex.setRow(vi)
      v = vertex.getData3f()
      print "prim %s has vertex %s: %s" % (p, vi, repr(v))
    print

You may find the call to prim.decompose() useful (as shown in the above example). This call automatically decomposes higher-order primitive types, like GeomTristrips and GeomTrifans, into the equivalent component primitive types, like GeomTriangles; but when called on a GeomTriangles, it returns the GeomTriangles object unchanged. Similarly, GeomLinestrips will be decomposed into GeomLines. This way you can write code that doesn't have to know anything about GeomTristrips and GeomTrifans, which are fairly complex; it can assume it will only get the much simpler GeomTriangles (or, in the case of lines or points, GeomLines and GeomPoints, respectively).


Panda3D Manual: Modifying existing geometry data

If you want to load a model and operate on its vertices, you can walk through the vertices as shown in the previous section, but you should substitute modifyGeom(), modifyVertexData(), and modifyPrimitive() for getGeom(), getVertexData(), and getPrimitive(), respectively. These calls ensure that, in case the data happens to be shared between multiple different GeomNodes, you will get your own unique copy to modify, without inadvertently affecting other nodes.

If you want to modify the vertex data, you have two choices. The simplest option is to create a new GeomVertexData and fill it up with your new vertex data (as described in Creating and filling a GeomVertexData), and then assigning this data to the geom with the call geom.setVertexData(). You must ensure that you add enough vertices to the new GeomVertexData to satisfy the GeomPrimitives that reference it.

Your second choice is to modify the vertex data in-place, by operating on the existing vertices. You can do this with a GeomVertexWriter. For instance, if you want to copy the (X, Y) position of each vertex to its (U, V) texture coordinate, you could do something like this:

texcoord = GeomVertexWriter(vdata, 'texcoord')
vertex = GeomVertexReader(vdata, 'vertex')

while not vertex.isAtEnd():
  v = vertex.getData3f()
  texcoord.setData2f(v[0], v[1])

Important! When you are simultaneously reading from and writing to the same GeomVertexData object, you should create all of the GeomVertexWriters you need before you create any GeomVertexReader. This is because of Panda's internal referencing-counting mechanism; creating a GeomVertexWriter may automatically (and transparently) force a copy of the data in the GeomVertexData, which could invalidate any GeomVertexReaders you have already created.

Writing to a column with a GeomVertexWriter does require that the GeomVertexData's format already has the appropriate columns to handle the data you are writing (in the above example, for instance, the format must already have a 'texcoord' column, or the above code will fail). Furthermore, the columns must have the appropriate format. For instance, if you wanted to upgrade a model's texture coordinates from 2-D texture coordinates to 3-D texture coordinates, simply calling texcoord.setData3f(u, v, w) wouldn't change the fact that the existing texcoord column is a 2-component format; you would just be trying to stuff a 3-component value into a 2-component column.

If you want to add a new column to a GeomVertexData, or modify the format of an existing column, you will have to create a new GeomVertexFormat that includes the new column (see Defining your own GeomVertexFormat), and then change the format on the GeomVertexData via vdata.setFormat(format). This call will internally adjust all of the data to match the new format. (Because of this internal adjustment, it is important to do this before you create the first GeomVertexWriter or GeomVertexReader.)


Panda3D Manual: More about GeomVertexReader, GeomVertexWriter, and GeomVertexRewriter
<<prev top next>>

The classes GeomVertexReader and GeomVertexWriter together represent the core interface for reading and writing the numeric data stored within a GeomVertexData object.

These two classes work similarly. Both are designed to get a temporary pointer to the data for a particular column when they are constructed, and they increment that pointer as you walk through the vertices. Initially, they start at row number 0 (the first vertex in the table), and after each setData/getData operation, they automatically increment to the next row (the next vertex).

You construct a GeomVertexReader or GeomVertexWriter with a pointer to the GeomVertexData object you are operating on, and the name of the column you wish to process, e.g.:

color = GeomVertexReader(vdata, 'color')

Because the GeomVertexReader and GeomVertexWriter classes only store a temporary pointer, which might become invalid between frames or even between different tasks within a frame, these objects should not be stored in any persistent object. Instead, they are designed to be temporary objects that are constructed locally, used immediately to iterate through a list of vertices, and then released. If you need to keep a persistent iterator for your vertex data, to be used over a long period of time (e.g. over several frames), then you should store just the GeomVertexData pointer (along with the current vertex index number if you require this), and construct a temporary GeomVertexReader/Writer each time you need to access it.

The following methods are available to read and write data in a column:

GeomVertexReader | GeomVertexWriter
x = getData1f() | setData1f(x)addData1f(x)
v2 = getData2f() | setData2f(x, y)setData2f(v2)addData2f(x, y)addData2f(v2)
v3 = getData3f() | setData3f(x, y, z)setData3f(v3)addData3f(x, y, z)addData3f(v3)
v4 = getData4f() | setData4f(x, y, z, w)setData4f(v4)addData4f(x, y, z, w)addData4f(v4)
x = getData1i() | setData1i(x)addData1i(x)
 | setData2i(x, y)addData2i(x, y)
 | setData3i(x, y, z)addData3i(x, y, z)
 | setData4i(x, y, z, w)addData4i(x, y, z, w)

Each of the getData family of functions supported by GeomVertexReader returns the value of the data in the current column, converted to the requested type. The 'f' suffix indicates a floating-point value, while 'i' indicates an integer value; the digit indicates the number of components you expect to receive.

For instance, getData2f() always returns a VBase2, regardless of the type of data actually stored in the column. If the column contains a 2-component value such as a 2-D texture coordinate, then the returned value will represent the (U, V) value in that column. However, if the column type does not match the requested type, a conversion is quietly made; for instance, if you call getData2f() but the column actually contains a 3-D texture coordinate, the third component will be omitted from the return value, which will still be a VBase2.

Similarly, the setData and addData family of functions supported by GeomVertexWriter accept a value in the indicated format, and convert it to whatever format is required by the column. So if you call setData3f(), and the column has three components, you will set all three components with the x, y, z parameters of setData3f(); but if the column has only two components, only the x, y parameters will be used to set those two components, and the third parameter will be ignored.

Certain kinds of numeric conversions are performed automatically, according to the column's designated contents. For instance, if you store a floating-point value into an integer column, the fractional part of the value is usually truncated. However, if the column contents indicates that it represents a color value, then the floating-point value is automatically scaled from the range 0.0 .. 1.0 into the full numeric range of the column's integer value. This allows you to store color components in the range 0.0 .. 1.0, and get the expected result (that is, the value is scaled into the range 0 .. 255). A similar conversion happens when data is read.

There are no getData2i, 3i, or 4i methods available, simply because Panda does not currently define a multi-component integer value that can be returned to Python. Since most multi-component column types are floating-point, or can be expressed as floating-point, this is not generally a limitation.

Each GeomVertexReader keeps track of the current read row, which is initially 0; the current value can be retrieved by getReadRow(). Each call to a getData function returns the value of the column at the current read row, and then increments the current read row. It is an error to call getData when the read row has reached the end of the data, but you can call isAtEnd(), which returns true when the reader has reached the end. Thus, you can iterate through all the rows of a vertex table by repeatedly calling getData until isAtEnd() returns true.

Similarly, each GeomVertexWriter keeps track of the current write row, which is initially 0, and can be retrieved by getWriteRow(). Each call to setData or addData stores the given value in the current write row, and then increments the current write row. It is an error to call setData when the write row has reached the end of the data; but as with the GeomVertexReader, you can call isAtEnd() to determine when you have reached the end of the data.

The addData family of functions work exactly like the setData functions, except that addData can be called when the GeomVertexWriter has reached its end. In this case, addData will add a new row to the table, and then fill in the specified data in that row (and then increment the current write row). If addData is called when the current write row already exists, it behaves exactly the same as setData.

With either GeomVertexReader or GeomVertexWriter, you can set the current read or write row at any time with the call setRow(). This sets the current read row (GeomVertexReader) or current write row (GeomVertexWriter) to the indicated value; the next call to getData or setData/addData will then operate on the specified row, and increment from there.

GeomVertexRewriter

The GeomVertexRewriter class exists as a convenience for code that needs to alternately read and write the data on a column. GeomVertexRewriter multiply inherits from GeomVertexReader and GeomVertexWriter, so it supports the getData family of functions, as well as the setData and addData family of functions. It also has both a current read row and a current write row, which might be different.

Normally, you would use a GeomVertexRewriter to walk through the list of vertices from the beginning to end, reading and writing as it goes. For instance, to set all of the Z components of a piece of geometry to 0.0, while preserving the X and Y components, you might write a loop such as:

vertex = GeomVertexRewriter(vdata, 'vertex')
while not vertex.isAtEnd():
  v = vertex.getData3f()
  vertex.setData3f(v[0], v[1], 0.0)

Note that this example code calls getData3f() and setData3f() exactly once through each iteration, which increments the current read row and current write row, respectively; so the current read row and current write row are kept in sync with each other.

Important! When you are simultaneously reading from and writing to the same GeomVertexData object, you should create all of the GeomVertexWriters and GeomVertexRewriters you need before you create any GeomVertexReader. This is because of Panda's internal referencing-counting mechanism; creating a GeomVertexWriter may automatically (and transparently) force a copy of the data in the GeomVertexData, which could invalidate any GeomVertexReaders you have already created.


Panda3D Manual: Creating New Textures from Scratch

The PNMImage Class

This class is how Panda3D handles regular images (.gif, .jpg, and the like). This class allows you to manipulate individual pixels of the image. You can load existing images using the function read(fileName) where filename is the path to the image file (in Panda Filename Syntax) wrapped in a Filename object. Or, you can create a brand new image from scratch, by passing the x, y size to the constructor.

myImage=PNMImage()
myImage.read(Filename("testImg.gif"))
myEmptyImage = PNMImage(256, 256)

You can get the size of the image you have read using the getXSize() and getYSize() functions. Although you cannot change the size of an image directly, you can rescale an image by filtering it into a larger or smaller PNMImage:

fullSize=PNMImage(Filename("testImg.gif"))
reduced=PNMImage(256, 256)
reduced.gaussianFilterFrom(1.0, fullSize)

You can get individual RGB values using the getRed(x,y), getGreen(x,y), getBlue(x,y) or getRedVal(x,y), getGreenVal(x,y), getBlueVal(x,y) where x and y tell what pixel to look at (lower-left is 0,0 upper right is getXSize()-1, getYSize()-1 The difference between these functions is that the get*Val functions return a number between 0 and 1 while the get* functions retrun their value as an integer. For example, if your image uses 8-bit color calling getGreenVal on a green pixel will return 255 and calling getGreen will return 1. You can also get all the RGB information at the sametime using getXel(x,y) and getXelVal(x,y) which return a 3 component vector with red in the x, green in the y, and blue in the z.

 #the pixel at 0,0 is red and we're using 8-bit color
myImage.getRedVal(0,0) #returns 255
myImage.getRed(0,0)#returns 1

colors=myImage.getXelVal(0,0) #returns (255,0,0)
colorVal=myImage.getXel(0,0) #returns (1,0,0)

The functions for setting pixel information are setRed(x,y,value), setGreen(x,y, value), setBlue(x,y, value) or setRedVal(x,y,value), setGreenVal(x,y, value), setBlueVal(x,y, value). Theres still the same dichotomy as above when it comes to regular sets and using setvals. You can also use setXel(x,y,colorVec) and setXelVal(x,y, colorVec). You can also fill an image with a color by using fill(r,g,b) and fillVal(r,g,b).

myImage.setGreenVal(0,0, 255) # if pixel (0,0) was red before, now it is yellow (255,255,0)
myImage.setBlue(0,0,1) #pixel (0,0) is now white

gray=Vec3(0.5,0.5,0.5)

#both of these set the origin to gray
myImage.setXelVal(0,0,gray*255)
myImage.setXel(0,0,gray)

#makes every pixel red
myImage.fillVal(255,0,0)
#makes every pixel green
myImage.fill(0,1,0)

There are also gets and sets for the alpha channel using the same interface as above. However, if you use them on an image that doesn't have an alpha channel you will cause a crash. To see if an image has an alpha channel use hasAlpha() which returns True if there is an alpha channel and False otherwise. You can add an alpha channel using addAlpha(). You can also remove it using removeAlpha().

You can also make an image grayscale by using makeGrayscale(). You can now use sets and gets for Gray too. Using getGray* on a color image just returns the value in the blue channel. If you want to get the grayscale value of a pixel regardless of whether the image is a grayscale or a color image, you can use getBright(x,y), which works equally well on color or on grayscale images. If you want to weight the colors use getBright(x,y, r,g,b) where r,g,b are the weights for their respective channels.

There are several other useful functions in the class this the API Reference for more information.

Getting the Image of a Texture

The Panda Texture class does not allow for pixel manipulation. However the PNMImage class below does. Therefore, if you want to change the image in a Texture object you must call its store(myImage) which saves the image of the texture into myImage.

myImage=PNMImage()
myTexture=loader.loadTexture("myTex.jpg")

#after this call, myImage now holds the same image as the texture
myTexture.store(myImage)

Loading a PNMImage Into a Texture

Once you have changed all the data in the image you can now load it into a texture using the Texture objects load(myImage) function, where myImage is the PNMImage to make the texture from.

 #assume we already have myImage which is our modified PNMImage
myTexture=Texture()

#This texture now represents myImage
myTexture.load(myImage)

Remember however, that most graphics cards require that the dimensions of texture have to be a power of two. PNMImage does not have this restriction and Panda will not automatically scale the image when you put it into a texture.


Panda3D Manual: Writing 3D Models out to Disk

Panda has two native file formats for models.

Egg files (with the extension .egg) are written in an ASCII human readable format. The egg format is designed to be easy to read and modify if necessary, and easy to write a convert into from another third-party format. Also, the egg format is intended to be backward-compatible from all future versions of Panda3D, so if you have an egg file that Panda can load now, it should always be able to load that file. (Well, we can't really make guarantees, but this is what we shoot for.) See Parsing and Generating Egg Files for more information about the egg format.

BAM Files

Because of the way the egg syntax is designed, an egg file might be very large, sometimes many times larger than the file it was converted from. It can also sometimes take several seconds for Panda to load a large egg file.

Bam files (with the extension .bam), on the other hand, are binary files that are closely tied to a particular version of Panda3D. The bam format is designed to be as similar as possible to the actual Panda data structures, so that a bam file is relatively small and can be loaded very quickly. However, you should not consider the bam file format to be a good long-term storage format for your models, since a future version of Panda3D may not be able to load bam files from older versions.

You can always convert egg files to bam files using the program egg2bam. For many simple models, it is also possible to convert back again with the program bam2egg, but you should not rely on this, since it does not convert advanced features like animation; and some structure of the original egg file may be lost in the conversion.

You can load files of these formats, as well as any other supported format, using the loader.loadModel interface. Any file types other than .bam or .egg will be automatically converted at runtime, exactly as if you had run the appropriate command-line conversion tool first.

The Bam Interface

The easiest way to save geometry is to use to call writeBamFile(filename) from the NodePath that contains your geometry.

myPanda=loader.loadModel("panda")
...
#do some fancy calculations on the normals, or texture coordinates that you dont
#want to do at runtime
...
#Save your new custom Panda
myPanda.writeBamFile("customPanda.bam")

The Egg Interface

One easy way to create .egg file for geometry that has already been made is to create a .bam file and use bam2egg. However, you will often want to use the egg interface to create geometry in the first place; this is usually the easiest way to create geometry in Panda3D.

The complete documentation for using the egg interfaces has yet to be written, but the egg library is really quite simple to use. The basic idea is that you create an EggData, and an EggVertexPool to hold your vertices; and then you can create a series of EggVertex and EggPolygon objects. If you want to create some structure in your egg file, you can create one or more EggGroups to separate the polygons into different groups. Here is an example:

def makeWedge(angleDegrees = 360, numSteps = 16):
    data = EggData()

    vp = EggVertexPool('fan')
    data.addChild(vp)

    poly = EggPolygon()
    data.addChild(poly)

    v = EggVertex()
    v.setPos(Point3D(0, 0, 0))
    poly.addVertex(vp.addVertex(v))

    angleRadians = deg2Rad(angleDegrees)

    for i in range(numSteps + 1):
        a = angleRadians * i / numSteps
        y = math.sin(a)
        x = math.cos(a)

        v = EggVertex()
        v.setPos(Point3D(x, 0, y))
        poly.addVertex(vp.addVertex(v))

    # To write the egg file to disk, use this:
    data.writeEgg(Filename("wedge.egg"))

    # To load the egg file and render it immediately, use this:
    node = loadEggData(data)
    return NodePath(node)

See the generated API documentation for more complete information about the egg library.


Panda3D Manual: Render-to-Texture and Image Postprocessing

Render-to-Texture Basics

Conceptually, you can think of render-to-texture as being like rendering a scene into a hidden window, and then copying the contents of the window into a texture. Such hidden windows are not actually called windows, they're called "buffers." Furthermore, the data is usually not copied into the texture, it's usually transferred into the texture using a more efficient mechanism. Details aside, though, if you think of it as rendering into a hidden window and then copying into a texture, you have a pretty accurate idea of what render-to-texture does.

Render-to-texture is particularly useful when you'd like to apply a "filter" to your scene. If you've ever used Photoshop, you know what a filter is: Photoshop has lots of interesting filters that do blurring, texturizing, edge detection, and more. The first step in implementing a filter is to render the entire scene into a texture. Then, the texture is applied to a full-screen quad and displayed in a window. So far, it looks the same as if you simply rendered the scene into the window. However, by applying a shader to the full-screen quad, you can implement your filter.

There are several other interesting uses for render-to-texture: water reflections, environment mapping, creating virtual televisions, shadow mapping... the list is quite long.

The Low-Level API and the High-Level Utilities

Panda3D contains a very low-level API for creating offscreen buffers, creating render-target textures, and the like. This low-level API is extremely flexible: it gives you a great deal of control over what kinds of buffers you create, over what gets rendered into them, over how the data gets transferred to a texture, and so forth. The advantage of using the low-level API is that you can control everything. The disadvantage is that you have to control everything, and there's a lot of stuff to control.

Panda3D also provides a number of utilities that make it convenient to use render-to-texture for certain specific applications. For example, there is a utility that helps set up common image filters. There is another that helps set up cube-map environment maps. The downside of these utilities is that each one performs a fairly specific function, and if there isn't one for your particular application, you'll need to resort to the lower-level API.

In the following sections, we document the high-level utilities first, because these are what most people are going to use. If none of the utilities does what you need, then the last subsection explains the low-level API.


Panda3D Manual: Common Image Filters

Common Image Filters

Note: the following information applies to Panda version 1.5.0 and beyond. It does not apply to older versions of Panda3D.

The purpose of class CommonFilters is to make it easy to set up a number of common image postprocessing operations. Currently, the image postprocessing operations supported by class CommonFilters is:

  1. Bloom Filter - creates a glowing halo around bright objects.
  2. Cartoon Inker - draws black lines around 3D objects.

We expect this list to grow rather substantially over the next year or so.

Basic Setup

The first step is to create an object of class CommonFilters. Pass in a pointer to your window, and your 3D camera:

filters = CommonFilters(base.win, base.cam)

This will have no effect until you enable a filter (instructions below.) Once a filter is enabled, class CommonFilters will reconfigure the Panda3D rendering as follows:

  • It will render the scene into an offscreen buffer, using the camera you provided.
  • It will remove the scene from the specified window, and replace it with a fullscreen quad.
  • The quad will be textured with the scene, plus a shader that implements whatever filter you have selected.

If all goes well, the net effect is that your scene will continue to appear in your window, but it will be filtered as you specify.

What if the Video Card can't handle it?

If the video card is not capable of implementing your filters, then all filters will be removed and the filter-enabling function will return False. Otherwise, filter-enabling functions will return True.

The Bloom Filter

The bloom filter causes bright objects to have a glowing halo around them. To enable a bloom filter, use setBloom. To disable, use delBloom:

filters.setBloom( ... options ...)
filters.delBloom()

The bloom filter works as follows. First, it renders the scene into a texture. It also asks the renderer to render any glow-maps into the alpha channel of the texture. After rendering the scene, it generates a second copy of the scene which has been darkened until only the brightest pixels are visible, and all the others go to black. It then blurs that texture, yielding soft halos where the bright pixels used to be, and black everywhere else. It then adds the soft halos back onto the scene in the window.

The bloom filter has many keyword parameters:

  • blend - The bloom filter needs to measure the brightness of each pixel. It does this by weighting the R,G,B, and A components. Default weights: (0.3,0.4,0.3,0.0). You should assign a nonzero weight to the alpha channel if you want the glow map to have an effect.
  • mintrigger - Minimum brightness at which a halo is generated. Default: 0.6
  • maxtrigger - Maximum brightness at which the halo reaches peak intensity. Default: 1.0
  • desat - Degree to which the halo is desaturated. Setting this to zero means the halo is the same color as the bright pixel. Setting it to one means the halo is white. Default: 0.6
  • intensity - An adjustment parameter for the brightness of the halos. Default: 1.0
  • size - Adjusts the size of the halos. Takes a string value: "small", "medium", or "large". The reason that this is a discrete value and not a continuous one is that the blur operation involves downsampling the original texture by a power of two. Default: "medium"

The Cartoon Inking Filter

The cartoon inking filter causes objects to have black lines around them. To enable a cartoon inking filter, use setCartoonInk. To disable, use delCartoonInk:

filters.setCartoonInk( ... options ...)
filters.delCartoonInk()

The cartoon inking filter works by rendering a camera-space normal into an texture. Then, a postprocessing filter does an edge-detect algorithm on the camera-space normal texture.

The filter has the following keyword parameters:

  • separation - distance in pixels, controls the width of the ink line. Default: 1 pixel.


Panda3D Manual: Generalized Image Filters

The Filter Manager

Class FilterManager is designed to make it easier to apply filters to your scene. Of course, the easiest way to apply filters to your scene is to use class CommonFilters. But if that utility does not contain the filters you need, then the FilterManager is your next best choice. The main function of the FilterManager is to help you set up the offscreen buffers, the quads, and the textures.

The Simplest Filter

The simplest possible code that uses the FilterManager looks like this:

manager = FilterManager(base.win, base.cam)
tex = Texture()
quad = manager.renderSceneInto(colortex=tex)
quad.setShader(Shader.load("myfilter.sha"))
quad.setShaderInput("tex", tex)

The first line creates an object of class FilterManager. We have told it that we want to apply filtering to the contents of the main window. We have also told it that we want to filter the stuff that's being rendered by the main camera, and not, for instance, the 2D camera.

The second line creates a texture - this is the texture that we're going to render the scene into.

The third line does most of the work. This removes the scene from the window, and instead, directs the rendering of the scene into 'tex'. It puts a quad into the window in place of the scene. The quad is returned.

Finally, we apply a shader to the quad, and pass the scene texture to the shader. Presumably, the shader is rendering the scene onto the quad, which covers the window. Presto, filtered scene.

There's one tricky aspect of all this. Usually, the window is usually not a power of two. The texture will end up being bigger than the window: for instance, if the window is 800x600, then the texture will be 1024x1024. The scene will be rendered into the lower-left 800x600 pixels of the texture. The shader needs to compensate for this. If you forget this, you will see an empty band above and to the right of the texture.

Extracting More Information from the Scene

In addition to fetching the color buffer of the scene, you can also fetch the depth buffer:

manager = FilterManager(base.win, base.cam)
tex = Texture()
dtex = Texture()
quad = manager.renderSceneInto(colortex=tex, depthtex=dtex)

The depth buffer is particularly useful for filters like depth-of-field. You can pass the depth-texture to the shader too.

Sometimes, scene rendering may generate not just a color buffer and a depth buffer, but also an auxiliary buffer. If so, you can fetch that too:

manager = FilterManager(base.win, base.cam)
tex = Texture()
atex = Texture()
quad = manager.renderSceneInto(colortex=tex, auxtex=atex)

Doing this would really only make sense if you've asked the renderer to put something of interest into the auxiliary buffer. To do this, see AuxBitplaneAttrib.

Using Intermediate Stages

The setup shown above works for any filter that can be computed in one stage. However, for certain filters, you want to perform intermediate computations before putting the output into the window.

The method renderQuadInto creates a quad, and then causes that quad to be rendered into a texture. This is the classic intermediate processing step for image postprocessing. Using renderQuadInto, we can create a simple two-stage filter:

manager = FilterManager(base.win, base.cam)
tex1 = Texture()
tex2 = Texture()
finalquad = manager.renderSceneInto(colortex=tex1)
interquad = manager.renderQuadInto(colortex=tex2)
interquad.setShader(Shader.load("stage1.sha"))
interquad.setShaderInput("tex1", tex1)
finalquad.setShader(Shader.load("stage2.sha"))
finalquad.setShaderInput("tex2", tex2)

So tex1 will contain the raw, unfitered scene. Tex2 will contain a scene that has been filtered through stage1.sha. The window will contain a scene that has been filtered through both stage1.sha and stage2.sha.

The function 'renderQuadInto' accepts the keywords 'colortex', 'auxtex0', and 'auxtex1'. It does not accept 'depthtex,' since no depth buffer is used when rendering a quad.

Resolution Management

Unless you specify otherwise, all textures will be the same resolution as the window. The FilterManager will preserve this condition - it will automatically resize the offscreen textures if the window gets resized.

The intermediate stages created by renderQuadInto can be the same size as the window, but they can also be larger or smaller by a constant factor. The function takes the following keyword arguments:

  • mul - The 'mul' option multiplies the size by an integer constant.
  • div - The 'div' option divides the size by an integer constant.
  • align - Relevant only when using the 'div' option - the window size is aligned to a specified alignment before dividing. This is useful to minimize resampling artifacts.

Cleaning Up

This function will cause the FilterManager to put everything back the way it started:

manager.cleanup()


Panda3D Manual: Dynamic Cube Maps

Since the six faces of a cube map are really just six different views of a scene from the same point, it's possible to generate a cube map automatically by rendering these six different views at runtime.

This is really just a form of offscreen rendering to a texture. Instead of rendering just one 2-D texture image, though, rendering a dynamic cube map means rendering six different 2-D images, one for each face of a cube map texture.

Panda3D makes this easy for you. To start rendering a dynamic cube map, simply call:

rig = NodePath('rig')
buffer = base.win.makeCubeMap(name, size, rig)

This will return an offscreen GraphicsBuffer that will be used to render the cube map. The three required parameters to makeCubeMap() are:

name: An arbitrary name to assign to the cube map and its associated GraphicsBuffer. This can be any string.

size: The size in pixels of one side of the cube. Many graphics cards require this size to be a power of two. Some cards don't require a power of two, but will perform very slowly if you give anything else.

rig: The camera rig node. This should be a new NodePath; it will be filled in with six cameras. See below.

There are also additional, optional parameters to makeCubeMap():

cameraMask: This specifies the DrawMask that is associated with the cube map's cameras. This is an advanced Panda3D feature that can be used to hide or show certain objects specifically for the cube map cameras.

toRam: This is a boolean flag that, when True, indicates the texture image will be made available in system RAM, instead of leaving it only in texture memory. The default is False. Setting it True is slower, but may be necessary if you want to write out the generated cube map image to disk.

Note that we passed a new NodePath, called rig in the above example, to the makeCubeMap() call. This NodePath serves as the "camera rig"; the makeCubeMap() method will create six cameras facing in six different directions, and attach them all to the camera rig. Thus, you can parent this rig into your scene and move it around as if it were a six-eyed camera. Normally, for environment maps, you would parent the rig somewhere within your shiny object, so it can look out of the shiny object and see the things that should be reflected in it.

The actual cube map itself be retrieved with the call:

tex = buffer.getTexture()

You can apply the texture to geometry as in the previous example. You should use the MWorldCubeMap mode to generate texture coordinates for your geometry, since the camera rig will have a CompassEffect on it to keep it unrotated with respect to render.

When you are done with the cube map, you should remove its buffer (and stop the cube map from continuing to render) by calling:

base.graphicsEngine.removeWindow(buffer)

As a complete example, here is how we might load up a dynamic cube map environment on our teapot, and move the teapot down the street to show off the dynamic reflections:

scene = loader.loadModel('bvw-f2004--streetscene/street-scene.egg')
scene.reparentTo(render)
scene.setZ(-2)

teapot = loader.loadModel('teapot.egg')
teapot.reparentTo(render)

rig = NodePath('rig')
buffer = base.win.makeCubeMap('env', 64, rig)
rig.reparentTo(teapot)

teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldCubeMap)
teapot.setTexture(buffer.getTexture())

zoom = teapot.posInterval(5, VBase3(20, 0, 0), startPos = VBase3(-20, 0, 0))
zoom.loop()

A word of caution

When you render a dynamic cube map, don't forget that you are re-rendering your scene six times every frame, in addition to the main frame render. If you are not careful, and you have a complex scene, you could easily end up reducing your frame rate by a factor of seven.

It is a good idea to limit the amount of geometry that you render in the cube map; one simple way to do this is to ensure that the far plane on the cube map cameras is set relatively close in. Since all of the cube map cameras share the same lens, you can adjust the near and far plane of all of the cameras at once like this:

lens = rig.find('**/+Camera').node().getLens()
lens.setNearFar(1, 100)

It is especially important, when you are using cube maps, that you structure your scene graph hierarchically and divide it up spatially, so that Panda3D's view-frustum culling can do an effective job of eliminating the parts of the scene that are behind each of the six cameras. (Unfortunately, the streetscene model used in the above example is not at all well-structured, so the example performs very poorly on all but the highest-end hardware.)

It's also usually a good idea to keep the cube map size (the size parameter to makeCubeMap) no larger than it absolutely has to be to get the look you want.

You can also take advantage of the DrawMask to hide things from the cube cameras that are not likely to be important in the reflections. The documentation for this advanced feature of Panda3D will be found in another section of the manual (which, as of the time of this writing, has yet to be written).

Finally, you can temporarily disable the cube map rendering from time to time, if you know the environment won't be changing for a little while; the cube map will retain its last-rendered image. You can do this with buffer.setActive(0). Use buffer.setActive(1) to re-activate it.


Panda3D Manual: Low-Level Render to Texture

Render to Texture Basics

In Panda3D, rendering to a texture consists of three basic steps:

  • Create a hidden window (class GraphicsBuffer).
  • Render into the hidden window.
  • Transfer the contents of the hidden window into a texture.

When I say "transfer" the contents of a window into a texture, I don't necessarily mean "copy." There are other ways to transfer the contents of a window into a texture that may be faster. For example, if the OpenGL implementation supports the ARB_pbuffers extension, then the transfer might be achieved using wglBindTexImageARB. The Panda user does not need to worry about how the transfer is done. It is only important that you know that Panda will use the fastest means available to transfer the contents of the window into the texture.

To generalize that a bit, although render-to-texture is usually done with a hidden window (class GraphicsBuffer), it can also be done with a visible window (class GraphicsWindow). You can transfer the contents of any window, hidden or not, into a texture. That's potentially useful - for example, you can transfer the contents of the main window into a texture, which you can then use when rendering the next frame. This can be used to create accumulation-buffer-like effects without an accumulation buffer.

The Simple API: makeTextureBuffer

Here is a short snippet of code that creates a hidden window, creates a camera that renders into that window, and creates a scene graph for that camera:

mybuffer = base.win.makeTextureBuffer("My Buffer", 512, 512)
mytexture = mybuffer.getTexture()
mybuffer.setSort(-100)
mycamera = base.makeCamera(mybuffer)
myscene = NodePath("My Scene")
mycamera.node().setScene(myscene)

The makeTextureBuffer is the simple interface to the render-to-texture functionality. It creates a new hidden window (usually a GraphicsBuffer), creates a texture to render into, and connects the texture to the hidden window. The (512, 512) in the function call specifies the size of the hidden window and texture. Of course, you need to use a power-of-two size. The getTexture method retrieves the texture, which will be rendered into every frame.

The setSort method sets a window's sort order. This controls the order in which panda renders the various windows. The main window's sort order is zero. By setting the sort order of mybuffer to a negative number, we ensure that mybuffer will be rendered first. That, in turn, ensures that mytexture will be ready to use by the time that the main window is rendered.

The new hidden window is not automatically connected to the scene graph. In this example, we create a separate scene graph rooted at myscene, create a camera to view that scene graph, and connect the camera to mybuffer.

The function makeTextureBuffer usually creates a GraphicsBuffer (hidden window), but if the video card is not powerful enough to create an offscreen window, it may not be able to do so. In that case, makeTextureBuffer will create a parasiteBuffer instead. A parasite buffer is primarily a trick to emulate a GraphicsBuffer on video cards that are less powerful. The trick is this: instead of rendering to an offscreen window and then transferring the data into a texture, panda renders into the main window and then copies the data into the texture. The limitations of this trick are self-evident. First, it garbles the contents of the main window. This is usually no big deal, since the main window is usually cleared and rendered from scratch every frame anyway. The other problem with this trick is that it fails if the main window is smaller than the desired texture. Since neither of these problems is common in practice, makeTextureBuffer will use parasite buffers transparently if GraphicsBuffers are not available.

There is a debugging mode in which makeTextureBuffer will create a visible window (class GraphicsWindow) instead of a hidden one (class GraphicsBuffer). To enable this debugging mode, set the boolean variable "show-buffers #t" in your panda configuration file.

The Advanced API: addRenderTexture

The simple API is convenient, but there are a few things it can not do. For instance, it can not:

  • Copy the main window into a texture.
  • Copy the Z-buffer into a depth texture.
  • Copy the window into a texture, but not every frame.
  • Limit or force the use of Parasite buffers.

If you need this level of control, you need to use a lower-level API. The documentation for the lower-level API is not currently written. However, several of the Sample Programs use the lower-level API.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Panda Rendering Process

The rendering process in Panda is comprised by four classes and their interactions: GraphicsPipe, GraphicsEngine, GraphicsStateGaurdian, and GraphicsOutput. The following sections will explain the purpose of each of these classes in detail.

Note that the following interfaces are for the advanced user only. If you are writing a simple application that only needs to open a window and perform basic 3-D rendering, there is no need to use any of these interfaces, as the appropriate calls to open a default window are made automatically when you import direct.directbase.DirectStart at the start of your application.

At some point, however, you may wish to understand more deeply how to manage your windows and buffers, and to do this it will help you to understand how everything is connected together.


Panda3D Manual: Introducing Graphics Classes

GraphicsEngine

The GraphicsEngine is where it all begins. There is only one, global, GraphicsEngine in an application, and its job is to keep all of the pointers to your open windows and buffers, and also to manage the task of doing the rendering, for all of the open windows and buffers. Panda normally creates a GraphicsEngine for you at startup, which is available as base.graphicsEngine. There is usually no reason to create a second GraphicsEngine.

GraphicsPipe

Each application will also need at least one GraphicsPipe. The GraphicsPipe encapsulates the particular API used to do rendering. For instance, there is one GraphicsPipe class for OpenGL rendering, and a different GraphicsPipe for DirectX. Although it is possible to create a GraphicsPipe of a specific type directly, normally Panda will create a default GraphicsPipe for you at startup, which is available in base.pipe.

The GraphicsPipe object isn't often used directly, except to create the individual GraphicsWindow and GraphicsBuffer objects.

GraphicsWindow and GraphicsBuffer

The GraphicsWindow class is the class that represents a single onscreen window for rendering. Panda normally opens a default window for you at startup, which is available in base.win. You can create as many additional windows as you like. (Note, however, that some graphics drivers incur a performance penalty when multiple windows are open simultaneously.)

Similarly, GraphicsBuffer is the class that represents a hidden, offscreen buffer for rendering special offscreen effects, such as render-to-texture. It is common for an application to have many offscreen buffers open at once.

Both classes inherit from the base class GraphicsOutput, which contains all of the code common to rendering to a window or offscreen buffer.

GraphicsStateGuardian

The GraphicsStateGuardian, or GSG for short, represents the actual graphics context. This class manages the actual nuts-and-bolts of drawing to a window; it manages the loading of textures and vertex buffers into graphics memory, and has the functions for actually drawing triangles to the screen. (During the process of rendering the frame, the "graphics state" changes several times; the GSG gets its name from the fact that most of its time is spent managing this graphics state.)

You would normally never call any methods on the GSG directly; Panda handles all of this for you, via the GraphicsEngine. This is important, because in some modes, the GSG may operate almost entirely in a separate thread from all of your application code, and it is important not to interrupt that thread while it might be in the middle of drawing.

Each GraphicsOutput object keeps a pointer to the GSG that will be used to render that window or buffer. It is possible for each GraphicsOutput to have its own GSG, or it is possible to share the same GSG between multiple different GraphicsOutputs. Normally, it is preferable to share GSG's, because this tends to be more efficient for managing graphics resources.

Consider the following diagram to illustrate the relationship between these classes. This shows a typical application with one window and two offscreen buffers:

GraphicsEngine

 | GraphicsPipe |
↙ | ↓ | ↘
GraphicsOutput (window) | GraphicsOutput (buffer) | GraphicsOutput (buffer)
┊ | ┊ | ┊
GSG | GSG | GSG

The GraphicsPipe was used to create each of the three GraphicsOutputs, of which one is a GraphicsWindow, and the remaining two are GraphicsBuffers. Each GraphicsOutput has a pointer to the GSG that will be used for rendering. Finally, the GraphicsEngine is responsible for managing all of these objects.

In the above illustration, each window and buffer has its own GSG, which is legal, although it's usually better to share the same GSG across all open windows and buffers.


Panda3D Manual: The Graphics Pipe

The GraphicsPipe class is Panda3D's interface to the available 3-D API's, for instance OpenGL or DirectX. In order to create a window that renders using a particular API, you must have a GraphicsPipe for that API.

Normally, there is one default graphics pipe created for you automatically when you import DirectStart, accessible as base.pipe. For most applications, there is no need to create any additional graphics pipes.

There are two Config.prc variables that determine the graphics pipe or pipes that will be available to an application:

load-display: this variable specifies the first choice for the graphics pipe. It names the type of GraphicsPipe that should be attempted first, e.g. pandagl or pandadx8. If for some reason a GraphicsPipe of this type cannot be created, for instance because of lack of driver support, then Panda3D will fall back to the next variable:

aux-display: this variable can be repeated multiple times, and should list all of the available GraphicsPipe implementations. If Panda3D is unable to open a pipe of the type named by load-display, then it will walk through the list of pipes named by aux-display, in the order they appear in the Config.prc file, and try them one at a time until one is successfully opened.

Note that the name specified to each of the above variables, e.g. pandagl, actually names a Windows DLL or Unix shared-library file. Panda3D will put "lib" in front of the name and ".dll" or ".so" (according to the operating system) after the name, and then attempts to import that library. This means that "load-display pandagl" really means to try to import the file "libpandagl.dll". The various display DLL's are written so that when they are successfully imported, they will register support for the kind of GraphicsPipe they implement.

You can create additional graphics pipes, for instance to provide an in-game interface to switch between OpenGL and DirectX rendering. The easiest way to do this is to call base.makeAllPipes(). Then you can walk through the list of GraphicsPipes in base.pipeList to see all of the available GraphicsPipes available in particular environment.

When you walk through the GraphicsPipes in base.pipeList, you can call the following interface methods on each one:

pipe.isValid() | Returns True if the pipe is available for rendering, False if it can't be used.
pipe.getDisplayWidth() | Returns the width of the desktop, or the maximum width of any buffer for an offscreen-only GraphicsPipe.
pipe.getDisplayHeight() | Returns the height of the desktop, or the maximum height of any buffer for an offscreen-only GraphicsPipe.
pipe.getInterfaceName() | Returns the name of the API that this GraphicsPipe impements, e.g. "OpenGL" or "DirectX8".
pipe.getType() | Returns a unique TypeHandle object for each kind of pipe.


Panda3D Manual: Creating Windows and Buffers

Although Panda does provide the convenience function base.openWindow() to create a new window, this function does a lot of things automatically for you and therefore takes away a lot of control. The following discussion will focus instead on creating a window using the low-level interface, in order provide a clearer understanding of the actual class relationships.

In order to create a window, you will first need a GraphicsEngine and a GraphicsPipe object. Both of these were discussed in more detail in previous pages. Panda will typically create both of these for you at startup, and store them in base.graphicsEngine and base.pipe, respectively.

You will also need to create a FrameBufferProperties object. This defines important properties such as the number of bits you wish to allocate for red, green, and blue channels; as well as the number of bits for depth buffer; and whether you require a stencil buffer or special multisampling bits for antialiasing. Your graphics card may be able to switch itself into one of several different configurations, and you can use the FrameBufferProperties to request certain properties that are more important to you. Note, however, that there is no guarantee that the graphics card you are running on will be able to provide everything you ask for (but you can later ask what properties you actually got). You can get a default FrameBufferProperties object using FrameBufferProperties.getDefault(). The default FrameBufferProperties has its settings already filled according to the Config.prc file variables; it is usually a good choice to use.

You will need to create a WindowProperties object as well. At a minimum, this defines the X, Y size of the window or buffer you want to create. For an offscreen buffer, this is all it defines; but if you are creating a window, it also allows you to specify things like the window title, the placement onscreen, whether it should be user-resizable, and so on. You can get a default WindowProperties object using WindowProperties.getDefault(). The default WindowProperties object has its settings filled in according to Config.prc file variables. If you are creating an offscreen buffer, you may wish to use WindowProperties.size(x, y) which creates a simple WindowProperties object that simply requests a buffer of size x y.

Once you have all of these objects, you can create a new window or buffer using the call graphicsEngine.makeOutput(). This is the fundamental method for creating a new GraphicsOutput; all of the other convenience functions like base.makeWindow() or win.makeTextureBuffer() eventually funnel down into this call. This method accepts several parameters:

base.graphicsEngine.makeOutput(pipe, name, sort, fbProps, winProps, flags, gsg, host)

pipe | The GraphicsPipe to use to create this output, usually base.pipe.
name | A string name to assign to this output. Each window and buffer should have a name, which makes it easier for you to identify the object in a list. This is an internal name only, and has nothing to do with the window title displayed above the window.
sort | The sort order of this output. This determines the order in which the windows will be rendered, which is particularly important for offscreen buffers that are used to render to textures, which are in turn used in other windows or buffers.
fbProps | The FrameBufferProperties for this output. If you intend to be sharing GSG's between multiple windows or buffers, it is usually important that they also share the same FrameBufferProperties.
winProps | The WindowProperties for this output.
flags | An integer value, a union of several possible bitmask options defined by the GraphicsPipe class. This controls the type of GraphicsOutput we are requesting, for instance whether we want a window or buffer, or other exotic requirements. Set this to GraphicsPipe.BFRequireWindow if you want to create a GraphicsWindow, or to GraphicsPipe.BFRefuseWindow if youw ant to create a GraphicsBuffer. For more options, see the source code.
gsg | This parameter is optional, but if provided, it is a GSG to share with other windows or buffers. You can get the GSG from an existing window or buffer with win.getGsg(). If you omit this parameter, a new GSG will be created.
host | This parameter is optional, but if provided, it is an already-existing host window or buffer. This is useful when creating an offscreen buffer; it allows the creation of a ParasiteBuffer, if necessary, instead of a true GraphicsBuffer object. If you provide a host window, Panda will be able to return either a ParasiteBuffer or a GraphicsBuffer, according to what the graphics driver is best able to provide.

The return value of makeOutput() is either the new GraphicsWindow or GraphicsBuffer object, or None if it failed for some reason.


Panda3D Manual: Display Regions

A new window can't render anything until it has at least one DisplayRegion. A DisplayRegion is necessary to associate a camera with the window.

A DisplayRegion is a rectangular area of the window that contains the rendered scene, as viewed by one particular camera. Usually, you create just one DisplayRegion that covers the entire window, although you can create as many different smaller regions as you like, each one displaying the output from a different camera.

A single display region

Two display regions

Another arrangement of two regions

You can create a DisplayRegion with the makeDisplayRegion() call on a window:

win.makeDisplayRegion()
win.makeDisplayRegion(left, right, bottom, top)

The first example creates a DisplayRegion that fills the entire window, while the second example specifies the size and placement of the DisplayRegion within the window. The ranges of left, right, bottom, top are from 0 to 1, where 0 is the left and bottom of the window, and 1 is the right and top of the window. (Note that this is different from the range of the render2d screen coordinates, which ranges from -1 to 1 instead of 0 to 1.) For instance, the right panel of the second example above was created with the call win.makeDisplayRegion(0.5, 1, 0, 1).

A new DisplayRegion won't render anything until it has been associated with a camera. Each DisplayRegion may have just one camera associated with it (although a particular camera may be associated with more than one DisplayRegion).

A Camera is a kind of PandaNode, so you can simply create one and wrap a NodePath around it:

camNode = Camera('cam')
camNP = NodePath(camNode)
displayRegion.setCamera(camNP)

Once you have a Camera, you need to decide where to attach it to the scene graph. If you parent it to base.camera, it will inherit the transform of that node and so it will move with the system trackball controls, if enabled, and will view the scene that base.camera is attached to (usually render). The default Camera that Panda creates is attached to base.camera.

# View render, as seen by the default camera
camNP.reparentTo(base.camera)

If you wish your new Camera to view a completely different scene, you can set up a new scene graph simply by creating a new NodePath to be the root, and then reparent your Camera into that new scene graph. Then, your camera will render whatever models you put into the same scene graph. In this case, since your camera is not a child of base.camera, it will be up to you to position the camera and the models correctly relative to each other.

# View some other scene, unrelated to render
render2 = NodePath('render2')  # the string parameter is important
camNP.reparentTo(render2)
env = loader.loadModel('environment.egg')
env.reparentTo(render2)


Panda3D Manual: Clearing Display Regions

When you have more than one DisplayRegion in a particular window, it is important to consider which of them, if any, you want to perform an explicit clear operation before drawing.

Clearing means to erase the contents of the window or DisplayRegion, and set it all to the background color. Normally, you should perform a clear at the beginning of every frame, or you will be drawing the new frame on top of the contents of the previous frame.

The default is for clear to be performed by the GraphicsOutput (the window or buffer), before any DisplayRegions are drawn at all. This is usually the best way to clear the window, because it is slightly faster to perform one big clear operation that resets the whole window at once, rather than clearing each DisplayRegion individually. This particularly makes sense when your DisplayRegions don't overlap, and they all want to have the same background color, like this:

However, when your DisplayRegions overlap, or when they each need to have a different background color, you may need to clear the DisplayRegions individually. Consider the following example:

Panda must draw this scene by first clearing the window to a gray background, then drawing the contents of the larger display region (with the panda), then clearing the smaller, nested display region to a black background, and then finally drawing the contents of the smaller display region. (Click here to see the sample code that sets all this up.)

You can control the clear operations per DisplayRegion, as well as on the overall GraphicsOutput. The following methods are defined for both DisplayRegions and GraphicsOutputs:

win.setClearColorActive(flag)
win.setClearColor(VBase4(r, g, b, a))

In the above, flag is a boolean flag--True or False, indicating whether this window or DisplayRegion should perform a clear to the background color. If False, no clear will be performed. If True, the color will be cleared before drawing, and the specific background color used will be specified by r, g, b, a.

In addition to clearing the color, you will also need to clear the depth or Z buffer. This buffer is used to determine which objects are in front of other objects, and if you fail to clear it, some objects may not draw. It has a similar interface:

win.setClearDepthActive(flag)
win.setClearDepth(depthValue)

The depthValue should almost always be 1.0, which is the default.

It is also possible to selectively clear the stencil buffer, and other auxiliary buffers, in a similar way. See the generated API docs for more information.

Sorting

Note that when you are overlapping DisplayRegions, it becomes very important to specify the order in which the DisplayRegions should be drawn. To do this, use setSort():

dr.setSort(sortValue)

The sortValue can be any integer number; the default is zero. All DisplayRegions for a particular window will be drawn in order from smallest sort first to largest sort last. If two DisplayRegions have the same sort value, the order in which they are drawn is undefined.


Panda3D Manual: The 2D Display Region

There is one more DisplayRegion that Panda normally creates automatically for the main window. This is the 2-D DisplayRegion that renders the onscreen GUI or heads-up display. It is simply another DisplayRegion that covers the entire screen, like the 3-D DisplayRegion it layers on top of, except that its camera has an Orthographic Lens instead of a normal Perspective Lens.

This is the DisplayRegion associated with render2d, and is normally used to render all of the gui elements and onscreen text items you may lay on top of the screen.

If you are creating a secondary window or buffer, and you would like to layer 2-D elements on top of the screen, you can do so by simply creating a 2-D scene similar to render2d. Some sample code to do so is shown here:

dr = win.makeDisplayRegion()
dr.setSort(20)

myCamera2d = NodePath(Camera('myCam2d'))
lens = OrthographicLens()
lens.setFilmSize(2, 2)
lens.setNearFar(-1000, 1000)
myCamera2d.node().setLens(lens)

myRender2d = NodePath('myRender2d')
myRender2d.setDepthTest(False)
myRender2d.setDepthWrite(False)
myCamera2d.reparentTo(myRender2d)
dr.setCamera(myCamera2d)

The first group of commands creates a new DisplayRegion on the window and sets its sort value to 20, so that it will be drawn after the main DisplayRegion has been drawn. This is important in order to layer text on top of the 3-D scene, of course.

The second group of commands creates a camera with an OrthographicLens. The lens is created with a wide near/far clipping plane: -1000 to 1000. This probably doesn't matter too much since we expect that everything we parent to this scene graph will have a Y value of 0 (which is easily between -1000 and 1000), but this allows us to accept a wide range of Y values.

The third group of commands sets up the myRender2d scene graph. It is just an ordinary node, with a few properties set on it, and the 2-D camera we have just created attached to it. We turn off the depth test and depth write properties because these are not important for a 2-D scene graph, and we don't want them to get in the way of our gui elements.

DirectGui in your new window

Note that if you wish to create any DirectGui elements, like buttons or other clickable widgets, in the new 2-D scene graph, and interact with them, you have just a bit more set-up to do. DirectGui has a special mechanism to connect it to the mouse pointer, which requires that all of its interactive objects be attached directly or indirectly to a PGTop node. In the default main window, this PGTop node is aspect2d, a special node created both to compensate for the non-square aspect ratio of the window, and also to be the special PGTop node required by DirectGui. If you are creating your own 2-D scene graph, you can create your own aspect2d node something like this:

aspectRatio = base.getAspectRatio()
myAspect2d = myRender2d.attachNewNode(PGTop('myAspect2d'))
myAspect2d.setScale(1.0 / aspectRatio, 1.0, 1.0)
myAspect2d.node().setMouseWatcher(base.mouseWatcherNode)

If this is for a different window than base.win, you will probably need to also create your own MouseWatcher, other than base.mouseWatcherNode, to manage the mouse associated with your new window. See elsewhere for more information about this.


Panda3D Manual: The Graphics Engine

The graphics engine is the heart of the rendering process. The GraphicsEngine class is ultimately responsible for all of the drawing and culling operations per frame.

Normally, there is no need to create a GraphicsEngine, as Panda3D will create one for you at startup. This default GraphicsEngine is stored in base.graphicsEngine.

Note also that the following interfaces are strictly for the advanced user. Normally, if you want to create a new window or an offscreen buffer for rendering, you would just use the base.openWindow() or window.makeTextureBuffer() interfaces, which handle all of the details for you automatically.

However, please continue reading if you want to understand in detail how Panda manages windows and buffers, or if you have special needs that are not addressed by the above convenience methods.

Rendering a frame

There is one key interface to rendering each frame of the graphics simulation:

base.graphicsEngine.renderFrame()

This method causes all open GraphicsWindows and GraphicsBuffers to render their contents for the current frame.

In order for Panda3D to render anything, this method must be called once per frame. Normally, this is done automatically by the task "igloop", which is created when you start Panda.

Using a GraphicsEngine to create windows and buffers

In order to render in Panda3D, you need a GraphicsStateGuardian, and either a GraphicsWindow (for rendering into a window) or a GraphicsBuffer (for rendering offscreen). You cannot create or destroy these objects directly; instead, you must use interfaces on the GraphicsEngine to create them.

Before you can create either of the above, you need to have a GraphicsPipe, which specifies the particular graphics API you want to use (e.g. OpenGL or DirectX). The default GraphicsPipe specified in your Config.prc file has already been created at startup, and can be accessed by base.pipe.

Now that you have a GraphicsPipe and a GraphicsEngine, you can create a GraphicsStateGuardian object. This object corresponds to a single graphics context on the graphics API, e.g. a single OpenGL context. (The context owns all of the OpenGL or DirectX objects like display lists, vertex buffers, and texture objects.) You need to have at least one GraphicsStateGuardian before you can create a GraphicsWindow:

myGsg=base.graphicsEngine.makeGsg(base.pipe)

Now that you have a GraphicsStateGuardian, you can use it to create an onscreen GraphicsWindow or an offscreen GraphicsBuffer:

base.graphicsEngine.makeWindow(gsg, name, sort)
base.graphicsEngine.makeBuffer(gsg, name, sort, xSize, ySize, wantTexture)

gsg is the GraphicsStateGuardian, name is an arbitrary name you want to assign to the window/buffer, and sort is an integer that determines the order in which the windows/buffers will be rendered.

The buffer specific arguments xSize and ySize decide the dimensions of the buffer, and wantTexture should be set to True if you want to retrieve a texture from this buffer later on.

You can also use graphicsEngine.makeParasite(host,name,sort,xSize,ySize), where host is a GraphicsOutput object. It creates a buffer but it does not allocate room for itself. Instead it renders to the framebuffer of host. It effectively has wantTexture set to True so you can retrieve a texture from it later on.

See The GraphicsOutput class and Graphics Buffers and Windows for more information.

myWindow=base.graphicsEngine.makeWindow(myGsg, "Hello World", 0)
myBuffer=base.graphicsEngine.makeBuffer(myGsg, "Hi World", 0, 800,600, True)
myParasite=base.graphicsEngine.makeBuffer(myBuffer,"Im a leech", 0, 800, 600)

Note: if you want the buffers to be visible add show-buffers true to your configuration file. This causes the buffers to be opened as windows instead, which is useful while debugging.

Sharing graphics contexts

It is possible to share the same GraphicsStateGuardian among multiple different GraphicsWindows and/or GraphicsBuffers; if you do this, then the graphics context will be used to render into each window one at a time. This is particularly useful if the different windows will be rendering many of the same objects, since then the same texture objects and vertex buffers can be shared between different windows.

It is also possible to use a different GraphicsStateGuardian for each different window. This means that if a particular texture is to be rendered in each window, it will have to be loaded into graphics memory twice, once in each context, which may be wasteful. However, there are times when this may be what you want to do, for instance if you have multiple graphics cards and you want to to render to both of them simultaneously. (Note that the actual support for simultaneously rendering to multiple graphics cards is currently unfinished in Panda at the time of this writing, but the API has been designed with this future path in mind.)

Closing windows

To close a specific window or buffer you use removeWindow(window). To close all windows removeAllWindows()

base.graphicsEngine.removeWindow(myWindow)
base.graphicsEngine.removeAllWindows()

More about GraphicsEngine

Here is some other useful functionality of the GraphicsEngine class.

getNumWindows() | Returns the number of windows and buffers that this GraphicsEngine object is managing.
isEmpty() | Returns True if this GraphicsEngine is not managing any windows or buffers.

See API for advanced functionality of GraphicsEngine and GraphicsStateGuardian class.


Panda3D Manual: The GraphicsOutput class

Buffers and windows, encapsulated in the GraphicsBuffer and GraphicsWindow classes are almost interchangable in Panda. In fact most operations in the GraphicsEngine class are defined on and return GraphicsOutput objects, the class that both GraphicsBuffer and GraphicsWindow inherit from. Therefore, we will discuss the properties of GraphicsOutput objects first.

The first very important note is that none of these classes are not meant to be constructed directly, i.e.:

myOutput=GraphicsOutput()
myWindow=GraphicsWindow()
myBuffer=GraphicsBuffer()

will not work. Refer to The Graphics Engine for how to create these objects. Furthermore, since GraphicsOutput is an abstract class, GraphicsWindow objects will be used in code examples.

All GraphicsOutput objects have getGsg(), getPipe(), and getName() which return respectively their GraphicsStateGuardian, GraphicsPipe, and name. You can also get the width and height using getXSize() and getYSize().

from pandac.PandaModules import GraphicsWindow

#assume we already have a window setup and in myWindow
myWindowGsg=myWindow.getGsg()
myWindowPipe=myWindow.getPipe()
myWindowName=myWindow.getName()

myWindowWidth=myWindow.getXSize()
myWindowLength=myWindow.getYSize()

You can also save a screenshot from any GraphicsOutput by using saveScreenShot(fileName), where fileName is the name of the picture(the format of the picture is specified by the extension of filename). Returns True upon succes and False otherwise. The picture is saved in the directory of the script you are running.

from pandac.PandaModules import Filename
myWindow.saveScreenShot(Filename('hello.bmp'))

This naturally flows into rendering into a texture. We'll start with copying a scene. If you want to get a texture that simply copies what's in its GraphicsOutput object, you must first make a call to setupCopyTexture(). You can then get the texture by using getTexture(). You can now apply the texture to a NodePath as you would a texture loaded from memory. Thanks to the magic of pointers, the texture automatically updates itself if the contents of its GraphicsOutput change. If you do not want this behaviour you should use detachTexture() when you no longer want the textute to be updated. However, since the first frame is always blank, the best way to use detachTexture() is in a do-later task or event.

myWindow.setupCopyTexture()
myTexture=myWindow.getTexture()

#assume myModel is already setup
myModel.setTexture(myTexture)

#and if you want to stop the texture from updating itself
def stopUpdating():
   global myWindow
   myWindow.detachTexture()
taskMgr.doMethodLater(1,stopUpdating,'stops updating')

While this is helpful, you may want to render an entirely new scene into a GraphicsOutput and then place it on screen (i.e. you have a televsion in your main scene and want to generate the show on the spot). The first thing you do is create a GraphicsOutput to hold the scene. You do this by calling makeTextureBuffer. It makes a GraphicsOutput specifically for rendering a scene and then retrieving it by getTexture().

makeTextureBuffer(name, xSize, ySize)

The arguments name, xSize, and ySize mean the same things they do for makeWindow and makeBuffer.

You then have to create a new camera for the new scene, using

base.makeCamera(win, sort=0, scene=None,
  displayRegion=(0,1,0,1), aspectRatio=None, camName='cam')

Here's a break down of what the parameters mean:

win | The GraphicsOutput object that you want to make the camera for
sort | The sort value of the camera. Decides the order in which DisplayRegions in the same window are drawn. See API for more information.
scene | Due to deprecation of other functions this parameter does not affect anything.
displayRegion | The area of the new GraphicsOutput that you want to cover in the form (left start point, right end point, bottom start point, top end point). (0,0) represent the bottom left of the screen and (1,1) represents the top right. Therefore (0,1,0,1) represents the entire area. Arguments must be between 0 and 1.
aspectRatio | The aspectRatio of the GraphicsOutput. When this is left to None makeCamera uses the aspectRatio of the default window.
camName | The name of the node that represents this camera in the scene graph

Cameras render whatever is connected to their ancestors in the scene graph. Therefore if you want a truly independent scene you have to start a new scene graph. Create a dummy NodePath and now reparentTo the new camera to this node. Now you can treat the new scene and the new camera like you would render and your scene gets drawn to your GraphicsOuptut.

However, any state changes you make to the NodePath camera will no longer affect your new camera. Also, since the standard mouse controls work on the camera NodePath, these will not work either. You can alternatively use the Camera class method setScene(scenePath), where scenePath is the top of the scene graph you want to draw. This preserves the standard heirarchy stated in Camera Control.

# I use a GraphicsBuffer only because this is a process you
# probably want the user to see
myBuffer=myWindow.makeTextureBuffer("Another Scene", 800,600)

# You must pass a string to the NodePath constructor or
# attempts to set it as a parent will remove the child from the graph
myNewScene=NodePath("myRender")
myNewCamera=base.makeCamera(myBuffer)
myNewCamera.reparentTo(myNewScene)
#or myNewCamera.node().setScene(myNewScene)

#You can now get a texture that represents anything
# you do in this new scene (that is still automatically updated)
myTexture=myBuffer.getTexture()


Panda3D Manual: Graphics Buffers and Windows

We'll now describe in detail what functions are specific to buffers and windows in Panda.

GraphicsBuffer and ParasiteBuffer

Use these if you want to do off-screen rendering. You must pass True when you create it if you want to get a texture from it. Otherwise, there is no difference in functionality than a GraphicsOutput.

The only difference between a GraphicsBuffer and a ParasiteBuffer is that a ParasiteBuffer does not create it own framebuffer space. To create a ParasiteBuffer you call makeParasite() from the graphics engine.

makeParasite(host, name,sort, xSize, ySize)

The arguments name, sort, xSize, and ySize mean the same things they mean for makeWindow and makeBuffer. The new argument host is the GraphicsOutput object whose space in memory it will use. Any rendering done to the parasite is done to the same space in memory as its host. The function makeTextureBuffer sometimes returns a ParasiteBuffer for space saving reasons. It is also useful for APIs that don't support offscreen rendering.

ParasiteBuffer objects are automatically setup for calls to getTexture() since their contents get cleared when host draws itself.

GraphicsWindows

Unlike GraphicsBuffer objects GraphicsWindow objects have a lot more functionality than GraphicsOutput objects.

The most basic of these functions is hasKeyboard() and hasPointer() which returns whether or not this window has the focus of keyboard and pointer respectively. Any calls to keyboard or pointer functions when you do not have control of them generates an error.

You can get the number of input devices for this window by using getNumInputDevices(). In the absence of a joystick, etc. there is usually only one input device, the 'keyboard/mouse' device. If the API you are using supports it, you can move a mouse to a certain place in the window by using movePointer(device, x,y) where device is the name of the device that holds the mouse (most probabaly 'keyboard/mouse') and x and y represent the screen position where you want to move the pointer. It returns True if it was succesful, and False otherwise.

You can also ask a window if it isFullscreen() and if it isClosed(). It is important to note that a window is not automatically opened after a call to makeWindow and is not automatically closed after a call to closeWindow.

In order to get the full set of properties for a given window you use the function getProperties(). This returns a WindowProperties object that holds all the information for the given screen. See the API documentation for the full functionality of the WindowProperties class.

If you want to change these properties, use getRequestedProperties() and apply the proper WindowProperties functions.

To run panda3d in full screen, rather than a window, do the following:

wp = WindowProperties()
wp.setFullscreen(True)
base.win.requestProperties(wp)

Alternatively, you can modify the fullscreen configuration variable before importing direct.directbase.DirectStart:

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", """fullscreen 1
win-size 1024 768""")

from direct.showbase.DirectObject import DirectObject   # for event handling
import direct.directbase.DirectStart
import sys

class World(DirectObject):
   def __init__(self):
      self.accept("escape",sys.exit)

w= World()
run()

If a requested change is not possible or invalid, you can call getRejectedProperties(). It returns a WindowProperties object that holds all of the properties that could not be changed.

Windows can also send Events when the user changes a property of the window. You can get the name of this event by calling getWindowEvent(). Initially, all windows send the same event when changed. If you want to setup events for a certain window, use setWindowEvent(name) where name is the name of the event you want sent when this window gets changed externally.

For more advanced functionality see GraphicsWindow in the API documentation.


Panda3D Manual: Multi-Pass Rendering

Multi-Pass Rendering

Sometimes you may need to draw the same scene more than once per frame, each view looking different. This is where multi-pass rendering comes into play.

The easiest way to do implement multi-pass rendering is to use the method mentioned near the bottom in The GraphicsOutput class. You:

  1. setup a GraphicsBuffer object
  2. create a camera for it and
  3. place the camera in the scene.

However, this method assumes you have two independent scene graphs. If you use this method to render the same scene graph, it is only useful for showing the scene from a different camera view. To actually make the scenes have different RenderStates (i.e. one without lighting, one with lighting) you must also change how each Camera renders the scene.

Each Camera node has a function called setInitialState(state). It makes every object in the scene get drawn as if the top node in its scene graph has state as its RenderState. This still means that attributes can be changed/overriden after the Camera has been put on a scene.

#this makes everything drawn by the default camera use myNodePath's RenderState
base.cam.setInitialState(myNodePath.getState())

You may, however, want more control over what RenderState gets assigned to each node in the scene. You can do this using the Camera class methods setTagStateKey(key) and setTagState(value, state). For any NodePaths that you want to recieve special treatment you call setTag(key, value)(See Common State Changes). Now, anytime the Camera sees a NodePath with a tag named key the Camera assigns it whatever RenderState is associated with value.

#Assume we have CgShaderAttrib instances toonShadingAttrib and blurShadingAttrib
#and we have a Camera whose NodePath is myCamera

base.cam.node().setTagStateKey("Toon Shading")
base.cam.node().setTagState("True", RenderState.make(toonShadingAttrib))

myCamera.node().setTagStateKey("Blur Shading")
myCamera.node().setTagState("True", RenderState.make(blurShadingAttrib))

# this makes myNodePath and its children get toonShaded
# when rendered by the default camera
myNodePath.setTag("Toon Shading", "True")
....
#now if you want myNodePath to be blurred when seen by myCamera,
#it's as easy as adding a tag
myNodePath.setTag("Blur Shading", "True")

For a full guide about Multi-Pass rendering in Panda3D, please read the Howto on Multipass Rendering of the original Panda3D documentation.


Panda3D Manual: How to Control Render Order

How to Control Render Order

In most simple scenes, you can naively attach geometry to the scene graph and let Panda decide the order in which objects should be rendered. Generally, it will do a good enough job, but there are occasions in which it is necessary to step in and take control of the process.

To do this well, you need to understand the implications of render order. In a typical OpenGL- or DirectX-style Z-buffered system, the order in which primitives are sent to the graphics hardware is theoretically unimportant, but in practice there are many important reasons for rendering one object before another.

Firstly, state sorting is one important optimization. This means choosing to render things that have similar state (texture, color, etc.) all at the same time, to minimize the number of times the graphics hardware has to be told to change state in a particular frame. This sort of optimization is particularly important for very high-end graphics hardware, which achieves its advertised theoretical polygon throughput only in the absence of any state changes; for many such advanced cards, each state change request will completely flush the register cache and force a restart of the pipeline.

Secondly, some hardware has a different optimization requirement, and may benefit from drawing nearer things before farther things, so that the Z-buffer algorithm can effectively short-circuit some of the advanced shading features in the graphics card for pixels that would be obscured anyway. This sort of hardware will draw things fastest when the scene is sorted in order from the nearest object to the farthest object, or "front-to-back" ordering.

Finally, regardless of the rendering optimizations described above, a particular sorting order is required to render transparency properly (in the absence of the specialized transparency support that only a few graphics cards provide). Transparent and semitransparent objects are normally rendered by blending their semitransparent parts with what has already been drawn to the framebuffer, which means that it is important that everything that will appear behind a semitransparent object must have already been drawn before the semitransparent parts of the occluding object is drawn. This implies that all semitransparent objects must be drawn in order from farthest away to nearest, or in "back-to-front" ordering, and furthermore that the opaque objects should all be drawn before any of the semitransparent objects.

Panda achieves these sometimes conflicting sorting requirements through the use of bins.

Cull Bins

The CullBinManager is a global object that maintains a list of all of the cull bins in the world, and their properties. Initially, there are five default bins, and they will be rendered in the following order:

   Bin Name        Sort  Type
   --------------  ----  ----------------
   "background"     10   BT_fixed
   "opaque"         20   BT_state_sorted
   "transparent"    30   BT_back_to_front
   "fixed"          40   BT_fixed
   "unsorted"       50   BT_unsorted

When Panda traverses the scene graph each frame for rendering, it assigns each Geom it encounters into one of the bins defined in the CullBinManager. (The above lists only the default bins. Additional bins may be created as needed, using either the CullBinManager::add_bin() method, or the Config.prc "cull-bin" variable.)

You may assign a node or nodes to an explicit bin using the NodePath::set_bin() interface. set_bin() requires two parameters, the bin name and an integer sort parameter; the sort parameter is only meaningful if the bin type is BT_fixed (more on this below), but it must always be specified regardless.

If a node is not explicitly assigned to a particular bin, then Panda will assign it into either the "opaque" or the "transparent" bin, according to whether it has transparency enabled or not. (Note that the reverse is not true: explicitly assigning an object into the "transparent" bin does not automatically enable transparency for the object.)

When the entire scene has been traversed and all objects have been assigned to bins, then the bins are rendered in order according to their sort parameter. Within each bin, the contents are sorted according to the bin type.

If you want simple geometry that's in back of something to render in front of something that it logically shouldn't, add the following code to the model that you want in front:

model.setBin("fixed", 40)
model.setDepthTest(False)
model.setDepthWrite(False)

The above code will only work for simple models. If your model self occludes (parts of the model covers other parts of the model), the code will not work as expected. An alternative method is to use a display region with displayRegion.clearDepthActive(True).

The following bin types may be specified:

 BT_fixed

   Render all of the objects in the bin in a fixed order specified by
   the user.  This is according to the second parameter of the
   NodePath::set_bin() method; objects with a lower value are drawn
   first.

 BT_state_sorted

   Collects together objects that share similar state and renders
   them together, in an attempt to minimize state transitions in the
   scene.

 BT_back_to_front

   Sorts each Geom according to the center of its bounding volume, in
   linear distance from the camera plane, so that farther objects are
   drawn first.  That is, in Panda's default right-handed Z-up
   coordinate system, objects with large positive Y are drawn before
   objects with smaller positive Y.

 BT_front_to_back

   The reverse of back_to_front, this sorts so that nearer objects
   are drawn first.

 BT_unsorted

   Objects are drawn in the order in which they appear in the scene
   graph, in a depth-first traversal from top to bottom and then from
   left to right.


Panda3D Manual: Panda Utility Functions

Panda3D has a set of utilities that may be used to learn more about various objects and methods within an application. To access these utilities you need to import the PythonUtil module as follows.

from direct.showbase.PythonUtil import *

The * can be replaced by any of the utility functions in that module.

To get a detailed listing of a class or an object's attributes and methods, use the pdir() command. pdir() prints the information out to the command console. pdir() can take many arguments for formatting the output but the easiest way to use it is to provide it a NodePath.

pdir() will list all of the functions of the class of NodePath including those of its base classes

pdir(NodePath)
e.g. pdir(camera)

There are many other useful functions in the PythonUtil module. All of these are not necessarily Panda specific, but utility functions for python. There are random number generators, random number generator in a gaussian distribution curve, quadratic equation solver, various list functions, useful angle functions etc. A full list can be found in the API.

An alternative command to pdir is inspect(). This command will create a window with methods and attributes on one side, and the details of a selected attribute on the other. inspect() also displays the current values of a class’ attributes. If these attributes are changing, you may have to click on a value to refresh it. To use inspect() you have to do the following:

from direct.tkpanels.inspector import inspect
inspect(NodePath)

E.g. inspect(camera)

While the directtools suite calls upon a number of tools, if the suite is disabled, the user may activate certain panels of the suite. The place() command opens the object placer console. The explore() opens the scene graph explorer, which allows you to inspect the hierarchy of a NodePath. Finally, in order to change the color of a NodePath, the rgbPanel() command opens color panel.

camera.place()
render.explore()
panda.rgbPanel()

Useful DirectTool panels are explained in the Panda Tools section.


Panda3D Manual: Particle Effects

Particle effects involve the use of several small images acting on the same set of forces. These particles are created, they move, and they die out. These systems are dynamic, and may be used for such effects as fireworks, bubbling cauldrons, and even swarms of balloons.

In essence, any particle effect needs three key parts: the renderer, the emitter, and the factory. The renderer translates the particle object into a visible object on the screen. The emitter assigns initial locations and velocity vectors for the particles. The factory generates particles and assigns their attributes. There are many different types of each part, and they each have their own parameters.

Creating your own particle effects using code alone may be difficult. A particle effect panel is available to ease through this process. This section will discuss using the particle panel and the large number of variables associated with particle effects.


Panda3D Manual: Using the Particle Panel

Creating the Particle System

The particle panel can be invoked by running the particle panel sample program.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Loading Particle Systems

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Particle Effect Basic Parameters

Every particle effect needs at least eleven parameters. These govern the overall properties, such as the number of particles on the screen, the birth and death rates, and the renderer, emitter, and factory that are used.

Variable | Definition | Values
poolSize | Maximum number of simultaneous particles | [0, infinity)
birthRate | Seconds between particle births | (0, infinity)
litterSize | Number of particles created at each birth | [1, infinity)
litterSpread | Variation of litter size | [0, infinity)
localVelocityFlag | Whether or not velocities are absolute | Boolean
systemGrowsOlder | Whether or not the system has a lifespan | Boolean
systemLifespan | Age of the system in seconds | [0, infinity)
BaseParticleRenderer* renderer | Pointer to particle renderer | Renderer type
BaseParticleRenderer* emitter | Pointer to particle emitter | Emitter type
BaseParticleRenderer* factory | Pointer to particle factory | Factory type

The renderer, emitter, and factory types will be discussed in the next three sections.


Panda3D Manual: Particle Factories

There are two types of particle factories, Point and ZSpin. The particle panel shows a third, Oriented, but this factory does not currently work. The differences between these factories lie in the orientation and rotational abilities.

First, there are some common variables to the factories.

Variable | Definition | Values
lifespanBase | Average lifespan in seconds | [0, infinity)
lifespanSpread | Variation in lifespan | [0, infinity)
massBase | Average particle mass | [0, infinity)
massSpread | Variation in particle mass | [0, infinity)
terminalVelocityBase | Average particle terminal velocity | [0, infinity)
terminalVelocitySpread | Variation in terminal velocity | [0, infinity)

Point particle factories generate simple particles. They have no additional parameters.

ZSpin particle factories generate particles that spin around the Z axis, the vertical axis in Panda3D. They have some additional parameters.

Variable | Definition | Values
initialAngle | Starting angle in degrees | [0, 360]
initialAngleSpread | Spread of initial angle | [0, 360]
finalAngle | Final angle in degrees | [0, 360]
fnalAngleSpread | Spread of final angle | [0, 360]


Panda3D Manual: Particle Emitters

There are a large number of particle emitters, each categorized by the volume of space they represent. Additionally, all emitters have three modes: explicit, radiate, and custom. Explicit mode emits the particles in parallel in the same direction. Radiate mode emits particles away from a specific point. Custom mode emits particles with a velocity determined by the particular emitter.

All emitters have a number of common parameters.

Variable | Definition | Values
emissionType | Emission mode | ET_EXPLICIT, ET_RADIATE, ET_CUSTOM
explicitLaunchVector | Initial velocity in explicit mode | (x, y, z)
radiateOrigin | Point particles launch away from in radiate mode | (x, y, z)
amplitude | Launch velocity multiplier | (-infinity, infinity)
amplitudeSpeed | Spread for launch velocity multiplier | [0, infinity)

The following list contains the different types of emitters, their unique parameters, and the effect of the custom mode.

BoxEmitter

Variable | Definition | Values
minBound | Minimum point for box volume | (x, y, z)
maxBound | Maximum point for box volume | (x, y, z)

Custom mode generates particles with no initial velocity.

DiscEmitter

Variable | Definition | Values
radius | Radius of disc | [0, infinity)
outerAngle | Particle launch angle at edge of disc | [0, 360]
innterAngle | Particle launch angle at center of disc | [0, 360]
outerMagnitude | Launch velocity multiplier at edge of disc | (-infinity, infinity)
innerMagnitude | Launch velocity multiplier at center of disc | (-infinity, infinity)
cubicLerping | Whether or not magnitude/angle interpolation is cubic | Boolean

Custom mode uses the last five parameters. Particles emitted from areas on the inside use interpolated magnitudes and angles, either liner or cubic.

PointEmitter

Variable | Definition | Values
location | Location of outer point | (x, y, z)

Custom mode generates particles with no initial velocity.

RectangleEmitter

Variable | Definition | Values
minBound | 2D point defining the rectangle | (x, z)
maxBound | 2D point defining the rectangle | (x, z)

Custom mode generates particles with no initial velocity.

RingEmitter

Variable | Definition | Values
radius | Radius of disc | [0, infinity)
angle | Particle launch angle | [0, 360]

Custom mode uses the second parameter to emit particles at an angle with respect to the vector from the ring center to the spawn point. 0 degrees emits particles away from the center, and 180 degrees emits particles into the center.

SphereSurfaceEmitter

Variable | Definition | Values
radius | Radius of sphere | [0, infinity)

Custom mode generates particles with no initial velocity.

SphereVolumeEmitter

Variable | Definition | Values
radius | Radius of sphere | [0, infinity)

Custom mode emits particles away from the sphere center. Their velocity is dependent on their spawn location within the sphere. It is 0 at the center, of magnitude 1 at the outer edge of the sphere, and linearly interpolated in between.

TangentRingEmitter

Variable | Definition | Values
radius | Radius of ring | [0, infinity)

Custom mode emits particles tangentially to the ring edge, with a velocity magnitude of 1.


Panda3D Manual: Particle Renderers

Particle renderers add particles to the visible scene graph according to the information stored in the particle objects and the type of renderer. All particle renderers have the following parameters:

Variable | Definition | Values
alphaMode | Alpha setting over particle lifetime | PR_ALPHA_NONE, PR_ALPHA_OUT, PR_ALPHA_IN, PR_ALPHA_USER
userAlpha | Alpha value for ALPHA_USER alpha mode | Boolean

The following list contains the different types of renderers and their unique parameters.

PointParticleRenderer

Renders particles as pixel points.

Variable | Definition | Values
pointSize | Width and height of points, in pixels | [0, infinity)
startColor | Starting color | (r, g, b, a)
endColor | Ending color | (r, g, b, a)
blendType | How the particles blend from the start color to the end color | ONE_COLOR, BLEND_LIFE, BLEND_VEL
blendMethod | Interpolation method between colors | LINEAR, CUBIC

ONE_COLOR: point is always the starting color.
BLEND_LIFE: color is interpolated from start to end according to the age of the point
BLEND_VEL: color is interpolated between start to end according to the velocity/terminal velocity.

LineParticleRenderer

Renders particles as lines between their current position and their last position.

Variable | Definition | Values
headColor | Color of leading end | (r, g, b, a)
tailColor | Color of trailing end | (r, g, b, a)

SparkleParticleRenderer

Renders particles star or sparkle objects, three equal-length perpendicular axial lines, much like jacks. Sparkle particles appear to sparkle when viewed as being smaller than a pixel.

Variable | Definition | Values
centerColor | Color of center | (r, g, b, a)
edgeColor | Color of edge | (r, g, b, a)
birthRadius | Initial sparkle radius | [0, infinity)
deathRadius | Final sparkle radius | [0, infinity)
lifeScale | Whether or not sparkle is always of radius birthRadius | NO_SCALE, SCALE

SpriteParticleRenderer

Renders particles as an image, using a Panda3D texture object. The image is always facing the user.

Variable | Definition | Values
texture | Panda texture object to use as the sprite image | (r, g, b, a)
color | Color | (r, g, b, a)
xScaleFlag | If true, x scale is interpolated over particle’s life | Boolean
yScaleFlag | If true, y scale is interpolated over particle’s life | Boolean
animAngleFlag | If true, particles are set to spin on the Z axis | Boolean
initial_X_Scale | Initial x scaling factor | [0, infinity)
final_X_Scale | Final x scaling factor | [0, infinity)
initial_Y_Scale | Initial y scaling factor | [0, infinity)
final_Y_Scale | Final y scaling factor | [0, infinity)
nonAnimatedTheta | If false, sets the counterclockwise Z rotation of all sprites, in degrees | Boolean
alphaBlendMethod | Sets the interpolation blend method | LINEAR, CUBIC
alphaDisable | If true, alpha blending is disabled | Boolean

GeomParticleRenderer

Renders particles as full 3D objects. This requires a geometry node.

Variable | Definition | Values
geomNode | A geometry scene graph node | <Node>


Panda3D Manual: Collision Detection

Collision detection allows for two objects to bump into each other and react. This includes not only sending messages for events, but also to keep the objects from passing through each other. Collision detection is a very powerful tool for immersion, but it is somewhat complex.

There are two ways to go about collision detection. One is to create special collision geometry, such as spheres and polygons, to determine collisions. The other is to allow collisions against all geometry. While the first is somewhat more complex and takes more effort to implement, it is much faster to execute and is a better long-term solution. For quick-and-dirty applications, though, collision with geometry can be a fine solution.

This section of the manual will address both methods.


Panda3D Manual: Collision Solids

The CollisionSolid is the fundamental object of the collision system. CollisionSolids represent special invisible geometry that is created solely for the purpose of performing collision tests; these CollisionSolids are stored in the scene graph alongside the normal visible geometry.

The CollisionSolids are specifically optimized for performing collision tests quickly. Collisions can be performed against visible geometry as well, but this is more expensive since visible geometry is not optimized for this sort of thing.

You can create CollisionSolids interactively in program code, or you can construct them in your modeling package and load them up from an egg or bam file along with the rest of your scene.

When you create a CollisionSolid interactively, you must also create a CollisionNode to hold the solid. (When you load your CollisionSolids in from an egg file, the CollisionNodes are created for you.) Often, a CollisionNode will be used to hold only one solid, but in fact a CollisionNode can contain any number of solids, and this is sometimes a useful optimization, especially if you have several solids that always move together as a unit.

cs = CollisionSphere(0, 0, 0, 1)
cnodePath = avatar.attachNewNode(CollisionNode('cnode'))
cnodePath.node().addSolid(cs)

CollisionNodes are hidden by default, but they may be shown for debugging purposes:

cnodePath.show()

Note: Be aware that the collision algorithm has only limited awareness of scaling transforms applied to CollisionSolids. If unequal scaling is applied between a from collider and an into collider, unexpected results may occur. In general, strive to have as few scaling transforms applied to your collision solids as possible.

There are several kinds of CollisionSolids available.

CollisionSphere

The sphere is the workhorse of the collision system. Spheres are the fastest primitives for any collision calculation; and the sphere calculation is particularly robust. If your object is even vaguely spherical, consider wrapping a sphere around it.

Also, a sphere is a particularly good choice for use as a "from object", because a sphere can reliably be tested for collision with most of the other solid types. The "from objects" are the objects that are considered the active objects in the world; see Collision Traversers. A sphere is usually the best choice to put around the player's avatar, for instance. The sphere also makes a good "into object"; it is the only object type that is a good choice for both "from" and "into" objects.

A sphere is defined in terms of a center and a radius. Note that, like any object, the sphere's coordinates are defined in the sphere's own coordinate space, so that often the center is (0, 0, 0).

sphere = CollisionSphere(cx, cy, cz, radius)

CollisionTube

A "tube" is a cylinder with hemispherical endcaps. This shape is sometimes called a capsule in other collision systems.

The tube is good as an "into object", for objects that are largely cylindrical. It is not a very good choice for a "from object", because not many intersection tests have been written from tubes into other shapes.

A tube is defined with its two endpoints, and the cylindrical radius.

tube = CollisionTube(ax, ay, az, bx, by, bz, radius)

CollisionInvSphere

The inverse sphere is a special-purpose solid that is rarely used, but occasionally it is very useful. It is an inside-out sphere: the solid part of the sphere is on the outside. Any object that is on the outside of the sphere is considered to be colliding with it; any object on the inside is not colliding.

Think of the inverse sphere as a solid mass that fills the whole universe in all directions, except for a bubble of space in the middle. It's useful for constraining an object within a particular space, since nothing can get out of an inverse sphere.

inv = CollisionInvSphere(cx, cy, cz, radius)

CollisionPlane

The CollisionPlane is an infinite plane extending in all directions. It is not often used, but it can be useful in certain cases, for instance as a trigger placed below the ground to detect when an avatar has accidentally slipped through a crack in the world. You can also build a box out of six planes to keep objects perfectly constrained within a rectangular region, similar to an inverse sphere; such a box is much more reliable than one constructed of six polygons.

The plane actually divides the universe into two spaces: the space behind the plane, which is all considered solid, and the space in front of the plane, which is all empty. Thus, if an object is anywhere behind a plane, no matter how far, it is considered to be intersecting the plane.

A CollisionPlane is constructed using a Panda3D Plane object, which itself has a number of constructors, including the A, B, C, D plane equation, or a list of three points, or a point and a normal.

plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))

CollisionPolygon

A CollisionPolygon is the most general of the collision solids, since it is easy to model any shape with polygons (especially using a modeling package). However, it is also the most expensive solid, and the least robust--there may be numerical inaccuracies with polygons that allow collisions to slip through where they shouldn't.

Like a plane and a tube, a CollisionPolygon is only a good choice as an "into object". It doesn't support collision tests as a "from object".

In general, if you must use CollisionPolygons to model your shape, you should use as few polygons as possible. Use quads instead of triangles if possible, since two triangles take twice as much time to compute as a single quad. This does mean that you need to ensure that your quads are perfectly coplanar.

You can also make higher-order polygons like five-sided and six-sided polygons or more, but you cannot make concave polygons. If you create a concave or non-coplanar CollisionPolygon in your modeling package, Panda will automatically triangulate it for you (but this might result in a suboptimal representation, so it is usually better to subdivide a concave polygon by hand).

Unlike a plane, a CollisionPolygon is infinitely thin; an object is only considered to be colliding with the polygon while it is overlapping it.

When you create a CollisionPolygon interactively, you can only create triangles or quads (the higher-order polygons can only be loaded from an egg file). Simply specify the three or four points to the constructor, in counter-clockwise order.

quad = CollisionPolygon(Point3(0, 0, 0), Point3(0, 0, 1),
  Point3(0, 1, 1), Point3(0, 1, 0))

CollisionRay

The ray, line, and segment (below) are special collision solids that are useful only as a "from" object; since these objects have no volume, nothing will collide "into" a ray.

The CollisionRay represents an infinite ray that begins at a specific point, and stretches in one direction to infinity.

It is particularly useful for picking objects from the screen, since you can create a ray that starts at the camera's point of view and extends into the screen, and then determine which objects that ray is intersecting. (In fact, there is a method on CollisionRay called setFromLens() that automatically sets up the ray based on a 2-d onscreen coordinate; this is used by the "picker". See Clicking on 3D Objects.)

The CollisionRay is also useful in conjunction with the CollisionHandlerFloor; see Collision Handlers.

A CollisionRay is created by specifing an origin point, and a direction vector. The direction vector need not be normalized.

ray = CollisionRay(ox, oy, oz, dx, dy, dz)

CollisionLine

This is essentially the same as a CollisionRay, except it extends to infinity in both directions. It is constructed with the same parameters, an origin point and a direction vector.

line = CollisionLine(ox, oy, oz, dx, dy, dz)

CollisionSegment

Finally, a segment is another variant on the CollisionRay that does not extend to infinity, but only goes to a certain point and stops. It is useful when you want to put a limit on how far the CollisionRay would otherwise reach.

A CollisionSegment is constructed by specifying the two end points.

segment = CollisionSegment(ax, ay, az, bx, by, bz)


Panda3D Manual: Collision Handlers

You will need to create a CollisionHandler that specifies what to do when a collision event is detected. Each object can only have one collision handler associated with it. There are several possible kinds of CollisionHandler available.

CollisionHandlerQueue

The simplest kind of CollisionHandler, this object simply records the collisions that were detected during the most recent traversal. You can then iterate through the list using queue.getNumEntries() and queue.getEntry():

queue = CollisionHandlerQueue()
traverser.addCollider(fromObject, queue)
traverser.traverse(render)
for i in range(queue.getNumEntries()):
  entry = queue.getEntry(i)
  print entry

By default, the Collision Entries appear in the queue in no particular order. You can arrange them in order from nearest to furthest by calling queue.sortEntries() after the traversal.

CollisionHandlerEvent

This is another simple kind of CollisionHandler. Rather than saving up the collisions, it generates a Panda event when collision events are detected.

There are three kinds of events that may be generated: the "in" event, when a particular object collides with another object that it didn't in the previous pass, the "out" event, when an object is no longer colliding with an object it collided with in the previous pass, and the "again" event, when an object is still colliding with the same object that it did in the previous pass.

For each kind of event, the CollisionHandlerEvent will construct an event name out of the names of the from and into objects that were involved in the collision. The exact event name is controlled by a pattern string that you specify. For instance:

handler.addInPattern('%fn-into-%in')
handler.addAgainPattern('%fn-again-%in')
handler.addOutPattern('%fn-out-%in')

In the pattern string, the following sequences have special meaning:

%fn | the name of the "from" object's node
%in | the name of the "into" object's node
%fs | 't' if "from" is declared to be tangible, 'i' if intangible
%is | 't' if "into" is declared to be tangible, 'i' if intangible
%ig | 'c' if the collision is into a CollisionNode, 'g' if it is an ordinary visible GeomNode
%(tag)fh | generate event only if "from" node has the indicated tag
%(tag)fx | generate event only if "from" node does not have the indicated tag
%(tag)ih | generate event only if "into" node has the indicated tag
%(tag)ix | generate event only if "into" node does not have the indicated tag
%(tag)ft | the indicated tag value of the "from" node.
%(tag)it | the indicated tag value of the "into" node.

You may use as many of the above sequences as you like, or none, in the pattern string. In the tag-based sequences, the parentheses around (tag) are literal; the idea is to write the name of the tag you want to look up, surrounded by parentheses. The tag is consulted using the nodePath.getNetTag() interface.

In any case, the event handler function that you write to service the event should receive one parameter (in addition to self, if it is a method): the CollisionEntry. For example:

  class MyObject(DirectObject.DirectObject):
    def __init__(self):
      self.accept('car-into-rail', handleRailCollision)

    def handleRailCollision(self, entry):
      print entry

Note that all of the following versions of CollisionHandler also inherit from CollisionHandlerEvent, so any of them can be set up to throw events in the same way.

CollisionHandlerPusher

This is the first of the more sophisticated handlers. The CollisionHandlerPusher, in addition to inheriting all of the event logic from CollisionHandlerEvent, will automatically push back on its from object to keep it out of walls. The visual effect is that your object will simply stop moving when it reaches a wall if it hits the wall head-on, or it will slide along the wall smoothly if it strikes the wall at an angle.

The CollisionHandlerPusher needs to have a handle to the NodePath that it will push back on, for each from object; you pass this information to pusher.addCollider. This should be the node that is actually moving. This is often, but not always, the same NodePath as the CollisionNode itself, but it might be different if the CollisionNode is set up as a child of the node that is actually moving.

smiley = loader.loadModel('smiley.egg')
fromObject = smiley.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))

pusher = CollisionHandlerPusher()
pusher.addCollider(fromObject, smiley)

Don't be confused by the call to pusher.addCollider; it looks a lot like the call to traverser.addCollider, but it's not the same thing, and you still need to add the collider and its handler to the traverser:

traverser.addCollider(fromObject, pusher)
smiley.setPos(x, y, 0)

If you are using Panda's drive mode to move the camera around (or some other node), then you also need to tell the pusher about the drive node, by adding it into the pusher.addCollider call:

fromObject = base.camera.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))
pusher = CollisionHandlerPusher()
pusher.addCollider(fromObject, base.camera, base.drive.node())

PhysicsCollisionHandler

This kind of handler further specializes CollisionHandlerPusher to integrate with Panda's Physics Engine. It requires that the NodePath you pass as the second parameter to pusher.addCollider actually contains an ActorNode, the type of node that is moved by forces in the physics system.

anp = render.attachNewNode(ActorNode('actor'))
fromObject = anp.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionSphere(0, 0, 0, 1))

pusher = PhysicsCollisionHandler()
pusher.addCollider(fromObject, anp)

Whenever you have an ActorNode that you want to respond to collisions, we recommend that you use a PhysicsCollisionHandler rather than an ordinary CollisionHandlerPusher. The PhysicsCollisionHandler will keep the object out of walls, just like the CollisionHandlerPusher does, but it will also update the object's velocity within the physics engine, which helps to prevent the physics system from becoming unstable due to large accumulated velocities.

CollisionHandlerFloor

This collision handler is designed to serve one very specialized purpose: it keeps an object on the ground, or falling gently onto the ground, even if the floor is not level, without involving physics.

It is intended to be used with a CollisionRay or CollisionSegment. The idea is that you attach a ray to your object, pointing downward, such that the topmost intersection the ray detects will be the floor your object should be resting on. Each frame, the CollisionHandlerFloor simply sets your object's z value to the detected intersection point (or, if it is so configured, it slowly drops the object towards this point until it reaches it).

Using the CollisionHandlerFloor can be an easy way to simulate an avatar walking over uneven terrain, without having to set up a complicated physics simulation (or involve physics in any way). Of course, it does have its limitations.

smiley = loader.loadModel('smiley.egg')
fromObject = smiley.attachNewNode(CollisionNode('colNode'))
fromObject.node().addSolid(CollisionRay(0, 0, 0, 0, 0, -1))

lifter = CollisionHandlerFloor()
lifter.addCollider(fromObject, smiley)


Panda3D Manual: Collision Entries

For each collision detected, a new CollisionEntry object is created. This CollisionEntry stores all the information about the collision, including the two objects (nodes) involved in the collision, and the point of impact and the surface normal of the into object at that point.

The CollisionEntry object is passed to the event handler method by the CollisionHandlerEvent and its derivatives; it is also the object stored in the queue of collisions maintained by the CollisionHandlerQueue.

However you get a handle to CollisionEntry object, you can query it for information using the following methods:

entry.getFromNodePath() | Returns the NodePath of the "from" object. This NodePath will contain a CollisionNode.
entry.getIntoNodePath() | Returns the NodePath of the "into" object. This NodePath will contain a CollisionNode, or if the collision was made with visible geometry, a GeomNode.
entry.getFrom() | Returns the actual CollisionSolid of the "from" object. This is useful if there were more than one CollisionSolid in the "from" CollisionNode.
entry.getInto() | Returns the actual CollisionSolid of the "into" object. However, if the collision was made with visible geometry, there is no CollisionSolid, and this will be an invalid call.
entry.hasInto() | Returns true if the collision was made into a CollisionSolid as opposed to visible geometry, and thus the above call will be valid.
entry.getSurfacePoint(nodePath) | Returns the 3-D point of the collision, in the coordinate space of the supplied NodePath. This point will usually be on the surface of the "into" object.
entry.getSurfaceNormal(nodePath) | Returns the 3-D surface normal of the "into" object at the point of the collision, in the coordinate space of the supplied NodePath.
entry.getInteriorPoint(nodePath) | Returns the 3-D point, within the interior of the "into" object, that represents the depth to which the "from" object has penetrated.

The three methods that return points or vectors all accept a NodePath as a parameter. This can be any NodePath in the scene graph; it represents the coordinate space in which you expect to receive the answer. For instance, to receive the point of intersection in the coordinate space of the "into" object, use:

point = collisionEntry.getSurfacePoint(collisionEntry.getIntoNodePath())

If you wanted to put an axis at the point of the collision to visualize it, you might do something like this:

axis = loader.loadModel('zup-axis.egg')
axis.reparentTo(render)
point = collisionEntry.getSurfacePoint(render)
normal = collisionEntry.getSurfaceNormal(render)
axis.setPos(point)
axis.lookAt(point + normal)


Panda3D Manual: Collision Traversers

A CollisionTraverser object performs the actual work of checking all solid objects for collisions. Normally, you will create a single CollisionTraverser object and assign it to base.cTrav; this traverser will automatically be run every frame. It is also possible to create additional CollisionTraversers if you have unusual needs; for instance, to run a second pass over a subset of the geometry. If you create additional CollisionTraversers, you must run them yourself.

The CollisionTraverser maintains a list of the active objects in the world, sometimes called the "colliders" or "from objects". The remaining collidable objects in the world that are not added to a CollisionTraverser are the "into objects". Each of the "from objects" is tested for collisions with all other objects in the world, including the other from objects as well as the into objects.

Note that it is up to you to decide how to divide the world into "from objects" and "into objects". Typically, the from objects are the moving objects in the scene, and the static objects like walls and floors are into objects, but the collision system does not require this; it is perfectly legitimate for a from object to remain completely still while an into object moves into it, and the collision will still be detected.

It is a good idea for performance reasons to minimize the number of from objects in a particular scene. For instance, the user's avatar is typically a from object; in many cases, it may be the only from object required--all of the other objects in the scene, including the walls, floors, and other avatars, might be simply into objects. If your game involves bullets that need to test for collisions with the other avatars, you will need to make either the bullets or the other avatars be from objects, but probably not both.

In order to add a from object to the CollisionTraverser, you must first create a CollisionHandler that defines the action to take when the collision is detected; then you pass the NodePath for your from object, and its CollisionHandler, to addCollider.

traverser = CollisionTraverser('traverser name')
base.cTrav = traverser
traverser.addCollider(fromObject, handler)

You only need to add the "from" objects to your traverser! Don't try to add the "into" objects to the CollisionTraverser. Adding an object to a CollisionTraverser makes it a "from" object. On the other hand, every object that you put in the scene graph, whether it is added to a CollisionTraverser or not, is automatically an "into" object.

Note that all of your "from" objects are typically "into" objects too (because they are in the scene graph), although you may choose to make them not behave as into objects by setting their CollideMask to zero.


Panda3D Manual: Collision Bitmasks

By default, every "from" object added to a CollisionTraverser will test for collisions with every other CollisionNode in the scene graph, and will not test for collisions with visible geometry. For simple applications, this is sufficient, but often you will need more control.

This control is provided with the collide masks. Every CollisionNode has two collide masks: a "from" mask, which is used when the CollisionNode is acting as a "from" object (i.e. it has been added to a CollisionTraverser), and an "into" mask, which is used when the node is acting as an "into" object (i.e. it is in the scene graph, and a from object is considering it for collisions).

In addition, visible geometry nodes--that is, GeomNodes--also have an "into" mask, so that visible geometry can serve as an "into" object also. (However, only a CollisionNode can serve as a "from" object.)

Before the solids in a "from" CollisionNode are tested for collisions with another CollisionNode or with a GeomNode, the collide masks are compared. Specifically, the "from" mask of the from object, and the "into" mask of the into object, are ANDed together. If the result is not zero--meaning the two masks have at least one bit in common--then the collision test is attempted; otherwise, the two objects are ignored.

The collide masks are represented using a BitMask32 object, which is really just a 32-bit integer with some additional methods for getting and setting particular bits.

You can only set the from collide mask on a collision node, and you must set it directly on the node itself, not on the NodePath:

nodePath.node().setFromCollideMask(BitMask32(0x10))

However, the into collide mask may be set on the NodePath, for convenience; this recursively modifies the into collide mask for all the nodes at the given NodePath level and below.

nodePath.setCollideMask(newMask, bitsToChange, nodeType)

The parameter newMask specifies the new mask to apply. The remaining parameters are optional; if they are omitted, then every node at nodePath level and below is assigned newMask as the new into collide mask. However, if bitsToChange is specified, it represents the set of bits that are to be changed from the original; bits that are 0 in bitsToChange will not be modified at each node level. If nodeType is specified, it should be a TypeHandle that represents the type of node that will be modified, e.g. CollisionNode.getClassType() to affect only CollisionNodes.

Examples:

nodePath.setCollideMask(BitMask32(0x10))

This sets the into collide mask of nodePath, and all children of nodePath, to the value 0x10, regardless of the value each node had before.

nodePath.setCollideMask(BitMask32(0x04), BitMask32(0xff))

This replaces the lower 8 bits of nodePath and all of its children with the value 0x04, leaving the upper 24 bits of each node unchanged.

The default value for both from and into collide masks for a new CollisionNode can be retrieved by CollisionNode.getDefaultCollideMask(), and the default into collide mask for a new GeomNode is GeomNode.getDefaultCollideMask(). Note that you can create a CollisionNode that collides with visible geometry by doing something like this:

nodePath.node().setFromCollideMask(GeomNode.getDefaultCollideMask())


Panda3D Manual: Rapidly-Moving Objects

Panda3D's collision system works by testing the current state of the world every frame for a possible intersection. If your objects are moving so quickly that they might pass completely through another object in the space of one frame, however, that collision might never be detected.

To avoid this problem, the Panda3D scene graph supports an advanced feature: it can record the previous frame's position of each moving object for the benefit of the CollisionTraverser. The CollisionTraverser can then take advantage of this information when it is testing for collisions. If it sees that a moving object was on one side of an object last frame, and on the opposite side this frame, it can trigger the collision detection even though the two objects might not currently be intersecting.

There are a few things you need to do to activate this mode.

1. First, you must tell the CollisionTraverser that you intend to use this mode; by default, it ignores the previous position information. To activate this mode, call:

base.cTrav.setRespectPrevTransform(True)

You only need to make this call once, at the beginning of your application (or whenever you create the CollisionTraverser). That switches the CollisionTraverser into the new mode. If you create any additional CollisionTraversers, you should make the call for them as well.

2. Ensure that base.resetPrevTransform(render) is called every frame. Actually, this is already done for you automatically by ShowBase.py, so normally you don't need to do anything for this step.

The resetPrevTransform() call should be made once per frame (at the very beginning of the frame) for every different scene graph in your application that involves collisions. It ensures that the current frame's position is copied to the previous frame's position, before beginning the processing for that frame. Note that if you have multiple CollisionTraversers handling the same scene graph, you only need to (and only should) call this function once, but if you have two or more disconnected scene graphs, you will need to call it for each scene graph.

If you don't understand the above paragraph, then you aren't using disconnected scene graphs, and you shouldn't worry about it.

3. Whenever you move an object from one point to another in your scene (except when you put it into your scene the first time), instead of using:

object.setPos(newPos)

You should use:

object.setFluidPos(newPos)

In general, setPos() means "put the object here, directly" and setFluidPos() means "slide the object here, testing for collisions along the way". It is important to make a clear distinction between these two calls, and make the appropriate call for each situation.

If you are moving an object with a LerpInterval, and you want collisions to be active (and fluid) during the lerp, you should pass the keyword parameter fluid = 1 to the LerpInterval constructor. It is rare to expect collisions to be active while an object is moving under direct control of the application, however.

Visualizing the previous transform

When you are using the setFluidPos() call, and you have called show() on your CollisionNode to make it visible, you will see the CollisionNode itself each frame, plus a ghosted representation of where it was the previous frame. This can help you visually see that the previous-transform mechanism is working. (It does not guarantee that the setRespectPrevTransform() call has been made on your CollisionTraverser, however.)

Caveats

At the present, the CollisionTraverser only uses the previous transform information when it is testing a CollisionSphere into a CollisionPolygon--that is, when the "from" object is a CollisionSphere, and the "into" object is a CollisionPolygon (or a wall of CollisionPolygons). Other kinds of collision solids currently do not consider the previous transform. (However, the other collision solids are generally thicker than a CollisionPolygon, so it is less likely that a moving object will pass all the way through them in one frame--so it is not quite as bad as it seems.)

Enabling the previous transform mode helps reduce slipping through walls considerably. However, it's not perfect; no collision system is. If your object is moving tremendously fast, or just happens to get lucky and slip through a tiny crack between adjacent polygons, it may still get through without detecting a collision. Any good application will be engineered so that the occasional collision slip does not cause any real harm.

The CollisionHandlerFloor is especially bad about allowing objects to slip through floors, in spite of the previous transform state, especially when you avatar is walking up a sloping path. This is just because of the way the CollisionHandlerFloor works. If you are having problems with the CollisionHandlerFloor, consider reducing the slope of your floors, increasing the height of the ray above the ground, and/or reducing the speed of your avatar.


Panda3D Manual: Pusher Example

Here is a short example to show two small spheres using a Pusher.

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *

#initialize traverser
base.cTrav = CollisionTraverser()

#initialize pusher
pusher = CollisionHandlerPusher()

#########

#load a model. reparent it to the camera so we can move it.
smiley = loader.loadModel('smiley')
smiley.reparentTo(camera)
smiley.setPos(0, 25.5,0.5)

#create a collision solid for this model
cNode = CollisionNode('smiley')
cNode.addSolid(CollisionSphere(0,0,0,1.1))
smileyC = smiley.attachNewNode(cNode)
smileyC.show()

#########

#load a model
frowney = loader.loadModel('frowney')
frowney.reparentTo(render)
frowney.setPos(5, 25,0)

#create a collsion solid for this model
cNode = CollisionNode('frowney')
cNode.addSolid(CollisionSphere(0,0,0,1.1))
frowneyC = frowney.attachNewNode(cNode)
frowneyC.show()

#########
#add collision node to the traverser and the pusher

base.cTrav.addCollider(frowneyC,pusher)
pusher.addCollider(frowneyC,frowney, base.drive.node())

#########

#have the one ball moving to help show what is happening
frowney.posInterval(5,Point3(5,25,0),startPos=Point3(-5,25,0),fluid=1).loop()

#run the world. move around with the mouse to see how the moving ball changes
#course to avoid the one attached to the camera.

run()


Panda3D Manual: Event Example

Here is a short example of using the Collision Handler Events

import direct.directbase.DirectStart
from direct.interval.IntervalGlobal import * #to make things flash on collisions
from pandac.PandaModules import *
from direct.showbase import DirectObject #so that we can accept messages

class World( DirectObject.DirectObject ):
    def __init__( self ):
        #initialize traverser
        base.cTrav = CollisionTraverser()

        #initialize handler
        self.collHandEvent=CollisionHandlerEvent()
                self.collHandEvent.addInPattern('into-%in')
        self.collHandEvent.addOutPattern('outof-%in')

        #initialize collision count (for unique collision strings)
        self.collCount=0

        #load a model.  reparent it to the camera so we can move it.
        s = loader.loadModel('smiley')
        s.reparentTo(camera)
        s.setPos(0, 25,0)

        #setup a collision solid for this model
        sColl=self.initCollisionSphere(s, True)

        #add this object to the traverser
        base.cTrav .addCollider(sColl[0] , self.collHandEvent)

        #accept the events sent by the collisions
                self.accept( 'into-' + sColl[1], self.collide3)
                self.accept( 'outof-' + sColl[1], self.collide4)
                print sColl[1]

        #load a model.
        t = loader.loadModel('smiley')
        t.reparentTo(render)
        t.setPos(5, 25,0)

        #setup a collision solid for this model
        tColl=self.initCollisionSphere(t, True)

        #add this object to the traverser
                base.cTrav .addCollider(tColl[0], self.collHandEvent)

                #accept the events sent by the collisions
                self.accept( 'into-' + tColl[1], self.collide)
                self.accept( 'outof-' + tColl[1], self.collide2)
                print tColl[1]

                print "WERT"

    def collide(self, collEntry):
        print "WERT: object has collided into another object"
        Sequence(Func(collEntry.getFromNodePath().getParent().setColor, VBase4(1,0,0,1)),
                 Wait(.2),
                 Func(collEntry.getFromNodePath().getParent().setColor, VBase4(0,1,0,1)),
                 Wait(.2),
                 Func(collEntry.getFromNodePath().getParent().setColor, VBase4(1,1,1,1))).start()

    def collide2(self, collEntry):
        #collEntry.getFromNodePath().getParent().remove()
        print "WERT.: object is no longer colliding with another object"

    def collide3(self, collEntry):
        #collEntry.getFromNodePath().getParent().remove()
        print "WERT2: object has collided into another object"

    def collide4(self, collEntry):
        #collEntry.getFromNodePath().getParent().remove()
        print "WERT2: object is no longer colliding with another object"

    def initCollisionSphere( self, obj, show=False):
        #get the size of the object for the collision sphere
        bounds = obj.getChild(0).getBounds()
         center = bounds.getCenter()
             radius = bounds.getRadius()*1.1

        #create a collision sphere and name it something understandable
        collSphereStr = 'CollisionHull' +str(self.collCount)+"_"+obj.getName()
        self.collCount+=1
        cNode=CollisionNode(collSphereStr)
        cNode.addSolid(CollisionSphere(center, radius ) )

        cNodepath=obj.attachNewNode(cNode)
        if show:
            cNodepath.show()

        # return a tuple with the collision node and its corrsponding string
        # return the collison node so that the bitmask can be set
        return (cNodepath,collSphereStr )

#run the world.  move around with the mouse to create collisions
w= World()
run()


Panda3D Manual: Bitmask Example

Here is a short example of using bitmasks

import direct.directbase.DirectStart
from direct.showbase import DirectObject
from pandac.PandaModules import *

class World( DirectObject.DirectObject ):
    def __init__( self ):
        #initialize traverser
        base.cTrav = CollisionTraverser()

        #initialize handler
        self.collHandEvent=CollisionHandlerEvent()
        self.collHandEvent.addInPattern('into-%in')
        self.collHandEvent.addOutPattern('outof-%in')

        #initialize collision count (for unique collision strings)
        self.collCount=0

        self.loadObj1()
        self.loadObj2()

    def loadObj1(self):
        #load a model. reparent it to the camera so we can move it.
        s = loader.loadModel('smiley')
        s.reparentTo(camera)
        s.setPos(0, 25,0)

        #setup a collision solid for this model
        sColl=self.initCollisionSphere(s, True)

        #set up bitmasks
        #this object can only collide into things
        sColl[0].node().setIntoCollideMask( BitMask32.allOff() )
        #this object will only collide with objects with this bitmask
        sColl[0].node().setFromCollideMask( BitMask32.bit( 1 ) )

        #add this object to the traverser
        base.cTrav .addCollider(sColl[0] , self.collHandEvent)

    def loadObj2(self):
        #load a model.
        t = loader.loadModel('smiley')
        t.reparentTo(render)
        t.setPos(5, 25,0)

        #setup a collision solid for this model
        tColl=self.initCollisionSphere(t, True)

        #set up bitmasks
        #this object can only be collided into and will collide with the other object
        tColl[0].node().setIntoCollideMask( BitMask32.bit( 1 ))
        tColl[0].node().setFromCollideMask( BitMask32.allOff())

        #add this object to the traverser
        base.cTrav .addCollider(tColl[0], self.collHandEvent)

        #accept the events sent by the collisions
        self.accept( 'into-' + tColl[1], self.collide)
        self.accept( 'outof-' + tColl[1], self.collide2)

    def collide(self, collEntry):
        print "WERT: object has collided into another object"

    def collide2(self, collEntry):
        print "WERT: object is no longer colliding with another object"

    def initCollisionSphere( self, obj, show=False):
        #get the size of the object for the collision sphere
        bounds = obj.getChild(0).getBounds()
        center = bounds.getCenter()
        radius = bounds.getRadius()*1.1

        #create a collision sphere and name it something understandable
        collSphereStr = 'CollisionHull' +obj.getName()
        cNode=CollisionNode(collSphereStr)
        cNode.addSolid(CollisionSphere(center, radius ) )
        cNodepath=obj.attachNewNode(cNode)
        if show:
            cNodepath.show()

        # return a tuple with the collision node and its corrsponding string
        # return the collison node so that the bitmask can be set
        return (cNodepath,collSphereStr )

#run the world. move around with the mouse to create collisions
w = World()
run()


Panda3D Manual: Clicking on 3D Objects

The simplest way to click on 3D objects in Panda is to use very simplistic collision detection coupled with event processing.

First, after a CollisonTraverser and a CollisionHandler have been setup, attach a CollisionRay node to the camera. This node will have its setFromCollideMask() set to GeomNode.getDefaultCollideMask() in order to be as general as possible:

pickerNode=CollisionNode('mouseRay')
pickerNP=camera.attachNewNode(pickerNode)
pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
pickerRay=CollisionRay()
pickerNode.addSolid(pickerRay)
myTraverser.addCollider(pickerNP, myHandler)

For any object that you want to be pickable you should add a flag to it. The easiest way is to use the setTag() function, e.g.:

object1.setTag('myObjectTag', '1')
object2.setTag('myObjectTag', '2')

The above example sets the tag 'myObjectTag' on two objects in your graph that you want to designate as pickable (we will check for the presence of this tag later, when we get the response back from the collision system).

Now assume that the function myFunction() is set up to be called for the 'mouse1' event. In myFunction() is where you call pickerRay.setFromLens(origin, destX, destY). This makes the ray's origin origin and the ray's vector the direction from origin to the point (destX,destY).

def myFunction():
   #This gives up the screen coordinates of the mouse
   mpos=base.mouseWatcherNode.getMouse()

   #This makes the ray's origin the camera and makes the ray point
   #to the screen coordinates of the mouse
   pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

After this, you now call the traverser like any other collision, get the closest object, and "pick" it.

def myFunction():
   mpos=base.mouseWatcherNode.getMouse()
   pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

   myTraverser.traverse(render)
   #assume for simplicity's sake that myHandler is a CollisionHandlerQueue
   if myHandler.getNumEntries() > 0:
      myHandler.sortEntries() #this is so we get the closest object
      pickedObj=myHandler.getEntry(0).getIntoNodePath()

Now, the node pickedObj returned by the collision system may not be the object itself, but might be just a tiny piece of the object--a wheel, or a nose, or whatever. In particular, it will be one of the GeomNodes that make up the object (the GeomNode class contains the visible geometry primitives that are used to define renderable objects in Panda). Since your object might consist of more than one GeomNode, what you probably would prefer to get is the NodePath that represents the parent of all of these GeomNodes--that is, the NodePath that you set the 'myObjectTag' tag on, above. You can use nodePath.findNetTag() to return the parent NodePath that contains a specified tag.

(There are also other, similar methods on NodePath that can be used to query the tag specified on a parent node, such as getNetTag() and hasNetTag(). For simplicity we will restrict this example to findNetTag().)

Now you can edit myFunction to look like:

def myFunction():
   mpos=base.mouseWatcherNode.getMouse()
   pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

   myTraverser.traverse(render)
   #assume for simplicity's sake that myHandler is a CollisionHandlerQueue
   if myHandler.getNumEntries() > 0:
      myHandler.sortEntries() #this is so we get the closest object
      pickedObj=myHandler.getEntry(0).getIntoNodePath()
      pickedObj=pickedObj.findNetTag('myObjectTag')
      if not pickedObj.isEmpty():
         handlePickedObject(pickedObj)


Panda3D Manual: Hardware support

Here is some advice on interating with hardware using Panda3D's build in keyboard and mouse support, as well as some suggestions for getting joystick information into Panda3D.


Panda3D Manual: Keyboard Support

Panda3D has keyboard support built in. Keyboard presses send Events. Each keyboard key will send an event when it is first pressed down, when it is released, and one repeatedly while its pressed.

The events can be accepted with the following code:

self.accept( <event name> , <Function> )
self.accept( <event name> , <Function> , <parameters list> )
self.acceptOnce( <event name> , <Function> )
self.acceptOnce( <event name> , <Function> , <parameters list> )

<event name> is a string that labels the event. <Function> is a python function to be called when the event is sent. <parameters list> is a python list of parameters to use to call <Function>.

The <event name> that a key sends is fairly predictable base on these rules:

You can see these results for yourself using messenger.toggleVerbose()

1. Keys that type a character are named that character. It is always lowercase even with shift or caps lock (Shift and other modifiers are explained below.)

e.g.

 "a", "b", "3", "[", etc.

not

 "A", "B", "#", "{"

2. The key down event is named for the key.

3. As of 1.3.0 The keyboard autorepeat is named for the key + "-repeat" e.g.

 "a-repeat", "2-repeat", "[-repeat"

4. The key up event is named for the key + "-up" e.g.

 "a-up", "2-up", "[-up"

5. All key events (including "-up") have a corresponding time event labeled

"time-" + <key name>

Here is an example of time reading in code:

class ReadKeys(DirectObject.DirectObject):
  def __init__(self):
    self.accept('time-a-repeat', self.printRepeat)

  def printRepeat(self,a):
        print "repeat a",a

that send a time argument corresponding to the time that event was fired

6. Keys that don't type a character are labeled as follows:

"escape", "f"+"1-12" (e.g. "f1","f2",..."f12"), "print_screen" "scroll_lock"
"backspace", "insert", "home", "page_up", "num_lock"
"tab",  "delete", "end", "page_down"
"caps_lock", "enter", "arrow_left", "arrow_up", "arrow_down", "arrow_right"
"shift", "lshift", "rshift",
"control", "alt", "lcontrol", "lalt", "space", "ralt", "rcontrol"

Note that "print_screen" is not available on Windows, (and some other OS'es) because it is used by the system. If you want to be able to catch this key, you need to find some way to prevent the system from catching the key first. (however, "print_screen-up" is still available in most cases)

7. Some physical keys are distinguishable from the events that they fire, and some are not. The modifier keys distinguish between left and right, but send a neutral event as well. (e.g. the left shift key sends both "lshift" and "shift" events when pressed) Save for "num_lock", "*", and "+" the numpad keys are indistinguishable from the main keyboard counterparts. (e.g. when Num Lock is on the both the numpad and keyboard 1 keys send "1")

8. Keys pressed in combination with modifiers send an additional event. The name is the modifier appended before the key and separated with a dash in the order shift control alt e.g.:

"shift-a" "shift-control-alt-a" "shift-alt-a"

These compound events don't send a "time-" event. If you need one, use the "time-" event sent by one of the keys in the combination.

The modifier compound events can be turned off to allow regular events to occur by calling:

base.mouseWatcherNode.setModifierButtons(ModifierButtons())
base.buttonThrowers[0].node().setModifierButtons(ModifierButtons())

Here are some examples in code:

self.accept('k', self.__spam )#calls the function __spam() on the k key event.
self.accept('k-up', self.__spam, [eggs, sausage, bacon,] )#calls __spam(eggs,sausage,bacon)
self.accept('escape', sys.exit )#exit on esc
self.accept('arrow_up', self.spamAndEggs )#call spamAndEggs when up is pressed
self.accept('arrow_up-repeat', self.spamAndEggs )#and at autorepeat if held
self.accept('arrow_up-up', self.spamAndEggs )#calls when the up arrow key is released

Please, note then when the Panda window is minimized or Panda3D loses focus somehow else, "-up" event is sent for all keys. Read this forum thread to learn more: http://panda3d.net/phpbb2/viewtopic.php?t=4630


Panda3D Manual: Mouse Support

Panda3D has mouse support built in. The default action of the mouse is to control the camera. If you want to disable this functionality you can use the command:

base.disableMouse()

This function's name is slightly misleading. It only disables the task that drives the camera around, it doesn't disable the mouse itself. You can still get the position of the mouse, as well as the mouse clicks.

To get the position:

if base.mouseWatcherNode.hasMouse():
  x=base.mouseWatcherNode.getMouseX()
  y=base.mouseWatcherNode.getMouseY()

The mouse clicks generate "events." To understand what events are, and how to process them, you will need to read the Event Handling section. The names of the events generated are:

mouse1 | Mouse Button 1 Pressed
mouse2 | Mouse Button 2 Pressed
mouse3 | Mouse Button 3 Pressed
mouse1-up | Mouse Button 1 Released
mouse2-up | Mouse Button 2 Released
mouse3-up | Mouse Button 3 Released
wheel_up | Mouse Wheel rolled upwards
wheel_down | Mouse Wheel rolled downwards

If you want to hide the mouse cursor, you want the line: "cursor hidden #t" in your Config.prc or this section of code:

from pandac.PandaModules import WindowProperties
props = WindowProperties()
props.setCursorHidden(True)
base.win.requestProperties(props)

Re-enabling mouse control

If you need to re-enable the mouse control of the camera, you have to adjust mouseInterfaceNode to the current camera transformation :

mat=Mat4(camera.getMat())
mat.invertInPlace()
base.mouseInterfaceNode.setMat(mat)
base.enableMouse()

Otherwise the camera would be placed back to the last position when the mouse control was enabled.

Multiple Mice

If you have multiple mice connected to a single machine, it is possible to get mouse movements and buttons for each individual mouse. This is called raw mouse input. It is really only useful if you are building an arcade machine that has lots of trackballs or spinners.

In order to use raw mouse input, you first need to enable it. To do so, add the following line to your panda configuration file:

read-raw-mice #t

This causes the panda main window to be created with the "raw_mice" window property. That window property, in turn, causes the window to track and store the positions and buttons of the raw mice. Then, that data is extracted from the main window by objects of class MouseWatcher. The application program can fetch the mouse data from the MouseWatchers. The global variable base.pointerWatcherNodes contains the MouseWatchers.

The first MouseWatcher on the list always represents the system mouse pointer - a virtual mouse that moves around whenever any of the physical mice do. Usually, you do not want to use this virtual mouse. If you're accessing raw mice, you usually want to access the real, physical mice. The list base.pointerWatcherNodes always contains the virtual system mouse first, followed by all the physical mice.

So to print out the positions of the mice, use this:

for mouse in base.pointerWatcherNodes:
  print "NAME=", mouse.getName()
  print "X=", mouse.getMouseX()
  print "Y=", mouse.getMouseY()

Each mouse will have a name-string, which might be something along the lines of "Micrologic High-Precision Gaming Mouse 2.0 #20245/405". The name is the only way to tell the various mice apart. If you have two different mice of different brands, you can easily tell them apart by the names. If you have two mice of the same make and manufacture, then their names will be very similar, but still unique. This is not because the mice contain serial numbers, but rather because they are uniquefied based on the USB port into which they are plugged. That means that if you move a mouse from one USB port to another, it will have a new name. For all practical purposes, that means that you will need to store a config file that maps mouse name to intended purpose.

Raw mouse buttons generate events. The event names are similar to the ones for the system mouse, except that they have a "mousedevX" prefix. Ie, an example event might be mousedev3-mouse1-up. In this example, the "mousedev3" specifier means that the mouse sending the event is base.pointerWatcherNode[3].

Multiple Mice under Linux

To use raw mouse input under Linux, the panda program needs to open the device files /dev/input/event*. On many Linux distributions, the permission bits are set such that this is not possible. This is a flaw in these distributions.

It is not a good idea to just change the permission bits. Doing so introduces a huge security hole in which any logged in user can monitor the mice, the joysticks, and the keyboard --- including any passwords that may be typed. The correct solution is to change the ownership of the input devices whenever a user sits down at the console. There is a module, pam_console, that does this, but it is now obsoleted, and has been removed from several distros. The Fedora pam_console removal page states that ACLs set by the HAL should replace pam_console's functionality. Currently, since it does not seem that HAL provides this yet, the best course of action is to make an 'input' group as described on the Gizmod wiki.

If you are building a stand-alone arcade machine that does not allow remote login and probably doesn't even have a net connection, then changing the permission bits isn't going to hurt you.


Panda3D Manual: Joystick Support

Note: I have been told that these instructions are inaccurate. - Josh

While Panda3D has mouse and keyboard support, it is best to look to the open source community for joystick support. Pygame is an open-source module that contains joystick support that may be easily included into a Panda3D application. Pygame may be found at http://www.pygame.org.

After downloading pygame, simply import the modules as you would any Panda3D module.

import pygame

Once pygame is imported, it needs to be initialized. Also, when the program is through with using pygame, it should be exited cleanly.

pygame.init()
pygame.quit()

Also, the joystick should be initialized. It too has a quit function.

joystick.init()
joystick.quit()

From here, it is possible to get the axis information of the joystick as well as the state of the buttons.

# Consume PyGame events.
# This seems superfluous, but it is necessary.
# Otherwise get_axis and get_button don't work.
for e in pygame.event.get(): pass

joystick.get_axis(<Axis>)
joystick.get_button(<Button>)

These are the primary functions for the joystick, but there are a number of other functions available for joystick support. This can be found at the pygame website.


Panda3D Manual: VR Helmets and Trackers

This section is especially geared towards Carnegie Mellon University's virtual reality equipment. The tracking setup used consists of a magnetic tracker base that receives signals, a virtual reality headmount, and four small trackers, one of which is attached to the headmount. These magnetic trackers may be held in the hand or implanted in non-metallic objects, and they will relay their position and orientation to the tracker base, which then supplies it to the program.

First, make sure that the VRHandler is in the same folder as your python files. Then, import it as you would any Panda3D module.

from VRHandler import *

Once imported, the VRHandler functions are now available. Make a constant in your code that controls whether or not the tracker is used.

USE_TRACKER = True

or

USE_TRACKER = False

Then, activate the tracker if required:

if USE_TRACKER:
  self.tracker = Tracker()
  camera.reparentTo(self.tracker.getHMDHelper())
else:
  print "Using Mouse and Keyboard Controls"
  self.controls = MouseAndKeyboardControls()
  self.controls.start()

If the tracker is not being used, the above code enables first-person-shooter style controls, using WASD to move, R and F for height, and the mouse to look around. You can add the following lines to your code if you want to customize the feel of the controls:

# Keyboard control constants
self.controls.acceleration = 15
self.controls.maxSpeed = 5
self.controls.friction = 6

Change those three numbers to whatever feels right for your world.

Typically, the tracker base is 6.5 feet off the ground, and the range of the trackers is from three or so inches from the tracker base to around the knees of an average person. The tracker base is treated as a NodePath, so it may be moved around. If you want the guest to move around the world while his real-life feet stay put, get the tracker base helper and move that around:

self.trackerBaseHelper = self.tracker.getTrackerBaseHelper()
self.trackerBaseHelper.setPos(0, 3, 0)

Finally, objects within the Panda3D application may be reparented to the trackers. The four trackers are HMDHelper, YellowHelper, GreenHelper, and BlueHelper. Also, remember that reparenting may create some strange inheritance issues.

NodePath.reparentTo(self.tracker.getHMDHelper() )
NodePath.reparentTo(self.tracker.getYellowHelper() )
NodePath.reparentTo(self.tracker.getGreenHelper() )
NodePath.reparentTo(self.tracker.getBlueHelper() )


Panda3D Manual: Math Engine

Panda3D has a number of vector, matrix, and quaternion operations built-in.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Matrix Representation

Periodically, the question arises: does Panda store matrices in column-major or row-major format? Unfortunately, people who ask that question often fail to realize that there are four ways to represent matrices, two of which are called "column major," and two of which are called "row major." So the answer to the question is not very useful. This section explains the four possible ways to represent matrices, and then explains which one panda uses.

The Problem

In graphics, matrices are mainly used to transform vertices. So one way to write a matrix is to write the four transform equations that it represents. Assuming that the purpose of a matrix is to transform an input-vector (Xi,Yi,Zi,Wi) into an output vector (Xo,Yo,Zo,Wo), the four equations are:

Xo = A*Xi + B*Yi + C*Zi + D*Wi
Yo = E*Xi + F*Yi + G*Zi + H*Wi
Zo = J*Xi + K*Yi + L*Zi + M*Wi
Wo = N*Xi + O*Yi + P*Zi + Q*Wi

There are two different orders that you can store these coefficients in RAM:

Storage Option 1: A,B,C,D,E,F,G,H,J,K,L,M,N,O,P,Q
Storage Option 2: A,E,J,N,B,F,K,O,C,G,L,P,D,H,M,Q

Also, when you're typesetting these coefficients in a manual (or printing them on the screen), there are two possible ways to typeset them:

A  B  C  D
E  F  G  H
J  K  L  M
N  O  P  Q

Typesetting
 Option 1

A  E  J  N
B  F  K  O
C  G  L  P
D  H  M  Q

Typesetting
Option 2

These are independent choices! There is no reliable relationship between the order that people choose to store the numbers, and the order in which they choose to typeset them. That means that any given system could use one of four different notations.

So clearly, the two terms "row major" and "column major" are not enough to distinguish the four possibilities. Worse yet, to my knowledge, there is no established terminology to name the four possibilities. So the next part of this section is dedicated to coming up with a usable terminology.

The Coefficients are Derivatives

The equations above contain sixteen coefficients. Those coefficients are derivatives. For example, the coefficient "G" could also be called "the derivative of Yo with respect to Zi."

This gives us a handy way to refer to groups of coefficients. Collectively, the coefficients "A,B,C,D" could also be called "the derivatives of Xo with respect to Xi,Yi,Zi,Wi" or just "the derivatives of Xo" for short. The coefficients "A,E,J,N" could also be called "the derivatives of Xo,Yo,Zo,Wo with respect to Xi" or just "the derivatives with respect to Xi" for short.

This is a good way to refer to groups of four coefficients because it unambiguously names four of them without reference to which storage option or which typesetting option you choose.

An alternative that works just as well (and is usually the only way to reverse-engineer a document that was written without specifying these conventions) is to find the translation parts of the matrix. If the translation lives in the rightmost column, then the matrix is intended for column vectors on the right. If the translation lives in the bottom row, then the matrix is intended for row vectors on the left.

What to Call the Two Ways of Storing a Matrix.

So here, again, are the two ways of storing a matrix. But using this newfound realization that the coefficients are derivatives, I have a meaningful way to name the two different ways of storing a matrix:

In the first storage scheme, the derivatives of Xo are stored first. In the second storage scheme, the derivatives with respect to Xi are stored first.

What to Call the Two Ways of Printing a Matrix.

One way to write the four equations above is to write them out using proper mathematical notation. There are two ways to do this, shown below:

Notice that the two matrices shown above are laid out differently. The first layout is the appropriate layout for use with column vectors. The second layout is the appropriate layout for use with row vectors. So that gives me a possible terminology for the two different ways of typesetting a matrix: the "row-vector-compatible" notation, and the "column-vector-compatible" notation.

The Four Possibilities

So now, the four possible representations that an engine could use are:

  1. Store derivatives of Xo first, typeset in row-vector-compatible notation.
  2. Store derivatives of Xo first, typeset in column-vector-compatible notation.
  3. Store derivatives wrt Xi first, typeset in row-vector-compatible notation.
  4. Store derivatives wrt Xi first, typeset in column-vector-compatible notation.

The Terms "Column Major" and "Row Major"

The term "row-major" means "the first four coefficients that you store, are also the first row when you typeset." So the four possibilities break down like this:

  1. Store derivatives of Xo first, typeset in row-vector-compatible notation (COLUMN MAJOR)
  2. Store derivatives of Xo first, typeset in column-vector-compatible notation (ROW MAJOR)
  3. Store derivatives wrt Xi first, typeset in row-vector-compatible notation (ROW MAJOR)
  4. Store derivatives wrt Xi first, typeset in column-vector-compatible notation (COLUMN MAJOR)

That makes the terms "row major" and "column major" singularly useless, in my opinion. They tell you nothing about the actual storage or typesetting order.

Panda Notation

Now that I've established my terminology, I can tell you what panda uses. If you examine the panda source code, in the method "LMatrix4f::xform," you will find the four transform equations. I have simplified them somewhat (ie, removed some of the C++ quirks) in order to put them here:

define VECTOR4_MATRIX4_PRODUCT(output, input, M)  \
output._0 = input._0*M._00 + input._1*M._10 + input._2*M._20 + input._3*M._30;  \
output._1 = input._0*M._01 + input._1*M._11 + input._2*M._21 + input._3*M._31;  \
output._2 = input._0*M._02 + input._1*M._12 + input._2*M._22 + input._3*M._32;  \
output._3 = input._0*M._03 + input._1*M._13 + input._2*M._23 + input._3*M._33;

Then, if you look in the corresponding header file for matrices, you will see the matrix class definition:

struct {
  FLOATTYPE  _00, _01, _02, _03;
  FLOATTYPE  _10, _11, _12, _13;
  FLOATTYPE  _20, _21, _22, _23;
  FLOATTYPE  _30, _31, _32, _33;
} m;

So this class definition shows not only how the coefficients of the four equations are stored, but also the layout in which they were intended to be typeset. So from this, you can see that panda stores derivatives wrt Xi first, and it typesets in row-vector-compatible notation.

Interoperability with OpenGL and DirectX

Panda is code-compatible with both OpenGL and DirectX. All three use the same storage format: derivatives wrt Xi first. You can pass a panda matrix directly to OpenGL's "glLoadMatrixf" or DirectX's "SetTransform".

However, remember that typesetting format and data storage format are independent choices. Even though two engines are interoperable at the code level (because their data storage formats match), their manuals might disagree with each other (because their typesetting formats do not match).

The panda typesetting conventions and the OpenGL typesetting conventions are opposite from each other. The OpenGL manuals use a column-vector-compatible notation. The Panda manuals use a row-vector-compatible notation.

DirectX uses the same conventions as Panda for both typesetting and memory storage: row vectors on the left, row major storage with the translation in the bottom row.


Panda3D Manual: Physics

Panda3D offers two choices to use for Physics:

  • Panda's Built-in Physics Engine: Panda3D has a very basic physics engine built-in that may apply forces to classes. The physics engine can handle angular or linear forces, as well as viscosity.
  • The Open Dynamics Engine: This is a more extended physics engine that Panda3D offers good integration for, you will want to use this if you need more complex physics simulations. In Panda3D versions 1.5.3 and above, this is integrated in the binaries from the download page.

When you have a very simple simulation, you will most likely want to use the built-in physics, since this is a much more stable system than ODE, which is still a bit experimental. Although, when the built-in engine doesn't offer enough functionality for you, it is advisable to switch to ODE.

Integration for various other physics engines have been provided by the community on the forums:

  • Old wrappers for AGEIA PhysX: http://panda3d.org/phpbb2/viewtopic.php?t=3108
  • More recent wrappers: http://panda3d.org/phpbb2/viewtopic.php?p=27906#27906
  • Most recent wrappers: http://panda3d.org/phpbb2/viewtopic.php?t=5067
  • Wrappers for Newton Game Dynamics: http://panda3d.org/phpbb2/viewtopic.php?t=2617


Panda3D Manual: Panda's Built-in Physics Engine

Panda3D has a very basic physics engine that may apply forces to classes. The physics engine can handle angular or linear forces, as well as viscosity.

To make use of the collision engine, first enable the particle system. The particle system relies upon the physics engine to move and update particles, so enabling the particle system adds the tasks in the engine that monitor and update the interactions of physics-enabled objects in the scene.

base.enableParticles()

The rest of this section will address how to prepare a model for physical interactions and apply forces to the model.


Panda3D Manual: Enabling physics on a node

The ActorNode is the component of the physics system that tracks interactions and applies them to a model. The calculations factor in the amount of time elapsed between frames, so the physics will be robust against changes in framerate.

To enable a node for physics, attach it to an ActorNode. An ActorNode's position and orientation can be updated automatically by the physics system, and so any children of the ActorNode will inherit that position and orientation as well.

(Do not confuse ActorNode with the Actor class, which is used to play animations. They are completely unrelated classes with similar names.)

When an ActorNode is created, it must be associated with a PhysicsManager. The PhysicsManager will handle the physics calculations every frame and update the ActorNode with any changes. Panda provides a default physics manager, base.physicsMgr, which will often be suitable for most applications.

Node=NodePath(PandaNode("PhysicsNode"))
Node.reparentTo(render)
jetpackGuy=loader.loadModel("models/jetpack_guy")
jetpackGuy.reparentTo(render)
an=ActorNode("jetpack-guy-physics")
anp=Node.attachNewNode(an)
base.physicsMgr.attachPhysicalNode(an)
jetpackGuy.reparentTo(anp)

Now, the "jetpackGuy" model will be updated every frame with the physics applied to it.

The ActorNode also serves as a repository for the PhysicsObject that describes the physical properties (i.e. mass) of the object. To modify these properties, use the getPhysicsObject call.

an.getPhysicsObject().setMass(136.077)   #about 300 lbs


Panda3D Manual: Applying physics to a node

To apply forces to a physical object, collect them into a ForceNode and then apply them to the object. The ForceNode is a node that specifies the "context" of the force; i.e. the local coordinate transform that determines the direction of the force. Because ForceNodes are separate from ActorNodes, a ForceNode can be placed in a different portion of the model tree from the ActorNode to which the forces applies. This allows for forces to be applied indirectly to a model (such as wind sweeping across the scene, or a mechanical impulse from an appendage of the model) without having to do the calculations necessary to transform from the ActorNode's coordinates to the coordinates of the force's source.

To add a force to a physical object, add the force using either the addLinearForce method (for translational forces) or the addAngularForce method (for rotational forces):

actorNode.addLinearForce(pusherForce)
actorNode.addAngularForce(spinnerForce)

Conversely, forces can be removed using the corresponding remove calls:

actorNode.removeLinearForce(pusherForce)
actorNode.removeAngularForce(spinnerForce)

By default, linear forces don't factor in the mass of the object upon which they act (meaning they are more like accelerations). To factor in the mass of the object when applying the linear force, use the following call to enable mass-dependent calculations:

pusherForce.setMassDependent(1)

Example 1: Gravity

To apply a gravitational pull to the "jetpack guy" from the previous example:

gravityFN=ForceNode('world-forces')
gravityFNP=render.attachNewNode(gravityFN)
gravityForce=LinearVectorForce(0,0,-9.81) #gravity acceleration
gravityFN.addForce(gravityForce)

base.physicsMgr.addLinearForce(gravityForce)

Since the gravitational force is relative to the entire world (and shouldn't change if, for example, the jetpack guy tumbles head-over-heels), the gravityForce vector was added to a ForceNode attached to render. So regardless of the orienation of the NodePath controlled by an, the force will always pull towards the bottom of the scene.

Since all objects in the scene should be affected by gravity, the force was added to the set of forces managed by the PhysicsManager itself. Since forces ignore the mass of the objects they act upon by default, this force will pull all objects towards the ground at standard gravitational acceleration. The next example shows how to apply a force to a single object.

Example 2: Rotary Thruster

Here is another example of applying forces to objects and the way in which the ForceNode alters the effect:

thruster=NodePath("thruster") # make a thruster for the jetpack
thruster.reparentTo(jetpackGuy)
thruster.setPos(0,-2,3)

thrusterFN=ForceNode('jetpackGuy-thruster') # Attach a thruster force
thrusterFNP=thruster.attachNewNode(thrusterFN)
thrusterForce=LinearVectorForce(0,0,4000)
thrusterForce.setMassDependent(1)
thrusterFN.addForce(thrusterForce)

an.getPhysical(0).addLinearForce(thrusterForce)

thruster.setP(-45) # bend the thruster nozzle out at 45 degrees

When this force is applied to the jetpack guy, it will push upwards and forwards. If the thruster's pitch and roll were controlled (say, by a joystick), then the jetpack could be moved around merely by changing the pitch and roll values; the ForceNode would inherit the orientation of the thruster and automatcially change the direction it pushes.

The effect that this thruster force has upon the jetpack guy should be dependent upon the mass of the system, so the setMassDependent call is used to factor mass into the acceleration analysis.


Panda3D Manual: Types of forces

Panda3D provides two types of forces that you can apply to an object.

LinearVectorForce

A LinearVectorForce treats the object as a point mass. It applies an acceleration in Newtons to the center of mass of the object it was added to. The direction of this force is relative to the orientation of the ForceNode that the LinearVectorForce was applied to.

Note: Since LinearVectorForce treats the object as a point mass, it is not possible to apply a rotation of any kind to your object. For rotational forces, see AngularVectorForce below.

Example:

lvf=LinearVectorForce(1,0,0)  # Push 1 newton in the positive-x direction
forceNode.addForce(lvf)  # Determine coordinate space of this force node
actorNode.getPhysical(0).addLinearForce(lvf) # Add the force to the object

AngularVectorForce

The AngularVectorForce applies a torque to the object it is attatched to. The acceleration is in Newtons, and AngularVectorForce may be treated in much the same way as LinearVectorForce. There are, however, some minor differences that that should be taken into account.

AngularVectorForce does not have a .setDependantMass() . The reason for this is simple: mass must be used in the torque calculations. As such, you will want to make sure your forces are sufficiently small or your masses are sufficiently large to keep your rotational velocity sane.

Example:

avf=AngularVectorForce(1,0,0) # Spin around the positive-x axis
forceNode.addForce(avf) # Determine which positive-x axis we use for calculation
actorNode.getPhysical(0).addAngularForce(avf) # Add the force to the object

One additional caveat with AngularVectorForce: Angular forces will not be processed on your object until an AngularIntegrator is added to the PhysicsManager.

Example:

from pandac.PandaModules import AngularEulerIntegrator

angleInt = AngularEulerIntegrator() # Instantiate an AngleIntegrator()
base.physicsMgr.attachAngularIntegrator(self.angleInt) # Attatch the AngleIntegrator to the PhysicsManager

Editor's Note: Each type of force should be given it's own page with much more in depth examples, and perhaps a small sample program.


Panda3D Manual: Notes and caveats

Here are some caveats, quirks, and behaviors to be aware of when working with the physics engine:

  1. You can add the same force to an object multiple times with repeated calls to addLinearForce or addAngularForce. The result will be that the total effect will be the effect of the force applied once times the number of times it is applied. Note, however, that to remove the force's effect on the object, you must call remove*Force the same number of times add*Force was called; each call to remove only removes one instance of the force. Of course, it is more efficient to use a single force with magnitude (n x # of copies) than to use the same force multiple times.

  2. If a NodePath that is controlled by an ActorNode also needs collision calculations done upon it, be sure to use the PhysicsCollisionHandler instead of CollisionHandlerPusher. More info can be found in the section on Collision Handlers. If you intend to use a PhysicsCollisionHandler to prevent a model from falling through a floor (for example, if the scene has gravity applied), be sure to look at the friction coefficient options on the PhyicsCollisionHandler.


Panda3D Manual: Using ODE with Panda3D

Panda3D also provides integration for the Open Dynamics Engine. This is a platform-independent open-source physics engine with advanced types and built-in collision detection. Panda3D provides support for ODE because sometimes Panda's limited built-in physics system might not always be enough to suit more complex needs.

From Panda3D version 1.5.3, ODE support is included in the downloadable binaries. Before that version, you would have to download the source packages and include ODE yourself.

This section will explain how to use this ODE system with Panda3D.

Table of Contents

  1. Worlds, Bodies and Masses
  2. Simulating the Physics World
  3. Attaching Bodies using Joints
  4. Collision Detection with ODE

More information

  • The API reference can list the classes and methods available (all of the classes are prefixed with Ode), although the function descriptions are lacking. It might also be useful to look at the PyODE API reference, which uses very similar class and method names.
  • Developers from Walt Disney VR Studio have held a lecture about using the ODE system with Panda3D. Click here to watch a video recording of it. (Recorded June 18, 2008)


Panda3D Manual: Worlds, Bodies and Masses

Worlds

To use the ODE physics system, you need to have an OdeWorld. A world is an essential component in the physics structure, it holds all your rigid bodies and joints, and controls global parameters, such as gravity, for the scene.

from pandac.PandaModules import OdeWorld
myWorld = OdeWorld()
myWorld.setGravity(0, 0, -9.81)

As you can see, the gravity is set to a downward vector with length 9.81. This value is the average gravity acceleration on Earth. If you want objects to fall faster or slower, (e.g. if your game plays on the Moon, where the gravity acceleration is 1.62 m/s²) you need to change this value, but in most cases you want to leave it around 9.81 m/s².

Bodies and masses

In physics space, the objects that matter are called bodies. In order to have something affected by physics, you need to create an OdeBody, and set an OdeMass on it.

An OdeMass does not just define how much an object weighs. You roughly have to specify a shape so ODE will know how the mass is divided over the body. Also, ODE will have to know either the density of the object or the mass.

In the following example the geometry is assumed to be a box-shaped object made of lead, and the box has a width, length and height of 1 meter.

from pandac.PandaModules import OdeBody, OdeMass
myBody = OdeBody(myWorld)
myBody.setPosition(somePandaObject.getPos(render))
myBody.setQuaternion(somePandaObject.getQuat(render))
myMass = OdeMass()
myMass.setBox(11340, 1, 1, 1)
myBody.setMass(myMass)

First, the position and quaternion are set of the body, this is directly copied from the NodePath's pos and quat; do note that when using getPos and getQuat, you need to get them in global coordinate space, this is done here by specifying render as first argument.

Then, a mass is set for the body. The first argument specified in the setBox call is the density of the object, the second is the dimensions (lx, ly, lz) of the box. Each material has it's own density, for example, water has a density of 1000 kg/m³, copper usually between 8920 and 8960 kg/m³. The value shown in the example above is the density for lead.

There are of course cases where you don't know the density (although it is easy to calculate), or when the object is not easy to fit in a box shape. OdeMass provides the following methods:

  • setZero(): Sets all the mass parameters to 0, meaning it will have no mass at all.
  • setSphere(density, radius): This specifies that the object's mass is spherical with the given radius.
  • setSphereTotal(total_mass, radius): Use this if you don't know the density but do know the total mass of the object.
  • setBox(density, lx, ly, lz): Use this for box-shaped objects.
  • setBoxTotal(total_mass, lx, ly, lz): The same as the former, but specifies the total mass instead of the density.
  • setCylinder(density, direction, radius, length): To be used for objects shaped like a cylinder.
  • setCylinderTotal(total_mass, direction, radius, length): Again the same cylinder, but specifies the mass instead of the density.
  • setCapsule(density, direction, radius, length): A capsule is similar to a cylinder, but has capped edges.
  • setCapsuleTotal(total_mass, direction, radius, length): Use this if you only have a mass and not the density.
  • add(other): Adds an other OdeMass object to this mass.
  • adjust(total_mass): Adjusts the mass parameters to have the specified mass.
  • rotate(matrix): Rotates the matrix using the specified Mat3 object.

More methods are listed in the API reference.

For more complex shapes, you might want to decompose the object into several simple ones, and use the add(other) method to add the masses together. If that still isn't enough, you might want to set the individual parameters of the mass using setParameters, which is not explained here because it that goes beyond the scope of this manual page. Note that the shape you set is not actually used for collisions: it's just used to roughly determine how the mass is divided in the object.


Panda3D Manual: Simulating the Physics World

Simulating the physics scene

Now, we've only had some theory so far, but haven't seen any simulation yet. To simulate, we will need to keep calling the quickStep(stepSize) function on the OdeWorld instance. stepSize is how much time should be simulated in one step. To get the most stable simulation, it is recommended that the stepSize be kept constant.

The problem with using the delta time of a task to step the simulation is that the time between tasks might not be consistent. To get around this, a deltaTime accumulator is used to figure out how many steps must be taken. When a step is performed, the world is iterated a few times, you can specify how much times the world is being iterated by calling the setQuickStepNumIterations(num) function on the OdeWorld instance.

Here's a small example showing a simple simulation showing an iron ball falling from a ridge:

from direct.directbase import DirectStart
from pandac.PandaModules import OdeWorld, OdeBody, OdeMass, Quat

# Load the cube where the ball will fall from
cube = loader.loadModel("box.egg")
cube.reparentTo(render)
cube.setColor(0.2, 0, 0.7)
cube.setScale(20)

# Load the smiley model which will act as our iron ball
sphere = loader.loadModel("smiley.egg")
sphere.reparentTo(render)
sphere.setPos(10, 1, 21)
sphere.setColor(0.7, 0.4, 0.4)

# Setup our physics world and the body
world = OdeWorld()
world.setGravity(0, 0, -9.81)
body = OdeBody(world)
M = OdeMass()
M.setSphere(7874, 1.0)
body.setMass(M)
body.setPosition(sphere.getPos(render))
body.setQuaternion(sphere.getQuat(render))

# Set the camera position
base.disableMouse()
base.camera.setPos(80, -20, 40)
base.camera.lookAt(0, 0, 10)

# Create an accumulator to track the time since the sim
# has been running
deltaTimeAccumulator = 0.0
# This stepSize makes the simulation run at 90 frames per second
stepSize = 1.0 / 90.0

# The task for our simulation
def simulationTask(task):
  global deltaTimeAccumulator
  # Set the force on the body to push it off the ridge
  body.setForce(0, min(task.time**4 * 500000 - 500000, 0), 0)
  # Add the deltaTime for the task to the accumulator
  deltaTimeAccumulator += globalClock.getDt()
  while deltaTimeAccumulator > stepSize:
    # Remove a stepSize from the accumulator until
    # the accumulated time is less than the stepsize
    deltaTimeAccumulator -= stepSize
    # Step the simulation
    world.quickStep(stepSize)
  # set the new positions
  sphere.setPosQuat(render, body.getPosition(), Quat(body.getQuaternion()))
  return task.cont

taskMgr.doMethodLater(1.0, simulationTask, "Physics Simulation")

run()

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Attaching Bodies using Joints

Joints

In most situations, you won't have just solid box-shaped or cylinder-shaped models, but for example a human character has multiple body parts which can all move in a different way. If you would make the entire character one solid body, you wouldn't be able to move them independently of each other, and if you made every body part a separate solid body, all the body parts would fall off since they are not attached to each other. This is where Joints come in.

Joints are basically used to attach bodies to each other, or to attach a body to the environment. There are several different kinds of joints: OdeHingeJoint, OdeBallJoint, OdeSliderJoint, just to name a few. (Check the API Reference for a more complete list).

OdeBallJoint example

To explain how joints work, look at the following example:

from direct.directbase import DirectStart
from direct.directtools.DirectGeometry import LineNodePath
from pandac.PandaModules import *

# Load the smiley and frowney models
smiley = loader.loadModel("smiley.egg")
smiley.reparentTo(render)
smiley.setPos(-5, 0, -5)
frowney = loader.loadModel("frowney.egg")
frowney.reparentTo(render)
frowney.setPos(-12.5, 0, -7.5)

# Setup our physics world
world = OdeWorld()
world.setGravity(0, 0, -9.81)

# Setup the body for the smiley
smileyBody = OdeBody(world)
M = OdeMass()
M.setSphere(5000, 1.0)
smileyBody.setMass(M)
smileyBody.setPosition(smiley.getPos(render))
smileyBody.setQuaternion(smiley.getQuat(render))

# Now, the body for the frowney
frowneyBody = OdeBody(world)
M = OdeMass()
M.setSphere(5000, 1.0)
frowneyBody.setMass(M)
frowneyBody.setPosition(frowney.getPos(render))
frowneyBody.setQuaternion(frowney.getQuat(render))

# Create the joints
smileyJoint = OdeBallJoint(world)
smileyJoint.attach(smileyBody, None) # Attach it to the environment
smileyJoint.setAnchor(0, 0, 0)
frowneyJoint = OdeBallJoint(world)
frowneyJoint.attach(smileyBody, frowneyBody)
frowneyJoint.setAnchor(-5, 0, -5)

# Set the camera position
base.disableMouse()
base.camera.setPos(0, 50, -7.5)
base.camera.lookAt(0, 0, -7.5)

# We are going to be drawing some lines between the anchor points and the joints
lines = LineNodePath(parent = render, thickness = 3.0, colorVec = Vec4(1, 0, 0, 1))
def drawLines():
  # Draws lines between the smiley and frowney.
  lines.reset()
  lines.drawLines([((frowney.getX(), frowney.getY(), frowney.getZ()),
                    (smiley.getX(), smiley.getY(), smiley.getZ())),
                   ((smiley.getX(), smiley.getY(), smiley.getZ()),
                    (0, 0, 0))])
  lines.create()

# The task for our simulation
def simulationTask(task):
  # Step the simulation and set the new positions
  world.quickStep(globalClock.getDt())
  frowney.setPosQuat(render, frowneyBody.getPosition(), Quat(frowneyBody.getQuaternion()))
  smiley.setPosQuat(render, smileyBody.getPosition(), Quat(smileyBody.getQuaternion()))
  drawLines()
  return task.cont

drawLines()
taskMgr.doMethodLater(0.5, simulationTask, "Physics Simulation")

run()

The part of the code that does the magic is this:

# Create the joints
smileyJoint = OdeBallJoint(world)
smileyJoint.attach(smileyBody, None) # Attach it to the environment
smileyJoint.setAnchor(0, 0, 0)
frowneyJoint = OdeBallJoint(world)
frowneyJoint.attach(smileyBody, frowneyBody)
frowneyJoint.setAnchor(-5, 0, -5)

This creates two joints, the first to attach the smiley to the environment, and the second to attach the frowney to the smiley. The attach() method on the joint is used to set the two bodies that are attached; you can replace either argument with None to attach them to the environment. The setAnchor method is used to set the anchor point for the joints.

At this image you can see how the joints are set up:

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Collision Detection with ODE

Collision Detection

There are two types of collision detection: the kind that immediately makes the objects bounce back on a collision, and the kind that instead of making the objects bounce back immediately creates control joints instead. The latter is the method the Open Dynamics Engine uses. Normally, you would use near callbacks to make the control joints and have the objects bounce back. However, Panda3D has an autoCollide feature that automatically does these things for you.

These are the steps needed to have your objects collide with each other:

  • Create an OdeSpace (explained below). Use setAutoCollideWorld(world) to let the OdeSpace know in which world you want to collide things.
  • Create an OdeJointGroup() to hold the contact joints. Use space.setAutoCollideJointGroup to let the space know in which OdeJointGroup you would like to store the contact joints.
  • Configure the surface table for the world.
  • Create ODE collision geometry for your bodies, e.g. OdeBoxGeom, OdePlaneGeom, etc. Be sure to set collide and category bitmasks on it using the setCollideBits and setCategoryBits methods. Assign it to your body using geom.setBody(body).
  • In your simulation loop, call space.autoCollide() before you call world.quickStep.
  • After using quickStep, you need to empty your OdeJointGroup using the empty() method.

Spaces

To be able to use collision detection, you will need to create an OdeSpace. There are three different kinds of space types you have to choose from, any one will work but each one is more optimized for a special kind of simulation.

  • OdeSimpleSpace. This is the most simple kind of space available. This does not perform any collision culling at all, that's why it is not preferred for a large number of objects. If you have a small amount of objects, however, you will most likely prefer an OdeSimpleSpace.
  • If you have more objects and a larger scene, you will want to use the OdeQuadTreeSpace. This uses a pre-allocated hierarchical grid-based AABB tree to quickly cull collision checks. It's exceptionally quick for large amounts of objects in landscape-shaped worlds.
  • Finally, theres the OdeHashSpace, which uses an internal data structure that records how each geom overlaps cells in one of several three dimensional grids. Each grid has cubical cells of side lengths 2**i, where i is an integer that ranges from a minimum to a maximum value. You can set this minimum and maximum value using the setMinLevel and setMaxLevel functions respectively, or you can use setLevels to set them all in one call.

Geometry

Geometry are the collision solids that you place in a space. These collision solids are separate from panda's own collision solids but there are similar collision solid types. The general code for creating geometry is OdeGeom(space, [parameters]), where space is the OdeSpace the geometry is being placed in. Parameters are dependent on which geometry solid you choose. The geometry types that you can choose from are:

Geometry | Shape | Initialization Parameters
OdeBoxGeom | Box | OdeBoxGeom(space, length, width, height)
OdeCappedCylinderGeom | Capsule | OdeCappedCylinderGeom(space, radius, length)
OdeCylinderGeom | Cylinder | OdeCylinderGeom(space, radius, length)
OdePlaneGeom | Infinite Plane | OdePlaneGeom(space, Vec4(<vector of plane normal>))
OdeRayGeom | Finite Ray | OdeRayGeom(space, ray_length)
OdeSphereGeom | Sphere | OdeSphereGeom(space, radius)
OdeTriMeshGeom | 3D Mesh | OdeTriMeshGeom(space, OdeTriMeshData)

To set the position and direction of an OdeRayGeom, you must call OdeRayGeom.set(Vec3(<position>), Vec3(<direction>)). The length of the direction vector is always set to the ray length specified during instantiation.

A trimesh geometry allows you to create collision geometry of an arbitrary shape from a 3d model. However collision detection with a trimesh is the most expensive and might be unreliable, for most applications you are better off approximating the shape with another collision solid. To get the OdeTriMeshGeom from a model requires two steps:

modelTrimesh = OdeTriMeshData(modelNodePath, True)
modelGeom = OdeTriMeshGeom(space, modelTrimesh)

If a geometry represents a physically dynamic object you can associate it with the dynamic body using odeGeom.setBody(body). This will automatically reposition the geometry with regard to the position of the related body in the OdeWorld.

Surfaces

Sufaces define the material a geometry is made of and the Surface Table defines how materials react with each other setting the bounce, friction etc. To set up the surface system, you must first initialize the surface table which is done with odeWorld.initSurfaceTable(numberOfSurfaces)

Once you have done that, you have to setup the parameters for collisions between two surfaces using odeWorld.setSurfaceEntry(surfaceId1, surfaceId2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen) The surfaceId's start from 0 so if you initialized your surface table with 3 surfaces, the surface ID's are 0, 1, 2.

These are what the rest of the parameters mean:

  • mu: This is the Coulomb friction coefficient. It means how much friction the contact has, a value of 0.0 means there will be no friction at all, while a value of OdeUtils.getInfinity() means the contact will never slip.
  • bounce: This is how bouncy the surface is. A value of 0.0 means it is not bouncy, a value of 1.0 gives a very bouncy surface.
  • bounce_vel: The minimum velocity a body must have before it bounces. If a body collides with a velocity lower than this value, it will not bounce off.
  • soft_erp: The error reduction parameter of the contact normal. This is used to simulate soft surfaces.
  • soft_cfm: The constraint force mixing parameter of the contact normal. This is used to simulate soft surfaces.
  • slip: The coefficient for the force-dependent slip. This makes it possible for bodies to slide past each other.
  • dampen: This is used to simulate a damping effect.

If you have multiple surfaces, you need to tell ODE which surface belongs to which geometry. You can assign surfaces to your geometry using odeSpace.setSurfaceType(geometry, surfaceId)

Example

This is an example of some random boxes falling down and colliding with the floor.

from direct.directbase import DirectStart
from pandac.PandaModules import OdeWorld, OdeSimpleSpace, OdeJointGroup
from pandac.PandaModules import OdeBody, OdeMass, OdeBoxGeom, OdePlaneGeom
from pandac.PandaModules import BitMask32, CardMaker, Vec4, Quat
from random import randint, random

# Setup our physics world
world = OdeWorld()
world.setGravity(0, 0, -9.81)

# The surface table is needed for autoCollide
world.initSurfaceTable(1)
world.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

# Create a space and add a contactgroup to it to add the contact joints
space = OdeSimpleSpace()
space.setAutoCollideWorld(world)
contactgroup = OdeJointGroup()
space.setAutoCollideJointGroup(contactgroup)

# Load the box
box = loader.loadModel("box")
# Make sure its center is at 0, 0, 0 like OdeBoxGeom
box.setPos(-.5, -.5, -.5)
box.flattenLight() # Apply transform
box.setTextureOff()

# Add a random amount of boxes
boxes = []
for i in range(randint(15, 30)):
  # Setup the geometry
  boxNP = box.copyTo(render)
  boxNP.setPos(randint(-10, 10), randint(-10, 10), 10 + random())
  boxNP.setColor(random(), random(), random(), 1)
  boxNP.setHpr(randint(-45, 45), randint(-45, 45), randint(-45, 45))
  # Create the body and set the mass
  boxBody = OdeBody(world)
  M = OdeMass()
  M.setBox(50, 1, 1, 1)
  boxBody.setMass(M)
  boxBody.setPosition(boxNP.getPos(render))
  boxBody.setQuaternion(boxNP.getQuat(render))
  # Create a BoxGeom
  boxGeom = OdeBoxGeom(space, 1, 1, 1)
  boxGeom.setCollideBits(BitMask32(0x00000002))
  boxGeom.setCategoryBits(BitMask32(0x00000001))
  boxGeom.setBody(boxBody)
  boxes.append((boxNP, boxBody))

# Add a plane to collide with
cm = CardMaker("ground")
cm.setFrame(-20, 20, -20, 20)
ground = render.attachNewNode(cm.generate())
ground.setPos(0, 0, 0); ground.lookAt(0, 0, -1)
groundGeom = OdePlaneGeom(space, Vec4(0, 0, 1, 0))
groundGeom.setCollideBits(BitMask32(0x00000001))
groundGeom.setCategoryBits(BitMask32(0x00000002))

# Set the camera position
base.disableMouse()
base.camera.setPos(40, 40, 20)
base.camera.lookAt(0, 0, 0)

# The task for our simulation
def simulationTask(task):
  space.autoCollide() # Setup the contact joints
  # Step the simulation and set the new positions
  world.quickStep(globalClock.getDt())
  for np, body in boxes:
    np.setPosQuat(render, body.getPosition(), Quat(body.getQuaternion()))
  contactgroup.empty() # Clear the contact joints
  return task.cont

# Wait a split second, then start the simulation
taskMgr.doMethodLater(0.5, simulationTask, "Physics Simulation")

run()

In this example, we're creating a random amount of boxes with a random orientation and position, assigning collision solids to them, and adding a tuple of the NodePath and the body to a list. This way we can easily keep track of all the boxes and loop through them to copy over the positions from the OdeBody to Panda's NodePath in the simulation loop.


Panda3D Manual: Motion Paths

Motion paths in Panda3D are splines created by a modeler that are then exported to egg files. These egg files are then imported into a program, and various nodes can then use the motion path for complex movement. A viable egg file for a motion path has the “curve” tag.

First, the Mopath and MopathInterval modules must be loaded. While motion paths come with their own play functions, a motion path interval allows for more functionality.

from direct.directutil import Mopath
from direct.interval.MopathInterval import *

With the modules loaded, the motion path is loaded much like an actor is loaded. A NodePath is created with the knowledge that it will be used for a motion path, and then the file is loaded.

myMotionPathName = Mopath.Mopath()
myMotionPathName.loadFile('File Path')

Finally, the motion path interval may be created, and played like any interval can. The interval requires not only the name of the motion path, but also the NodePath that will be affected by it.

myInterval = MopathInterval(myMotionPathName,myNodePath,name='Name')


Panda3D Manual: Timing

While the python time module can do a decent job of timing, panda has a built in timing system that allows for lag and cpu stutter.


Panda3D Manual: The Global Clock

The global clock is an instance of the ClockObject class. It gets imported into the global namespace when you load the DirectStart/Showbase modules.

To get the time (in seconds) since the last frame was drawn:

globalClock.getDt()

Another useful function is the frame time (in seconds, since the program started):

globalClock.getFrameTime()

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Networking

Panda3D contains support for networked games. This includes both a low-level stream based API, and a higher level distributed object API. The documentation in this section assumes some familiarity with the basic concepts of networking in general, and the IP protocol in particular.

The documentation on these features is still in development. Read the forums for the most up-to-date information.


Panda3D Manual: Downloading a File

To download a file while the game is running without blocking the connections one has to use HTTPClient and HTTPChannel objects. This will allow the file to be downloaded in the background using the downloadTask task.

self.http = HTTPClient()
self.channel = self.http.makeChannel(True)
self.channel.beginGetDocument(DocumentSpec('http://my.url/'))
self.rf = Ramfile()
self.channel.downloadToRam(self.rf)
taskMgr.add(self.downloadTask, 'download')

def downloadTask(self, task):
    if self.channel.run():
        # Still waiting for file to finish downloading.
        return task.cont
    if not self.channel.isDownloadComplete():
        print "Error downloading file."
        return task.done
    data = self.rf.getData()
    print "got data:"
    print data
    return task.done

You can also download to file

channel.downloadToFile(Filename(fileName))

The file channel can be quarried for further information while the game is running to get the current download state.

mbDownloaded = self.channel.getBytesDownloaded()/1024/1024
percentDownloaded = 100.*self.channel.getBytesDownloaded()/channel.getFileSize()


Panda3D Manual: Multifiles

This section describes that Multifiles are, how to use them and patch them. The multifiles can store multiple resources like models, textures, sounds, shaders, and so on, and Panda can load them from directly from the multifiles without having to unpack them first.

Multifiles can also be used in patching, zipping and encrypting your data. Multifiles are similar to ".egg" for python libraries or .jar files for java platform. Many games employ a similar concept of "data" file such as .upk for Unreal Engine and .pak for Quake Engine.

The key to Multifiles is that they are very easy to use. Almost none of the game loading logic has to change when switching to multifiles except for a few lines at the top to mount some multifiles into the Panda3D virtual file system.


Panda3D Manual: Creating Multifiles

Multifiles archives are archive files that store game resources. Think of it as a gaint zip file that stores optionally zips and encrypts your data files, but does not need to be extracted.

The multify program

The multify console program creates such files. You can get information about the commandline parameters by running multify with the -h option. This is how program describes itself:

Usage: multify -[c|r|u|t|x] -f <multifile_name> [options] <subfile_name> ...

multify is used to store and extract files from a Panda Multifile. This is similar to a tar or zip file in that it is an archive file that contains a number of subfiles that may later be extracted.

Panda's VirtualFileSystem is capable of mounting Multifiles for direct access to the subfiles contained within without having to extract them out to independent files first.

The command-line options for multify are designed to be similar to those for tar, the traditional Unix archiver utility.

If you want to load assets from a Multifile directly, you can "mount" it into the virtual file system:

from pandac.PandaModules import VirtualFileSystem, Filename
vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(Filename("foo.mf"), ".", VirtualFileSystem.MFReadOnly)

If you are having problems loading form multifiles you can list the complete contents of your .mf file with a command like:

mutify -tvf mymultifile.mf

Doing a sanity inspection like this can be useful to ensure that your assets are in the right place within the multifile.

Multifile objects

The Multifile class is designed for opening, reading and writing multifiles. You can open a new multifile by creating an instance of the class and calling the openRead method:

from pandac.PandaModules import Multifile
mf = Multifile()
mf.openRead("foo.mf")

The openRead method opens the multifile as read-only. If you want to make changes to it and write it back to disk, you will need to use the openReadWrite method. Also, there exists openWrite to create a new multifile.

If you have made important structural changes to a Multifile, it is recommended to rewrite the multifile using the repack() method. (This won't work if you've opened it using openRead.) If you are uncertain about whether it has become suboptimal, you can call needsRepack() which returns True if the Multifile is suboptimal and should be repacked.

To write it back to disk, you can use the flush() method which flushes the changes you've made to the multifile back to disk, or the close() method if you're done with the file.

To mount Multifile objects into the VirtualFileSystem without writing them to disk first, here's an example on how to mount them:

yourMF = Multifile()
#... now do something with yourMF

vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(yourMF, ".", VirtualFileSystem.MFReadOnly)

Subfiles

Files that are added to a multifile are called subfiles. You can add existing files to a multifile object using the addSubfile function. This function takes three arguments: the target filename, the existing source file and the compression level (1-9). There is also updateSubfile, which does the same thing but if the file already exists, only updates it if the content is different.

There are several other methods which operate on subfiles, which you can find in the API Reference. Here are a few examples of working with subfiles:

# Add an existing file with compression level 6
m = Multifile()
m.openWrite("foo.mf")
m.addSubfile("bar.txt", Filename("/tmp/bar.txt"), 6)
m.repack()
m.close()

# Open a multifile and replace a file
m = Multifile()
m.openReadWrite("foo.mf")
m.updateSubfile("bar.txt", Filename("/tmp/bar2.txt"), 9)
m.repack()
m.close()

# Open a multifile and extract all files smaller than 3kb
m = Multifile()
m.openRead("foo.mf")
for i in range(m.getNumSubfiles):
  if m.getSubfileLength() < 3 * 1024:
    m.extractSubfile(i, "/tmp/" + m.getSubfileName())

# Find, print and remove a file named bar.txt
barIdx = m.findSubfile("bar.txt")
if barIdx != -1:
  # It returns -1 if it doesn't exist
  print m.readSubfile(barIdx)
  m.removeSubfile(barIdx)
m.close()

Encryption

Multifiles can also encrypt your files with a password. To do so, you need to set the encryption flag and password using the setEncryptionFlag and setEncryptionPassword methods, before adding, extracting or reading multifiles. For instance, this code creates a multifile and adds an encrypted file to it:

m = Multifile()
m.openWrite("foo.mf")
m.setEncryptionFlag(True)
m.setEncryptionPassword("foobar")
m.addSubfile("bar.txt", Filename("/tmp/bar.txt"), 1)
m.repack()
m.close()

You can read encrypted multifiles the same way:

m = Multifile()
m.openWrite("foo.mf")
m.setEncryptionFlag(True)
m.setEncryptionPassword("foobar")
m.readSubfile("bar.txt")

You can check if a certain subfile is encrypted or not using the isSubfileEncrypted method, which takes the subfile index as parameter.

It is possible to have a multifile where different subfiles have different encryption, but you will not be able to mount it with the VirtualFileSystem or use it with the multify tool. To mount an encrypted file using the VirtualFileSystem, pass the password as parameter to the mount method:

from pandac.PandaModules import VirtualFileSystem, Filename
vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(Filename("foo.mf"), ".", vfs.MFReadOnly, "foobar")

To use encryption with the multify tool, run it with the -e option, which will prompt for a password on the command line. Alternatively, if you also specify the -p "password" option, you can specify it in the command instead of typing it at the prompt.


Panda3D Manual: Patching

Patching is a process of generating the difference between 2 files:

p = Patchfile()
p.build(Filename('version_1.mf'), Filename('version_2.mf'), Filename('v1_to_v2.patch'))

This will generate the file v1_to_v2.patch based on the differences between version_1.mf and version_2.mf. Then when you have version_1.mf and v1_to_v2.patch, you can produce version_2.mf with:

p = Patchfile()
p.apply(Filename('v1_to_v2.patch'), Filename('version_1.mf'))

There are other, more esoteric options, for limiting memory usage during patching, or for patching in increments instead of all at once so you can update a progress bar.

In the example above the files are named *.mf. This example suggests that you might be patching files in Panda's Multifile format (*.mf). The multifiles can store multiple resources like bams, textures, mp3's, and so on, and Panda can load them from directly from the multifiles without having to unpack them first.

The Patchfile object works on any arbitrary binary files; you don't need to limit yourself to just patching multifiles. However, the Patchfile does recognize a multifile and treats it as a special case; it can build patches for large multifiles without running out of memory, while building a patch for a large generic binary file might require so much memory it brings your system to its knees. (Applying patches doesn't require much memory, however.)

Patchfiles are not automatically compressed. You can do that yourself. Also, I recommend patching uncompressed source files for best results. (You can build patches against compressed source files, but the resulting patchfiles will tend to be much larger than the same patchfiles built against the original uncompressed files.)


Panda3D Manual: Loading resources from nonstandard sources

If you have want to load a resource from a different spot then a hard drive or inside a multifile, say for instance database or network packet you can using a StringStream.

Here is an example that reads and image into data and then uses StringStream to feed that data into the image.

data = open('my-image-file.png').read()
# send data over network or any other place and pass it on
p = PNMImage()
p.read(StringStream(data))
tex = Texture()
tex.load(p)

But, you can go one step further. Instead of just loading textures, models, sounds or other data one at a time this way, you can load an entire multifile, which as we learned in the previous section can contain any number of models, textures, sounds and other data.

data = open('my-multifile.mf').read()
stream = StringStream(data)
mf = Multifile()
mf.openRead(stream)
vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(mf, '/mf', 0)
smiley = loader.loadModel('/mf/smiley.egg')


Panda3D Manual: File Reading

General file reading in Panda is handled by the Virtual File System.

Although it presents the files and directories that it provides access to as a single, unbroken file system, it can in fact include files from multiple sources (such as Multifiles) in the hierarchy, regardless of the underlying structure.

This has the advantage of allowing one to access files and directories without worrying overmuch about where they actually reside, and even access Multifile archives as a directory hierarchy.

NB: While present in C++, on the Python side Panda does not offer file writing functionality, with the exception of writing to certain specialised file types. For general file writing, however, Python itself offers file-handling functionality, including potentially-useful features such as reading to the end of the line in a single call.

Thread-safe file I/O

In versions 1.6.0 and above, Panda3D offers a thread-safe replacement for the Python file module. You can find it in direct.stdpy.file. The interface is exactly the same as Python's, so it's safe to put this import above all the files where you want to use the "file" or "open" functions:

from direct.stdpy.file import *

This module reimplements Python's file I/O mechanisms using Panda constructs. This enables Python to interface more easily with Panda's virtual file system, and it also better-supports Panda's SIMPLE_THREADS model, by avoiding blocking all threads while waiting for I/O to complete.

Incomplete Section

Note: This section is incomplete.


Panda3D Manual: Threading

Note: In versions 1.6.0 and above, Panda3D provides a safe threading interface you can use, which works very similar to Python's threading modules. It is not recommended to use threading before version 1.6 or to use Python's threading modules, since your application will then become highly unstable.

Threading

If you want to use threading with Panda3D, it's not recommended to use Python's built-in threading modules, since you will most likely run into issues (for Panda3D is written in C++ and thus does not use the Python threading modules). However, Panda3D offers a threading implementation that is safe to use, by reimplementing Python's "thread" and "threading" modules, these work the same as the Python built-in threading modules but are actually safe to use with Panda3D.

You can get access to Panda3D's implementation of Python's "thread" module by importing the "thread" module from direct.stdpy:

# WRONG:
import thread
# RIGHT:
from direct.stdpy import thread

For the Python module "threading", Panda3D offers two equivalents, "threading" and "threading2", which you can find both in direct.stdpy also. The "threading" module implements the threading module with a thin layer over Panda's threading constructs. As such, the semantics are close to, but not precisely, the semantics documented for Python's standard threading module. If you really do require strict adherence to Python's semantics, see the threading2 module instead.

In fact, the threading2 module is a bald-face copy of Python's threading module from Python 2.5, with a few lines at the top to import Panda's thread reimplementation instead of the system thread module, and so it is therefore layered on top of Panda's thread implementation.

However, if you don't need such strict adherence to Python's original semantics, the "threading" module is probably a better choice. It is likely to be slightly faster than the threading2 module (and even slightly faster than Python's own threading module). It is also better integrated with Panda's threads, so that Panda's thread debug mechanisms will be easier to use and understand.

# WRONG:
import threading
# RIGHT:
from direct.stdpy import threading
# ALSO RIGHT:
from direct.stdpy import threading2 as threading

It is permissible to mix-and-match both threading and threading2 within the same application.

File I/O

In versions 1.6.0 and above, Panda3D also offers a thread-safe replacement for the Python file module. You can find it in direct.stdpy.file. The interface is exactly the same as Python's, so it's safe to put this import above all the files where you want to use the "file" or "open" functions:

from direct.stdpy.file import *

This module reimplements Python's file I/O mechanisms using Panda constructs. This enables Python to interface more easily with Panda's virtual file system, and it also better-supports Panda's SIMPLE_THREADS model, by avoiding blocking all threads while waiting for I/O to complete.

Compiling Panda3D with threading support

There are two different interfaces for threading which you can enable using the definitions HAVE_THREADS and SIMPLE_THREADS. The former is a full and heavy implementation of threading and compiling with that option will slow down the Panda3D build, unless you fully make use of the benefits that threading gives. The latter, however, is a more simple threading interface (introduced in Panda3D 1.6.0) that doesn't give you the runtime overhead HAVE_THREADS gives you. In Panda3D 1.6.0 and higher, SIMPLE_THREADS is enabled in the default build.

In the makepanda system, SIMPLE_THREADS is automatically defined for you. If you don't want it, or want to use the more heavy HAVE_THREADS instead, you will need to edit the makepanda.py script and change the definitions.

If you use ppremake, you will just need to define either in your Config.pp file.


Panda3D Manual: Sample Programs in the Distribution

The Panda3D Distribution includes quite a few sample programs. The following is a list of what's included, and what features of panda each sample demonstrates. If you are just learning Panda3D, take a look at the ones marked "Beginner."

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d. On OSX, you'll find them in /Applications/Panda3D/X.X.X/samples.

Sample Programs: Asteroids

  • Difficulty: Advanced
  • Creating tasks (routines that get called every frame)
  • Using 2D graphics

Sample Programs: Ball in Maze

  • Difficulty: Intermediate
  • Using the collision detector
  • Reading the mouse

Sample Programs: Boxing Robots

  • Difficulty: Intermediate
  • Loading animated actors and playing animations

Sample Programs: Carousel

  • Difficulty: Beginner
  • Relative positioning
  • Intervals (automatic movement)

Sample Programs: Cartoon Shader

  • Difficulty: Intermediate (or Advanced)
  • Enables per-pixel lighting
  • Explicit Shaders
  • Image Postprocessing
  • Render-to-Texture

Sample Programs: Chessboard

  • Difficulty: Intermediate
  • Selecting an object with the mouse

Sample Programs: Disco Lights

  • Difficulty: Intermediate
  • Using vertex lighting
  • Enables per-pixel lighting

Sample Programs: Distortion

  • Difficulty: Advanced
  • Creates a interesting shader effect where the entire screen is rendered into a texture

Sample Programs: Fireflies

  • Difficulty: Very Advanced
  • Deferred shading - a complex shader-based algorithm

Sample Programs: Fractal Plants

  • Difficulty: Very Advanced
  • Creates a 3D model without loading it from disk

Sample Programs: Glow Filter

  • Difficulty: Intermediate (or Advanced)
  • Enabling per-pixel lighting
  • Explicit Shaders
  • Image Postprocessing
  • Render-to-Texture

Sample Programs: Infinite Tunnel

  • Difficulty: Intermediate
  • Using fog to hide artifacts in the distance

Sample Programs: Looking and Gripping

  • Difficulty: Intermediate
  • Controlling a character's head procedurally
  • Causing a character to grip an object

Sample Programs: Media Player

  • Difficulty: Beginner
  • Loading a texture from an AVI file
  • Loading a sound from an AVI file
  • Synchronizing audio and video

Sample Programs: Motion Trails

  • Difficulty: Advanced
  • Captures contents of screen and reuses it later.

Sample Programs: Music Box

  • Difficulty: Intermediate
  • Playing sounds
  • GUI buttons and sliders

Sample Programs: Normal Mapping

  • Difficulty: Intermediate (or Advanced)
  • Enables per-pixel lighting
  • Explicit Shaders

Sample Programs: Particles

  • Difficulty: Advanced
  • Demonstrates the particle system

Sample Programs: Procedural Cube

  • Difficulty: Advanced
  • Creates a 3D model without loading it

Sample Programs: Roaming Ralph

  • Difficulty: Advanced
  • A character walks around a terrain
  • Uses collision system to detect height of terrain

Sample Programs: Shadows

  • Difficulty: Advanced
  • Explicit Shaders
  • Dynamic Shadows: shadow mapping

Sample Programs: Solar System

  • Difficulty: Beginner Tutorial
  • Tutorial structure shows development steps
  • Shows how objects can move relative to other objects

Sample Programs: Teapot on TV

  • Difficulty: Intermediate
  • Render-to-texture
  • Places rendered texture on model in scene

Sample Programs: Texture Swapping

  • Difficulty: Intermediate
  • Swaps textures on models to create interesting effects


Panda3D Manual: Sample Programs: Asteroids

The Asteroids Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program shows how to use Tasks. Tasks are functions that run during every frame of the program. Panda3D runs a number of tasks during any program, but additional tasks may be added. For this tutorial, tasks will be used to update the ship, asteriod and bullet positions, in addition to checking for collisions. To do this, all the data you need to know is how much time has passed and the velocity of each object.

NOTE: Adding lots of processor intensive tasks will have an adverse affect on the framerate/performance of your program.

For more information on tasks, please consult the Tasks section of the manual.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Ball in Maze

The Ball-in-Maze Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial will demonstrate how collision detection works in panda and provide a simple implementation of its use. Collisions are used extensively in modern games for a variety of purposes. At the most basic level, collision detection allows for two objects to bump into each other and react. This can be used to keep the objects from passing through each other but is not limited to that purpose. In this tutorial, collision detection be used to simulate the game of Labyrinth and will keep the ball within the bounds of the board. It will also be used to detect if the ball is over a hole.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Boxing Robots

The Boxing Robots Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial will show how to play animations on models aka "actors". We will base this world on a the popular game of "Rock 'em Sock 'em Robots".

Actors are specific models that come with some pre-generated animations. These animations are either available in the same egg file as the model or they are available as their own egg files. Only actors may play these animations, and actors also come with their own set of functions to use. Actors behave almost identically to models but use a different load command called Actor.Actor(). For more information on actors, please consult the Panda3D online manual.

The image above shows the tutorial in action. Use the A,S,K,L keys to make the robots punch and thus triggering their animations.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Carousel

The Carousel Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample progam demonstrates some uses for intervals in panda to move objects in your panda world.

Intervals are built in Panda3D functions that will let you change an attribute over time. In this tutorial, the position and texture attributes of various objects will be altered over time to give life to a carousel. The most common intervals for doing simple movement of objects are the posInterval and hprInterval. These intervals will change the position/orientation of an object (or node) over a given time and given displacement/rotation, respectively. Another interval we will look at is the LerpFunc interval. LerpFunc will call a function and give it a linearly interpolated value range over a specified time range. This is how we will oscilate pandas via a sin wave and alternate textures over time. For more information on intervals, please refer to the intervals section of the online manual.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Cartoon Shader

The Cartoon Shader Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial will show how to make a cartoon shader. It requires three separate shaders. On video cards that do not support shaders, the results will not be interesting.

Every frame, the scene is rendered into the main window using a "lighting shader." The lighting shader calculates lighting pretty much the same way OpenGL always does, but it adds a threshold function, so that the line between light and dark is a clear, discrete line.

Every frame, the scene is also rendered into an offscreen buffer using a shader that stores the surface normals into the buffer. Every frame, the contents of this buffer are copied to a texture. The result is a "surface normals texture." The lower-right corner of the screen contains a small quad showing the contents of the "surface normals texture."

The surface normals texture is applied to the main window using a fullscreen quad. A shader that detects edges is run on this quad. Wherever there is a discontinuity in the normals, the shader outputs black. Otherwise, it outputs a transparent pixel. This creates the lines around the model.

The model itself isn't even textured - it's vertex colored. Untextured models often look great in cartoon-shaded worlds.

The image below shows the tutorial in action.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Chessboard

The Chessboard Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program demonstrates how to use the mouse cursor interactively in your panda world. It is sometimes desireable to be able to interact with objects in a 3D scene via a mouse cursor. There are many solutions to this problem and this tutorial will cover a popular method. The method used in this tutorial will use collision detection to determine which objects the mouse cursor is hovering over. By using collision rays and shooting them from the lens (camera) with respect to the current position of the mouse cursor, we can hit objects that are being covered by the cursor.

Note that there's a much faster way to achieve this, using the Plane class. However, this sample demonstrates how it can be done using the Collision system.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Disco Lights

The Disco Lights Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program demonstrates lighting in panda. Lighting in panda can add a lot of depth into a scene. In panda, you can create point, directional, ambient and spot lights(for a detailed description of these lights, see the manual link below). Note that lights in panda will not cast shadows. They can create a dark and light side of an object but they cannot cast shadows from one object to another or onto the same object. Panda lights also act on object vertices only. The more polygons an object has, the more detailed the lighting will be on it. For more information on lighting, please consult the Panda3D online manual.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Fireflies

The Fireflies Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

A nature scene with 500 fireflies: how do you do a high-polygon scene with 500 lights, without a total performance collapse?

This sample program shows how to do Deferred Shading in Panda. You can have hundreds of lights, and all the lights affect all the models automatically. This is not expensive: the cost is only proportional to the number of actual lit pixels times the number of times each pixel is lit. That's why fireflies make a perfect demonstration --- you could never have 500 lights in a high-polygon scene without deferred shading. But perhaps even more importants is the elegance of a rendering algorithm that automatically connects lights to the appropriate objects without the need for LightAttribs.

To understand how deferred shading works, first consider how ordinary shading works in plain OpenGL. In most fairly simple cases, the lighting equation boils down to:

final = diffuse color * dot(light vector, surface normal) * attentuation(surface position, light position and orientation)

Where the attenuation function depends on the type of light. So anyhow, this equation has four inputs:

1. The diffuse color. 2. The surface normal. 3. The surface position. 4. The light parameters.

The idea behind deferred shading is that during the rendering process, you don't compute the final color. Instead, you store the values listed above in the framebuffer itself. Of course, you need a "fat framebuffer" to store all that data. In an image postprocessing step you scan the framebuffer and compute the final color.

Of course, that's a lot of data to store in the framebuffer. The first optimization is not to store the light parameters. It is not necessary to store light parameters because the light parameters don't vary from pixel to pixel --- they're constants.

Our second optimization involves surface position. Surface position can be inferred by calculating backward from the depth buffer. Each pixel on the screen represents a ray from the camera into the scene, and the depth value in the pixel indicates a distance along the ray. Because of this, it is not actually necessary to store surface position explicitly - it is only necessary to store depth values. Of course, OpenGL does that for free.

So the framebuffer now needs to store surface normal, diffuse color, and depth value (to infer surface position). In practice, most ordinary framebuffers can only store color and depth - they don't have any place to store a third value. So we need to use a special offscreen buffer with an "auxiliary" bitplane. The auxiliary bitplane stores the surface normal.

So then, there's the final postprocessing pass. This involves combining the diffuse color texture, the surface normal texture, the depth texture, and the light parameters into a final rendered output. The light parameters are passed into the postprocessing shader as constants, not as textures.

If there are a lot of lights, things get interesting. You use one postprocessing pass per light. Each pass only needs to scan those framebuffer pixels that are actually in range of the light in question. To traverse only the pixels that are affected by the light, just render the illuminated area's convex bounding volume.

The shader to store the diffuse color and surface normal is trivial. But the final postprocessing shader is a little complicated. What makes it tricky is that it needs to regenerate the original surface position from the screen position and depth value. The math for that deserves some explanation.

We need to take a clip-space coordinate and depth-buffer value (ClipX,ClipY,ClipZ,ClipW) and unproject it back to a view-space (ViewX,ViewY,ViewZ) coordinate. Lighting is then done in view-space.

Okay, so here's the math. Panda uses the projection matrix to transform view-space into clip-space. But in practice, the projection matrix for a perspective camera always contains four nonzero constants, and they're always in the same place:

A	0	0	0
0	0	B	1
0	C	0	0
0	0	D	0

The result is that the panda projection matrix boils down to these simple equations:

clipx = viewx * A
clipy = viewz * C
clipz = viewy * B + D
clipw = viewy

Look out, there has been a coordinate system change! In the scene graph, Z corresponds to "up", but in clip-space, Z is the depth value (and X,Y address a pixel).

After panda calculates clip-space coordinates, it divides by W. Finally, it rescales the depth-value:

screenx = clipx / clipw
screeny = clipy / clipw
screenz = clipz / clipw
depth = screenz * 0.5 + 0.5

So now we have some equations defining (clipx,clipy,clipz,clipw) in terms of (viewx,viewy,viewz), and (screenx,screeny,screenz) in terms of (clipx,clipy,clipz,clipw). It's basic algebra to solve these equations for (viewx, viewy, viewz) in terms of (screenx, screeny, screenz). Here, I have shown all my algebraic steps:

depth = screenz * 0.5 + 0.5
depth = (clipz / clipw) * 0.5 + 0.5
depth = ((viewy * B + D) / viewy) * 0.5 + 0.5
depth - 0.5 = ((viewy * B + D) / viewy) * 0.5
(2*depth - 1.0) = ((viewy * B + D) / viewy)
(2*depth - 1.0) * viewy = viewy * B + D
(2*depth - 1.0) * viewy + viewy * -B = D
(2*depth - 1.0 - B) * viewy = D
viewy = 0.5D / (depth - 0.5 - 0.5B)

screenx = clipx / clipw
screenx = (viewx * A) / viewy
screenx * viewy = viewx * A
screenx * (0.5D / (depth - 0.5 - 0.5B)) = viewx * A
(screenx * 0.5D) / (depth - 0.5 - 0.5B) = viewx * A
(screenx * 0.5D/A) / (depth - 0.5 - 0.5B) = viewx
viewx = (screenx * 0.5D/A) / (depth - 0.5 - 0.5B)

screeny = clipy / clipw
screeny = (viewz * C) / viewy
screeny * viewy = viewz * C
screeny * (0.5D / (depth - 0.5 - 0.5B)) = viewz * C
(screeny * 0.5D) / (depth - 0.5 - 0.5B) = viewz * C
(screeny * 0.5D/C) / (depth - 0.5 - 0.5B) = viewz
viewz = (screeny * 0.5D/C) / (depth - 0.5 - 0.5B)

To save our vertex and pixel shaders a little work, we can precompute these constants:

projx = 0.5D/A
projy = 0.5D
projz = 0.5D/C
projw = -0.5-0.5B

So, here are the equations in their final form:

viewx = (screenx * projx) / (depth + projw)
viewy = (1 * projy) / (depth + projw)
viewz = (screeny * projz) / (depth + projw)

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Fractal Plants

The Fractal Plants Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program isn't so much about fractals as it is about synthesizing a 3D model. Ie, this program does not include any 3D models on disk - it generates the trees procedurally.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Glow Filter

The Glow Filter Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation of the 'Basic' Version

There are two versions of this program: Basic and Advanced. The Basic version relies on Panda3D's automatic shader generation mechanisms and Panda3D's built in catalog of image-postprocessing filters. In other words, in the Basic version, Panda3D is doing all the hard work for us. In the Advanced version, we have written our own shaders and are doing our own image postprocessing.

So what the basic version demonstrates is really just how to turn on the shader generation, how to turn on a 'light ramp' (the essence of cartoon shading), and how to turn on a built-in image postprocessing filter.

Explanation of the 'Advanced' Version

The advanced version shows how to use Cg shaders to implement a "glow" postprocessing filter. The demo will only work on video cards that support shaders.

If you examine the texture for this model in photoshop, you'll see that it has an alpha channel. I'm not using the alpha channel to represent transparency. Instead, the alpha channel is a "glow map." Where the alpha is 1, the model glows, in places where the alpha is zero, it does not glow.

The scene is rendered to the main window in the normal way. It is also rendered to an offscreen buffer using a special shader that pays attention to the glow map (in the alpha channel). Wherever the glow map is 1, it renders the model's normal color. Wherever the glow map is 0, it renders black. This offscreen buffer is called the "glow buffer".

The contents of the glow buffer are then rendered into another offscreen buffer, the "blur x" buffer, using a shader that does a one-dimensional horizontal blur. The contents of the "blur x" buffer are then rendered into a third offscreen buffer, the "blur y" buffer, using a shader that does a one-dimensional vertical blur. Taken together, the two one-dimensional blurs add up to a two-dimensional blur. The result is that the glowing lines that were rendered into the glow buffer are smeared outward, forming little halos.

In a final step, the blurred texture is applied to the main window using additive blending - ie, the halos are added to the scene.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Infinite Tunnel

The Infinite Tunnel Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

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 be 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 way fog in Panda works is by coloring the models in the world. Fog cannot exist without any objects in the scene. The parts of the object which are furthest from the camera will get gradually colored by the color specified for the fog. If this color blends in with the background, it will appear as it the object is being occluded by a 'cloud' of fog.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Looking and Gripping

The Looking and Gripping Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial will cover how you can manipulate actor joints in Panda. Joints are essentially bones used by a 3D package (Maya, 3D Studio, XSI, etc) to deform a character. The model is bound to these joints and moving these joints will cause the model to deform. For example, rotating the shoulder joint will move the arms, rotating the leg joints will bend the legs. In Panda, these joints are normally not accessable. However, by controlling them with controlJoint, you can manipulate the orientation of that joint in Panda independent of its preloaded animations. You could even use a model without premade animations and manipulate the joints manually in Panda.

Specifically in this tutorial, 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 using exposeJoint and use its positional data to "attach" objects that the character can hold. By using exposeJoint, the object will stay in the character's hand even if the hand is moving through an animation.

NOTE: Models with joints will differ greatly from each other depending on how they were made and who made them. You may find that a certain model's joints are not oriented logically or practically. Also beware that joints may have limits on how far they can twist. Going beyond a joints limits may cause strange and unwanted deformations.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Media Player

The Media Player Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program shows how to play a movie in panda. This is particularly useful for cut-scenes, but movies can also be used as animated textures on the surfaces of objects. This sample shows both video and audio, and how to synchronize them properly.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Motion Trails

The Motion Trails Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial shows how to create motion trails in panda. The basic process is this: after rendering the scene, you copy the scene into a texture. You apply that texture to a full-screen quad. You integrate that quad into the rendering of the next frame. That creates a feedback loop.

The basic steps are: create a texture that will hold the contents of the main window. Tell the main window to copy is output into this texture using setupRenderTexture. Obtain a full-screen quad containing this texture using getTextureCard. Position this quad in the scene.

You can get a lot of different effects by simply moving the quad a little bit. By putting it behind the actor, the actor is fully visible, and surrounded by trails. By putting it in front of the actor, the trails overlap the actor. By rotating the quad slightly, you get a whirlpool. By offsetting it or scaling it, you can cause the trails to move away from the actor. You can colorize it, adjust its transparency, and otherwise tweak it in a number of ways.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Music Box

The Music Box Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

Music Box is a simple program that allows you to play sounds. It demonstrates playing sounds, and creating GUI buttons and sliders.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Normal Mapping

The Normal Mapping Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

Note: this explanation corresponds to Panda3D version 1.5.0. It does not match the older version of Panda3D, version 1.4.2.

Normal Mapping is a technique to make textured surfaces appear more "lifelike" by having them respond to changing lights and viewing angles. As the light moves around, it reflects off of simulated "bumps" on the surface (the bumps are not real, the polygons are flat, but they look real because of how they reflect the light).

Panda's built-in Normal Mapping Capabilities

Normally, to use fancy rendering techniques like normal mapping, you would have to write special GPU code called "shaders." However, in Panda3D version 1.5.0, panda gained the ability to synthesize many shaders for you. What that means is that you can use many modern rendering techniques (including normal mapping) without having to know shader programming at all.

Look in the models subdirectory for a model called "abstractroom". It was created in Maya, and a normal map was applied in Maya. The maya exporter was used to convert this into panda's model file format, abstractroom.egg. During the conversion process, the maya exporter noticed the normal map and included information about it in the egg file: if you examine the egg file with a text editor, you will see the references in there. You can view the model using panda's model viewer:

pview abstractroom.egg

After pview loads, press 'c' to center the model, 'l' to turn on lighting, and 'p' to turn on per-pixel lighting. Presto, you will see the effects of normal mapping.

If you are doing this in a program, the steps are basically the same: load a model which already has normal maps, set up some lights, and turn on per-pixel lighting. The sample program Tut-Normal-Mapping-Basic shows this process.

Doing it the Hard Way

The problem with panda's shader synthesis capabilities is that they can only synthesize the shaders that people usually need. If what you need is esoteric, you'll have to write your own shader.

There are two difficulties associated with writing your own shader. The first is that you need to know shader programming. Teaching shader programming is beyond the scope of this tutorial. Panda uses the Cg shader language, which you can learn elsewhere. Furthermore, normal mapping shaders are more complex than most. The mathematics involved in normal mapping are quite complex, which puts writing a normal mapping shader even further outside the scope of this tutorial.

The second problem with writing your own shader is that shaders must be hardwired to a very limited set of circumstances. The shader included with this sample program is hardwired for models with a single diffuse texture and a single normal map. It is hardwired for absence of vertex colors. It implements one white point light and one white ambient light. Changing any of these parameters would require a different shader.

This is not because the shader was written lazily. It is because shaders are by nature specialized to a specific combination of model attributes. This makes it rather awkward to write a substantial program that uses manually-written shaders. This is a big part of the reason that panda now supports shader synthesis - the shader generator can handle almost any kind of model.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Particles

The Particles Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

In this tutorial, you will learn how to create particle effects in Panda3d. Particle effects are systems simulated by the movement of many small objects, or particles. They are commonly used to simulate phenomenon like explosions, sparks, fire, smoke, and steam to name a few. Particle effect systems are very compilcated. It's not likely that you would be able to create any good systems just by reading a list of available parameters. You need to be able to see and manipulate the systems to really get results. Fortunatly, there is a tool built into Panda for visualising particle effects. particlePanelLoader.py contians the code needed to run the Particle Panel. Run it, and you should see a black screen with a default particle effect, and a second window listing the parameters for that effect.

There are too many parameters in the Particle Panel to go over them all, however, the main sections are:

  • System: Details on how many particles are allowed and how often they are created.
  • Factory: Sets the properties of particles when they are created.
  • Emitter: Sets an area that particles can be created in and their initial velocities.
  • Renderer: How the particles will be drawn on screen (Hint: Most good particle effects are sprites with a small texture at very low alpha (around .1) heavily blending between each other.).
  • Forces: Sets forces that can be used to move existing particles.

Play around with the panel for a while, and create something you like. You can then save the settings to a file with the extension ptf. You can also load that ptf later and continue to modify it. Some sample ptfs are included for inspiration. The Panel does need some improvements because it is somewhat buggy. It can crash, especially when dragged around, and forgets some settings when they are saved out to a ptf. It forgets to include the extention of the texture for a SpriteRenderer, so you need to open the file by hand and correct the loader.loadTexture command or it will not load. It also forgets to record the axis masks on forces.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Procedural Cube

The Procedural Cube Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This program synthesizes a 3D model of a cube. If you need to see in the minimum number of lines of code how to synthesize a model, this is the sample you want.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Roaming Ralph

The Roaming Ralph Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial shows a character walking on uneven terrain. He rises and falls with the contours of the terrain, stops when he runs into a tree or a rock, and won't walk off the edge. The camera follows him, also moving intelligently over the terrain, and you have some control over the camera angle. The character's body animates sensibly according to his motion.

Much of this is implemented using collision rays to detect the height of the terrain and the presence of obstacles.

Caution: this program uses an extremely inefficient method for detecting the height of the terrain. There is a much better way to do it, but we have not had time to correct the sample program.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Shadows

The Shadows Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This program shows how to use shadow maps in Panda3D. This is a technique for dynamic shadowing. Unfortunately, Panda3D does not yet support doing this fully automatically, so some manual setup is needed to get dynamic shadows. Adding automatic dynamic shadows is high on the priority list of the Panda3D developers.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Solar System

The Solar System Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This sample program shows planets orbiting around the sun, and a moon orbiting one of the planets. If you just stare at the moon, you'll realize that its motion is a spiral:

The motion of the moon is only simple if you think about it in relative terms: the moon is moving in a circle relative to the planet, and the planet is moving in a circle relative to the sun.

This panda program shows how to implement relative movement and relative positioning.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Teapot on TV

The Teapot on TV Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

In this tutorial, a spinning teapot is rendered into a texture. Then, the texture (which contains a picture of a teapot) is applied to the face of an animated TV-headed character, who dances.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: Sample Programs: Texture Swapping

The Texture Swapping Sample Program

To run a sample program, you need to install Panda3D. If you're a Windows user, you'll find the sample programs in your start menu. If you're a linux user, you'll find the sample programs in /usr/share/panda3d.

Screenshots

Explanation

This tutorial will show how to use a sequence of textures on an object to achieve a specific effect. Popular uses of this technique are:

  • Animated sprites
  • Moving shadows

The basic principle of this tutorial is that even though a model file contains a reference to a texture file, the actual texture applied to a model can be changed in panda. Textures are just image files and they can be loaded in panda via loader.loadTexture. Each model loaded into Panda has a texture attribute that can be changed. Not only can this feature be used to change the texture on a model from one to another but it can also be used play an animated sequence on the model by using a sequence of textures and swapping them at regular intervals.

This tutorial will also demonstrate the billboard function which orients an object to always face the the camera. This is useful for 2D sprites in a 3D world.

Above is a screenshot of the tutorial running with two animated sprites. One is a flying duck and the other is an explosion. Only the explosion has the billboard effect applied to it. As the camera moves, the explosion will turn to face the camera while the duck will not.

Back to the List of Sample Programs:

Sample Programs in the Distribution


Panda3D Manual: DirectStart

An overview of DirectStart

If you are already up to speed with Python, the following code may look like a black box:

import direct.directbase.DirectStart
run()

This section provide some key details on the function of this import statement.

DirectStart creates and instance of the class ShowBase, which itself inherits DirectObject. Under Linux, the relevant file can be found:

/usr/share/panda3d/direct/src/showbase

On Windows, the code is (by default) located in:

C:\Panda3D-1.X.X\direct\src\showbase

An important item created meanwhile is a base task manager as taskMgr. The function run() is in fact a single call to launch this task manager.

Apparent global variables created

Some key variables used in all Panda3D scripts are actually attributes of the ShowBase instance. Here is the relevant code making these attribute global in scope:

        __builtin__.base = self
        __builtin__.render2d = self.render2d
        __builtin__.aspect2d = self.aspect2d
        __builtin__.render = self.render
        __builtin__.hidden = self.hidden
        __builtin__.camera = self.camera
        __builtin__.loader = self.loader
        __builtin__.taskMgr = self.taskMgr
        __builtin__.jobMgr = self.jobMgr
        __builtin__.eventMgr = self.eventMgr
        __builtin__.messenger = self.messenger
        __builtin__.bboard = self.bboard
        __builtin__.run = self.run
        __builtin__.ostream = Notify.out()
        __builtin__.directNotify = directNotify
        __builtin__.giveNotify = giveNotify
        __builtin__.globalClock = globalClock
        __builtin__.vfs = vfs
        __builtin__.cpMgr = ConfigPageManager.getGlobalPtr()
        __builtin__.cvMgr = ConfigVariableManager.getGlobalPtr()
        __builtin__.pandaSystem = PandaSystem.getGlobalPtr()

This is where the commonly used objects base, render, render2d, camera, messenger, taskMgr are created. There are more such variables added to __builtin__. Consult the code if you want to find them all.

Note that this way of exposing these attributes make them available globaly even though these will not be returned by a call to the built-in function dir().


Panda3D Manual: Debugging

This section documents the various debugging tools to use to find problems in a Panda3D program.


Panda3D Manual: Log Messages

Panda periodically outputs log messages for debugging purposes. A typical log message might look like this:

:util(warning): Adjusting global clock's real time by -3.3 seconds.

The first part of the message, util, is the name of the module that generated the message. The second part, warning, indicates the severity. The severity levels are, in decreasing order: fatal, error, warning, info, debug, and spam. The panda configuration file (Config.prc) contains these directives:

notify-level warning
default-directnotify-level warning

Directives like these tell panda which messages to show, and which to suppress. In the default configuration (shown above), all messages whose severity is warning or above are shown, all messages whose severity is less are suppressed.

It is interesting and educational to change the configuration to this:

notify-level spam
default-directnotify-level spam

If you do this, panda will print out vast amounts of information while it runs. These informational messages can be useful for debugging. However, there are so many print-statements that it slows panda down to a crawl. So it may be desirable to tell panda to narrow it down a little. The way to do that is to name a particular module in the panda config file. For example, you might do this:

notify-level warning
notify-level-glgsg spam
default-directnotify-level warning

This tells panda that module "glgsg" should print out everything it can, but that every other module should only print warnings and errors. By the way, module glgsg is a particularly interesting module to investigate. This is the module that invokes OpenGL. If you tell it to spam you, it will tell you what it's setting the MODELVIEW and PROJECTION matrices to, and lots of other interesting information.

Generating your own Log Messages

You can use the Notify class to output your own log messages.

FINISH THIS SECTION.

Redirecting Log Messages to a File

If you wish, you can redirect all of panda's log messages into a file. The following snippet will do the trick:

nout = MultiplexStream()
Notify.ptr().setOstreamPtr(nout, 0)
nout.addFile(Filename("out.txt"))


Panda3D Manual: The Python Debugger

Python is a very powerful interactive and interpreted language. Python's development cycle is very fast. Often the most effective way to to debug is to output relevant information. Having said that, there are many ways to enhance productivity with knowledge of good debugging techniques.

Using python -i mode

Python programs may be developed and tested with the help of the interactive mode of the Python interpreter, which, allows program components to be debugged, traced, profiled, and tested interactively. When invoking python with -i, this ensures that an interactive session of python is invoked. With regards to Panda3D this requires a little explanation. Panda3D programs typically have a command called run() to start rendering, so here's one way to start an interactive session. On the command prompt type:

python -i myPandaFile.py

After Panda3D has loaded, make sure the command window has focus and type Ctrl-C. This will show a python prompt like so:

>>>

Now on the command prompt you can execute any python commands, related or unrelated to your Panda3D program. This is useful for looking at information at that specific point in time. You could even change that information for that running instance of the program.

pdb

Python offers hooks enabling interactive debugging. Module pdb supplies a simple text-mode interactive debugger. It supports setting (conditional) breakpoints and single stepping at the source line level, inspection of stack frames, source code listing, and evaluation of arbitrary Python code in the context of any stack frame. It also supports post-mortem debugging and can be called under program control.

The debugger's prompt is "(Pdb) ". There are many ways to enter the debugger. Typical usage to run a program under control of the debugger is:

>>> import pdb
>>> import <mymodule>
>>>  pdb.run('mymodule.test()')
> <string>(0)?()
(Pdb) continue
> <string>(1)?()
(Pdb) continue
 NameError: 'spam'
 <string>(1)?()
 (Pdb)

Detailed information about pdb can be found here. In addition to pdb, python also has two modules called inspect and traceback. inspect supplies functions to extract information from all kinds of objects, including the Python call stack and source files. The traceback module lets you extract, format and output information about tracebacks as normally produced by uncaught exceptions.


Panda3D Manual: Running Panda under the C++ Debugger

These instructions show how to compile and run panda from inside the Microsoft Visual Studio debugger.

Before you Begin

The first step is to download the panda source code and compile it. The instructions can be found in the section Building Panda from Source. Be sure to compile with the Optimize setting of 1, otherwise, Visual Studio will not be able to debug properly. Once you have compiled panda, start up visual studio, and ask it to create a new project:

There are four pieces of information you need to enter into the new project dialog:

  • Tell it to make a "C++ project"
  • Tell it to use a "Makefile"
  • Tell it the name of the project, "debugpanda"
  • Tell it where the panda source tree is (ie, "c:\panda3d-b")

Click ok, and confirm. You have now created the project, and the project is open for editing. You now need to access the "solution explorer:"

Normally, you can see the solution explorer in the upper-right corner of visual studio. It should show your project name (debugpanda). The word "debugpanda" needs to be highlighted - if it is not, click on it:

Now that your project is selected, you can edit its project properties:

The project property dialog initially looks like this. It contains three subpanels, the "General" panel, the "Debugging" panel, and the "NMake" subpanel. You can see these three subheadings in the left pane:

There is nothing to fill in on the general panel, so switch to the debugging panel. You need to fill in the command name, the command arguments, and the working directory. For now, we will ask it to debug the Actors/Robots sample program. Since visual studio puts the project file in a subdirectory, the paths need to be preceded by ".." to get to the root of the panda source tree:

Finally, switch to the "NMake" panel. Here, you can tell it what the command is to recompile panda. I use a bat file "mkp.bat" to compile panda. Since the project file is in a subdirectory, the command needs to be preceded by "cd .." in order to get back to the root of the panda source tree:

Visual studio now knows how to run panda, and how to compile it. You can run your program (in this case, the "Actors/Robots" tutorial) by clicking on the Debug menu:

You can rebuild panda at any time by clicking on the "Build" menu:

Now that you are running in the debugger, you can open any panda source file and set a breakpoint, or examine data. Of course, it may be advantageous to learn how to use the python debugger as well as the C++ debugger.


Panda3D Manual: Performance Tuning

NOTE: THIS PAGE OF THE MANUAL IS STILL BEING WRITTEN.

Modern Performance Tuning Introduction

Inexperienced game programmers, when they see that their programs are running slow, often react by trying to reduce the number of polygons. This almost never makes any difference whatsoever. Back in the mid-90's, reducing polygon counts was a reasonable strategy to make games faster. That strategy just doesn't work any more. The reason for this is that video card manufacturers have been increasing the polygon limits of their video cards by leaps and bounds. In fact, they've raised the polygon limit so high that you almost never hit that limit any more: you usually hit some other limit first.

That's the key to modern performance tuning: knowing all the limits of the machine other than the polygon limit, and figuring out which of those limitations you've run into. In other words, the key to modern performance tuning is diagnosis.

Most Common Problem: Silly Little Bugs

When people start performance tuning, they often start with the assumption that there's something serious wrong with the engine, or the design of the game. In my experience, however, 90% of the time the problem is that the game contains a silly (but destructive) bug. Here are some examples of bugs that I have seen:

  • A game had a text readout in the corner of the screen, which got updated every frame. But they updated the text by creating a new text object, and forgot to remove the old text object. So the corner of the screen contained thousands of layers of accumulated messages.
  • A shooter allowed you to fire bullets from your gun. But they forgot to remove the bullet after it collided with a wall. As a result, the bullets just kept going through the wall, and into outer space. Every bullet you or anyone else had ever fired was still flying through space, and the animation system was working like crazy to animate tens of thousands of bullets.
  • Think of another example here.

I cannot emphasize this too much: do not redesign your game, until you are sure that the problem isn't a typo! Imagine how irritated you would be if you wrote an MMO, and then spent six months reengineering it to make it faster, only to discover that the entire performance problem was an off-by-one error in a minor subroutine.

Catching performance problems that are bugs can be tricky. The place to begin is using the same performance diagnostics that you would use for any other problem. However, you will usually find a red flag somewhere: if the performance monitoring tools say that you're rendering 50,000 models, but you only count 50 models on the screen, you're dealing with a bug. You need to be alert enough not to discount such red flags. If you see a stat that looks suspicious, don't assume that the performance monitoring tool is telling you the wrong thing --- assume that there's a bug in the code.

How Fast Should it Run?

One of the things that makes performance tuning difficult is that you need to find things that are running slower than they "should" - but how do you know how fast something "should" run? Experienced game programmers have a gut feel for what their video card should be capable of, but inexperienced ones often don't really know what to expect. This makes performance tuning that much harder.

However, you have an advantage. We have a collection of sample programs demonstrating Panda3D features. It is easy to turn on the frame-rate meter to see how fast these samples run. The screenshots in the manual contain frame-rates, taken with a Radeon x700. That should give you a baseline. It is even more informative to turn on the frame-rate meter to see what your video card can deliver.

Video Synchronisation
Panda3D sometimes caps the framerate to not exceed the monitor's refresh rate: this is called video synchronisation. Panda3D knows that since the monitor can't refresh faster (the monitor refresh rate is usually between 60 and 85 Hz), everything above that rate is wasted, so Panda3D will not refresh faster than the monitor's refresh rate. To disable this and be able to see the 'true' framerate, set the config variable sync-video to #f in your Config.prc.

List of Common Performance Issues

Here is a list of things that can go wrong, roughly in order from most likely to least likely. Each of these has a section to explain it in greater detail.

  • Performance Issue: Too Many Meshes. A well-made typical 3D model contains one mesh. Huge 3D models, such as the model of an entire dungeon or an entire island, might contain multiple meshes. 3D models created by inexperienced modelers can contain dozens of meshes. Most video cards can render about 300 meshes total for the entire scene. Panda3D contains tools to coalesce multiple meshes into one, but they aren't fully automatic.

  • Performance Issue: Too Many State Changes. The state of an object is the sum of its color, material, light, fog, and other attributes. It can be expensive, for a variety of reasons, to have too many different states in your scene. It is better if many objects share the same state.

  • Performance Issue: Too Many Text Updates. If you have lots of text in your game that gets updated every frame, it will often take a long time for Panda to keep regenerating the text. You need to minimize the amount of text to regenerate per frame.

  • Performance Issue: Too Many Pixel Shader Instructions. If you are using per-pixel lighting, or hand-written shaders, you need to be conscious of how long your shaders are. Adding one pixel shader instruction can slow the video card a lot. Adding a texture lookup can slow it even more. Professional pixel shaders contain 20-30 assembly-level instructions.

  • Performance Issue: Excessive Fill. The fill rate of the video card is number of pixels it can render per second. Objects that are occluded (behind other objects) still consume fill rate. The total fill-consumption of the scene is the total screen real estate of all objects, including the occluded ones. Particles, in particular, can consume fill-rate like crazy, especially if the camera gets close to the particles.

  • Performance Issue: Memory Full. A floating point-number takes four bytes. Just one vertex contains (X,Y,Z), and a normal, and a texture coordinate. An RGBA color takes four bytes, so a 1024x1024 texture is four megabytes. Do the math, and you'll see how fast it all adds up.

  • Performance Issue: Python Calculation. Python is a very slow language. Most Panda3D programs only run a few thousand lines of python per frame, since all the real work is done in C++. Sometimes, though, you need to do some complex calculation, and Panda3D just doesn't contain any C++ code to do it for you. In that case, trying to write the calculation in python can cause problems. You may need a C++ plug-in.

  • Performance Issue: Failure to Garbage Collect. It's easy to get used to the fact that Python's garbage collector can automatically clean up Panda3D data structures. Unfortunately, there are a few structures that can't be cleaned up automatically. You need to know what they are, or you may end up with a leak.

  • Performance Issue: Collision System Misuse. The collision system can detect most types of collisions very rapidly. However, it is possible to construct situations that the collision detection system just can't handle. Know what it's good at, and what it's not.

  • Performance Issue: Motherboard Integrated Video. Motherboard video is very misleading. The chips have names like "Radeon" and "GeForce" that we have come to associate with speed, but these chips are an order of magnitude slower than real video cards. Programming for these chips requires special consideration.

  • Performance Issue: Too Many Polygons. This is at the bottom of the likelihood list, but it can still happen. Usually this happens in combination with something else, e.g. if you have a large vertex shader, performance can be drastically reduced for each vertex you add.

  • Performance Issue: Miscellaneous. There are a lot of small things that have a surprisingly large impact on performance. For instance, printing messages on the console can be very slow in Windows. This section lists a number of miscellaneous things that can bog you down.

    A recommended read about performance tuning is also chapter 28 of the book GPU Gems:
    http://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch28.html

    Incomplete Section

    Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Basic Performance Diagnostics

Introductory Performance Diagnostics

In Panda3D, the "big gun" of performance analysis is called pstats. This program gives you real-time diagnostic analysis of a running Panda3D virtual world broken down into hundreds of different categories.

But sometimes, when you've just encountered a problem, you don't want that much information. Sometimes, you just want a simple question answered, like "how many meshes do I have," or "is this a fill-rate problem?" For simple questions like that, there are lots of things you can do to get a quick-and-dirty answer.

The Frame-Rate Meter

The frame-rate meter doesn't tell you why your program is running slow, but it does have one important purpose: it's a lightweight and unobtrusive utility that you can leave on throughout the entire development process. This is valuable in that it gives you immediate feedback when you do something inefficient. To turn on the frame-rate meter, put this in your config file:

show-frame-rate-meter #t

Or, if you want to have it set at run-time:

base.setFrameRateMeter(True)

The Scene Analyzer

If a scene needs to be rendered and has multiple nodes, Panda has to send each node to the graphics hardware as a separate batch of polygons (because the nodes might move independently, or have different state changes on them). Modern graphics hardware hasn't made any improvements recently in handling large numbers of batches, just in handling large numbers of polygons per batch. So if a scene is composed of a large number of nodes with a small number of polygons per node, the frame rate will suffer. This problem is not specific to Panda; any graphics engine will have the same problem. The problem is due to the nature of the PC and the AGP bus.

For example, though your graphics card may claim it can easily handle 100,000 polygons, this may be true in practice only if all of those polygons are sent in one batch--that is, just a single Geom. If, however, your scene consists of 1,000 nodes with 100 polygons each, it may not have nearly as good a frame rate.

To inspect performance the nodePath.analyze() method is extremely useful. For example:

render.analyze()

The response is printed to the command window. It may look something like this:

371 total nodes (including 43 instances).
21 transforms; 16% of nodes have some render attribute.
205 Geoms, with 94 GeomVertexDatas, appear on 133 GeomNodes.
21665 vertices, 21573 normals, 21557 texture coordinates.
35183 triangles:
   3316 of these are on 662 tristrips (5.00906 average tris per strip).
   0 of these are on 0 trifans.
   31867 of these are independent triangles.
0 lines, 0 points.
99 textures, estimated minimum 326929K texture memory required.

For a scene with many static nodes there exists a workaround.

If a scene is composed of many static objects, for example boxes, and the intent of all of these boxes to just sit around and be part of the background, or to move as a single unit, they can flattened together into a handful of nodes (or even one node). To do this, parent them all to the same node, and use:

node.flattenStrong()

One thing that flattenStrong() won't touch is geometry under a ModelRoot or ModelNode node. Since each egg or bam file loads itself up under a ModelRoot node, the proper way to handle this is to get rid of that node first to make the geometry from multiple different egg files to be flattened together. This can be done with the following:

modelRoot = loader.loadModel('myModel.egg')
newModel = NodePath('model')
modelRoot.getChildren().reparentTo(newModel)


Panda3D Manual: Measuring Performance with PStats

QUICK INTRODUCTION

PStats is Panda's built-in performance analysis tool. It can graph frame rate over time, and can further graph the work spent within each frame into user-defined subdivisions of the frame (for instance, app, cull and draw), and thus can be an invaluable tool in identifying performance bottlenecks. It can also show frame-based data that reflects any arbitrary quantity other than time intervals, for instance, texture memory in use or number of vertices drawn.

The performance graphs may be drawn on the same computer that is running the Panda client, or they may be drawn on another computer on the same LAN, which is useful for analyzing fullscreen applications. The remote computer need not be running the same operating system as the client computer.

To use PStats, you first need to build the PStats server program, which is part of the Pandatool tree (it's called pstats.exe on Windows, and pstats on a Unix platform). Start by running the PStats server program (it runs in the background), and then start your Direct/Panda client with the following in your Config.prc file:

 want-pstats 1

Or, at runtime, issue the Python command:

 PStatClient.connect()

Or if you're running pview, press shift-S.

Any of the above will contact your running PStats server program, which will proceed to open a window and start a running graph of your client's performance.

If you have multiple computers available for development, it can be advantageous to run the pstats server on a separate computer so that the processing time needed to maintain and update the pstats user interface isn't taken from the program you are profiling. If you wish to run the server on a different machine than the client, start the server on the profiling machine and add the following variable to your client's Config.prc file, naming the hostname or IP address of the profiling machine:

 pstats-host profiling-machine-ip-or-hostname

If you are developing Python code, you may be interested in reporting the relative time spent within each Python task (by subdividing the total time spent in Python, as reported under "Show Code"). To do this, add the following lines to your Config.prc file before you start ShowBase:

 task-timer-verbose 1
 pstats-tasks 1

THE PSTATS SERVER (The user interface)

The GUI for managing the graphs and drilling down to view more detail is entirely controlled by the PStats server program. At the time of this writing, there are two different versions of the PStats server, one for Unix and one for Windows, both called simply pstats. The interfaces are similar but not identical; the following paragraphs describe the Windows version.

When you run pstats.exe, it adds a program to the taskbar but does not immediately open a window. The program name is typically "PStats 5185", showing the default PStats TCP port number of 5185; see "HOW IT WORKS" below for more details about the TCP communication system. For the most part you don't need to worry about the port number, as long as server and client agree (and the port is not already being used by another application).

Each time a client connects to the PStats server, a new monitor window is created. This monitor window owns all of the graphs that you create to view the performance data from that particular connection. Initially, a strip chart showing the frame time of the main thread is created by default; you can create additional graphs by selecting from the Graphs pulldown menu.

Time-based Strip Charts

This is the graph type you will use most frequently to examine performance data. The horizontal axis represents the passage of time; each frame is represented as a vertical slice on the graph. The overall height of the colored bands represents the total amount of time spent on each frame; within the frame, the time is further divided into the primary subdivisions represented by different color bands (and labeled on the left). These subdivisions are called "collectors" in the PStats terminology, since they represent time collected by different tasks.

Normally, the three primary collectors are App, Cull, and Draw, the three stages of the graphics pipeline. Atop these three colored collectors is the label "Frame", which represents any remaining time spent in the frame that was not specifically allocated to one of the three child collectors (normally, there should not be significant time reported here).

The frame time in milliseconds, averaged over the past three seconds, is drawn above the upper right corner of the graph. The labels on the guide bars on the right are also shown in milliseconds; if you prefer to think about a target frame rate rather than an elapsed time in milliseconds, you may find it useful to select "Hz" from the Units pulldown menu, which changes the time units accordingly.

The running Panda client suggests its target frame rate, as well as the initial vertical scale of the graph (that is, the height of the colored bars). You can change the scale freely by clicking within the graph itself and dragging the mouse up or down as necessary. One of the horizontal guide bars is drawn in a lighter shade of gray; this one represents the actual target frame rate suggested by the client. The other, darker, guide bars are drawn automatically at harmonic subdvisions of the target frame rate. You can change the target frame rate with the Config.prc variable pstats-target-frame-rate on the client.

You can also create any number of user-defined guide bars by dragging them into the graph from the gray space immediately above or below the graph. These are drawn in a dashed blue line. It is sometimes useful to place one of these to mark a performance level so it may be compared to future values (or to alternate configurations).

The primary collectors labeled on the left might themselves be further subdivided, if the data is provided by the client. For instance, App is often divided into Show Code, Animation, and Collisions, where Show Code is the time spent executing any Python code, Animation is the time used to compute any animated characters, and Collisions is the time spent in the collision traverser(s).

To see any of these further breakdowns, double-click on the corresponding colored label (or on the colored band within the graph itself). This narrows the focus of the strip chart from the overall frame to just the selected collector, which has two advantages. Firstly, it may be easier to observe the behavior of one particular collector when it is drawn alone (as opposed to being stacked on top of some other color bars), and the time in the upper-right corner will now reflect just the total time spent within just this collector. Secondly, if there are further breakdowns to this collector, they will now be shown as further colored bars. As in the Frame chart, the topmost label is the name of the parent collector, and any time shown in this color represents time allocated to the parent collector that is not accounted for by any of the child collectors.

You can further drill down by double-clicking on any of the new labels; or double-click on the top label, or the white part of the graph, to return back up to the previous level.

Value-based Strip Charts

There are other strip charts you may create, which show arbitrary kinds of data per frame other than elapsed time. These can only be accessed from the Graphs pulldown menu, and include things such as texture memory in use and vertices drawn. They behave similarly to the time-based strip charts described above.

Piano Roll Charts

This graph is used less frequently, but when it is needed it is a valuable tool to reveal exactly how the time is spent within a frame. The PStats server automatically collects together all the time spent within each collector and shows it as a single total, but in reality it may not all have been spent in one continuous block of time.

For instance, when Panda draws each display region in single-threaded mode, it performs a cull traversal followed by a draw traversal for each display region. Thus, if your Panda client includes multiple display regions, it will alternate its time spent culling and drawing as it processes each of them. The strip chart, however, reports only the total cull time and draw time spent.

Sometimes you really need to know the sequence of events in the frame, not just the total time spent in each collector. The piano roll chart shows this kind of data. It is so named because it is similar to the paper music roll for an old-style player piano, with holes punched down the roll for each note that is to be played. The longer the hole, the longer the piano key is held down. (Think of the chart as rotated 90 degrees from an actual piano roll. A player piano roll plays from bottom to top; the piano roll chart reads from left to right.)

Unlike a strip chart, a piano roll chart does not show trends; the chart shows only the current frame's data. The horizontal axis shows time within the frame, and the individual collectors are stacked up in an arbitrary ordering along the vertical axis.

The time spent within the frame is drawn from left to right; at any given time, the collector(s) that are active will be drawn with a horizontal bar. You can observe the CPU behavior within a frame by reading the graph from left to right. You may find it useful to select "pause" from the Speed pulldown menu to freeze the graph on just one frame while you read it.

Note that the piano roll chart shows time spent within the frame on the horizontal axis, instead of the vertical axis, as it is on the strip charts. Thus, the guide bars on the piano roll chart are vertical lines instead of horizontal lines, and they may be dragged in from the left or the right sides (instead of from the top or bottom, as on the strip charts). Apart from this detail, these are the same guide bars that appear on the strip charts.

The piano roll chart may be created from the Graphs pulldown menu.

Additional threads

If the panda client has multiple threads that generate PStats data, the PStats server can open up graphs for these threads as well. Each separate thread is considered unrelated to the main thread, and may have the same or an independent frame rate. Each separate thread will be given its own pulldown menu to create graphs associated with that thread; these auxiliary thread menus will appear on the menu bar following the Graphs menu. At the time of this writing, support for multiple threads within the PStats graph is largely theoretical and untested.

Color and Other Optional Collector Properties

If you do not specify a color for a particular collector, it will be assigned a random color at runtime. At present, the only way to specify a color is to modify panda/src/pstatclient/pStatProperties.cxx, and add a line to the table for your new collector(s). You can also define additional properties here such as a suggested initial scale for the graph and, for non-time-based collectors, a unit name and/or scale factor. The order in which these collectors are listed in this table is also relevant; they will appear in the same order on the graphs. The first column should be set to 1 for your new collectors unless you wish them to be disabled by default. You must recompile the client (but not the server) to reflect changes to this table.

HOW TO DEFINE YOUR OWN COLLECTORS

The PStats client code is designed to be generic enough to allow users to define their own collectors to time any arbitrary blocks of code (or record additional non-time-based data), from either the C++ or the Python level.

The general idea is to create a PStatCollector for each separate block of code you wish to time. The name which is passed to the PStatCollector constructor is a unique identifier: all collectors that share the same name are deemed to be the same collector.

Furthermore, the collector's name can be used to define the hierarchical relationship of each collector with other existing collectors. To do this, prefix the collector's name with the name of its parent(s), followed by a colon separator. For instance, PStatCollector("Draw:Flip") defines a collector named "Flip", which is a child of the "Draw" collector, defined elsewhere.

You can also define a collector as a child of another collector by giving the parent collector explicitly followed by the name of the child collector alone, which is handy for dynamically-defined collectors. For instance, PStatCollector(draw, "Flip") defines the same collector named above, assuming that draw is the result of the PStatCollector("Draw") constructor.

Once you have a collector, simply bracket the region of code you wish to time with collector.start() and collector.stop(). It is important to ensure that each call to start() is matched by exactly one call to stop(). If you are programming in C++, it is highly recommended that you use the PStatTimer class to make these calls automatically, which guarantees the correct pairing; the PStatTimer's constructor calls start() and its destructor calls stop(), so you may simply define a PStatTimer object at the beginning of the block of code you wish to time. If you are programming in Python, you must call start() and stop() explicitly.

When you call start() and there was another collector already started, that previous collector is paused until you call the matching stop() (at which time the previous collector is resumed). That is, time is accumulated only towards the collector indicated by the innermost start() .. stop() pair.

Time accumulated towards any collector is also counted towards that collector's parent, as defined in the collector's constructor (described above).

It is important to understand the difference between collectors nested implicitly by runtime start/stop invocations, and the static hierarchy implicit in the collector definition. Time is accumulated in parent collectors according to the statically-defined parents of the innermost active collector only, without regard to the runtime stack of paused collectors.

For example, suppose you are in the middle of processing the "Draw" task and have therefore called start() on the "Draw" collector. While in the middle of processing this block of code, you call a function that has its own collector called "Cull:Sort". As soon as you start the new collector, you have paused the "Draw" collector and are now accumulating time in the "Cull:Sort" collector. Once this new collector stops, you will automatically return to accumulating time in the "Draw" collector. The time spent within the nested "Cull:Sort" collector will be counted towards the "Cull" total time, not the "Draw" total time.

If you wish to collect the time data for functions, a simple decorator pattern can be used below, as below:

def pstat(func):
    from pandac.PandaModules import PStatCollector
    collectorName = "Debug:%s" % func.__name__
    if hasattr(base, 'custom_collectors'):
        if collectorName in base.custom_collectors.keys():
            pstat = base.custom_collectors[collectorName]
        else:
            base.custom_collectors[collectorName] = PStatCollector(collectorName)
            pstat = base.custom_collectors[collectorName]
    else:
        base.custom_collectors = {}
        base.custom_collectors[collectorName] = PStatCollector(collectorName)
        pstat = base.custom_collectors[collectorName]
    def doPstat(*args, **kargs):
        pstat.start()
        returned = func(*args, **kargs)
        pstat.stop()
        return returned
    doPstat.__name__ = func.__name__
    doPstat.__dict__ = func.__dict__
    doPstat.__doc__ = func.__doc__
    return doPstat

To use it, either save the function to a file and import it into the script you wish to debug. Then use it as a decorator on the function you wish to time. A collection named Debug will appear in the Pstats server with the function as its child.

from pstat_debug import pstat

@pstat
def myLongRunFunction():
    """ This function does something long """

HOW IT WORKS (What's actually happening)

The PStats code is divided into two main parts: the client code and the server code.

The PStats Client

The client code is in panda/src/pstatclient, and is available to run in every Panda client unless it is compiled out. (It will be compiled out if OPTIMIZE is set to level 4, unless DO_PSTATS is also explicitly set to non-empty. It will also be compiled out if NSPR is not available, since both client and server depend on the NSPR library to exchange data, even when running the server on the same machine as the client.)

The client code is designed for minimal runtime overhead when it is compiled in but not enabled (that is, when the client is not in contact with a PStats server), as well as when it is enabled (when the client is in contact with a PStats server). It is also designed for zero runtime overhead when it is compiled out.

There is one global PStatClient class object, which manages all of the communications on the client side. Each PStatCollector is simply an index into an array stored within the PStatClient object, although the interface is intended to hide this detail from the programmer.

Initially, before the PStatClient has established a connection, calls to start() and stop() simply return immediately.

When you call PStatClient.connect(), the client attempts to contact the PStatServer via a TCP connection to the hostname and port named in the pstats-host and pstats-port Config.prc variables, respectively. (The default hostname and port are localhost and 5185.) You can also pass in a specific hostname and/or port to the connect() call. Upon successful connection and handshake with the server, the PStatClient sends a list of the available collectors, along with their names, colors, and hierarchical relationships, on the TCP channel.

Once connected, each call to start() and stop() adds a collector number and timestamp to an array maintained by the PStatClient. At the end of each frame, the PStatClient boils this array into a datagram for shipping to the server. Each start() and stop() event requires 6 bytes; if the resulting datagram will fit within a UDP packet (1K bytes, or about 84 start/stop pairs), it is sent via UDP; otherwise, it is sent on the TCP channel. (Some fraction of the packets that are eligible for UDP, from 0% to 100%, may be sent via TCP instead; you can specify this with the pstats-tcp-ratio Config.prc variable.)

Also, to prevent flooding the network and/or overwhelming the PStats server, only so many frames of data will be sent per second. This parameter is controlled by the pstats-max-rate Config.prc variable and is set to 30 by default. (If the packets are larger than 1K, the max transmission rate is also automatically reduced further in proportion.) If the frame rate is higher than this limit, some frames will simply not be transmitted. The server is designed to cope with missing frames and will assume missing frames are similar to their neighbors.

The server does all the work of analyzing the data after that. The client's next job is simply to clear its array and prepare itself for the next frame.

The PStats Server

The generic server code is in pandatool/src/pstatserver, and the GUI-specific server code is in pandatool/src/gtk-stats and pandatool/src/win-stats, for Unix and Windows, respectively. (There is also an OS-independent text-stats subdirectory, which builds a trivial PStats server that presents a scrolling-text interface. This is mainly useful as a proof of technology rather than as a usable tool.)

The GUI-specific code is the part that manages the interaction with the user via the creation of windows and the handling of mouse input, etc.; most of the real work of interpreting the data is done in the generic code in the pstatserver directory.

The PStatServer owns all of the connections, and interfaces with the NSPR library to communicate with the clients. It listens on the specified port for new connections, using the pstats-port Config.prc variable to determine the port number (this is the same variable that specifies the port to the client). Usually you can leave this at its default value of 5185, but there may be some cases in which that port is already in use on a particular machine (for instance, maybe someone else is running another PStats server on another display of the same machine).

Once a connection is received, it creates a PStatMonitor class (this class is specialized for each of the different GUI variants) that handles all the data for this particular connection. In the case of the windows pstats.exe program, each new monitor instance is represented by a new toplevel window. Multiple monitors can be active at once.

The work of digesting the data from the client is performed by the PStatView class, which analyzes the pattern of start and stop timestamps, along with the relationship data of the various collectors, and boils it down into a list of the amount of time spent in each collector per frame.

Finally, a PStatStripChart or PStatPianoRoll class object defines the actual graph output of colored lines and bars; the generic versions of these include virtual functions to do the actual drawing (the GUI specializations of these redefine these methods to make the appropriate calls).


Panda3D Manual: The Rigid Body Combiner

The Rigid Body Combiner

When you are developing a complex game, you will most likely run into the problem that you will have too many meshes in your scene. Panda3D's flattening methods can help you reducing the number of nodes, but often when you have hundreds of moving bodies this is not always an option. Therefore, Panda3D has a feature to help you reduce the number of nodes: the RigidBodyCombiner.

The RigidBodyCombiner is designed reduce the number of nodes actually sent to the graphics card, just like the flattening functions. But instead of flattening everything into one node immediately, the RigidBodyCombiner keeps your original node structure intact, still allowing you to apply transforms (e.g. moving around, rotating or scaling) sub-nodes. But what's actually sent to the graphics cards is just one node, a combined version of all these sub-nodes. If you want to see the combined version of these nodes (not likely), you can call getInternalScene(), this function will return the nodepath that is actually sent to the graphics card.

The RigidBodyCombiner class is just another kind of PandaNode. All of the standard node interfaces apply. Thus, the easiest way to add nodes to a RigidBodyCombiner is to wrap a NodePath around it, and then use the standard reparentTo() interfaces to parent the nodes you want to combine to this NodePath.

When you are done with reparenting the nodes, you need to call collect() on the original RigidBodyCombiner instance. This is a fairly expensive call and you should normally only call this once -- but after you called collect() you may freely transform all nodes below without having to call this again. If you later add more children to the RBC, though, you will need to call collect() again.

The vertices of the objects you attach to the RigidBodyCombiner must be transformed each frame on the CPU. For this reason, you may find a performance advantage in limiting the number of vertices in the models you use. Also, be sure you do not have normals on your models unless you are actually using lighting.

Here is a small example showing a random cloud of boxes:

from direct.directbase.DirectStart import *
from pandac.PandaModules import RigidBodyCombiner, NodePath, Vec3
import random

rbc = RigidBodyCombiner("rbc")
rbcnp = NodePath(rbc)
rbcnp.reparentTo(render)

for i in range(200):
    pos = Vec3(random.uniform(-100, 100),
               random.uniform(-100, 100),
               random.uniform(-100, 100))

    f = loader.loadModel("box.egg")
    f.setPos(pos)
    f.reparentTo(rbcnp)

rbc.collect()

Note: RenderEffects such as Billboards are not supported below this node.

For more information and a complete list of RigidBodyCombiner functions please see the API reference.


Panda3D Manual: Performance Issue: Too Many Meshes

If you are having too much nodes/meshes in your scene, this might result in a slow performance. You can check your Geom count in PStats, or by calling NodePath.analyze() on your object or scene.

Panda3D provides a function to reduce the mesh count: NodePath.flattenStrong(). This will reduce the NodePath to only one node. Be careful with this function: Usage without care might cause your game to crash, since you will not be able to move individual subnodes of a flattened node around anymore.

NodePath.flattenMedium() and flattenLight() are not as rigorous NodePath.flattenStrong(), but may be worth considering.

Though, if you have multiple independently-moving rigid nodes, the flattening functions might not suit your needs, because, since the flattening functions flatten everything to one node you won't be able to move individual sub-nodes around. An alternative is the Rigid Body Combiner, which can combine multiple nodes while you can still change the transforms on the sub-nodes.

If you are using the GeoMipTerrain for terrain rendering, that might also result in a large mesh count. (You can check the block count by calling terrain.getRoot().analyze().) If it is too high, try increasing the block size, or enable AutoFlattening, which will reduce the block count to only one. The autoflatten function was created because normally you can't flatten a terrain using the normal flattenX methods, because this will interfere with the GeoMipTerrain's updating system.


Panda3D Manual: Performance Issue: Too Many State Changes

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Too Many Text Updates

If you are using the TextNode or OnscreenText (or similar) interfaces, and you have large chunks of text changing every frame, you might find it to be a big performance hit. Panda3D does a lot of work to assemble the text, so you will want to minimize unnecessary calls to setText() or related functions that force the text to be recomputed.

On the other hand, if you really want to change your text frequently, you can try putting this in your Config.prc file:
text-flatten 0

this will remove the call to flattenStrong() within the text generation process. Changing the text will be much faster, but rendering the resulting text will be slower, since you will be dealing with a lot of meshes in your scene graph.

Panda3D 1.6.0 and later contain a performance optimization that speeds up the text generation. If you have this version, you will also need the following line in your Config.prc, in addition to the text-flatten line, to achieve the same effect (though this is not recommended, for the reason stated above):
text-dynamic-merge 0


Panda3D Manual: Performance Issue: Too Many Shader Instructions
<<prev top next>>

This only can happen if you have at least one NodePath.setShader or NodePath.setAutoShader method call in your application, or you are using a post processing filter from CommonManager.

Too Many Vertex Shader Instructions

Try so simplify your scene. Objects that are far away don't need millions of vertices. Look at LODNode and FadeLODNode.

Too Many Fragment/Pixel Shader Instructions

An easy way to detect whether this is a bottleneck in your application, try resizing the window. If the framerate heavily varies with the window size or screen resolution, you're most likely dealing with this problem.

If your frame rate strong depends on the window or screen resolution, this may be one hint that your fragment shader has too many instructions. Another problem is if your depth complexity is too high. Try to look at your scene from different angles and positions. If your frame rate varies then the overdraw from one specific view angle is to high.

Try to minimize the objects Panda3D needs to draw. Use lens.setFar, or fallback to a simpler fragment shaders for objects that are far away. If an object is far away from the viewer it doesn't make sense to apply normal mapping. LODNode and FadeLODNode may help.

If your fragment shader is self made, then try to offload some work to your vertex shader.

There is a simple method to test your scene. Replace your whole fragment shader with the following snippet:

o_color = float4(1.0, 0.0, 1.0, 0.0);

If the frame rate doesn't change, then it is the depth complexity. It if changes it may be the depth complexity or the shader.


Panda3D Manual: Performance Issue: Excessive Fill

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Memory Full

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Python Calculation

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Failure to Garbage Collect
<<prev top next>>

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Collision System Misuse

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Motherboard Integrated Video
<<prev top next>>

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Too Many Polygons

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Performance Issue: Miscellaneous

This page lists a number of miscellaneous things that can possibly be a cause of bad performance in your game.

  • You are using textures with a width or height that is not a power of two. This could cause Panda3D to resize the textures, which can slow your game down a lot. Always try to stick to powers of two when choosing a Texture Size.
  • You are printing too much to the console every frame. Printing to the console, especially on windows, can cause a slow-down of your application. Always check your console whether its not flooded with errors or debug messages. You might want to consider output buffering or, on windows, using pythonw (python running with windows subsystem instead of console subsystem) if you are experiencing this issue.

Incomplete Section

Note: this section is incomplete. It will be updated soon.


Panda3D Manual: Using C++

Though Panda3D is often used with Python, it is possible to program a complete game in C++ without any line of Python. To start a C++ project, rebuilding Panda3D from source is not needed. This is done by including the right headers and linking with the right libraries.

This section will explain how to use C++ to create your Panda3D programs instead of the default Python language.

The manual and the API are mainly focused at the use of Python, so you will need to keep a few things in mind:

  • You need to include the file pandabase.h, which is needed to initialize the Panda3D library.
  • There's no DirectStart. You need to create windows yourself using the Window Framework.
  • You can import the classes you need just by including their header file, like this:
#import "textNode.h"
  • The functions are called the same way, but in lowercase and spaces between the words.
So instanceTo() in Python becomes instance_to() in C++.


Panda3D Manual: How to compile a C++ Panda3D program

This short guide explains how to compile and run a Panda3D game written in C++. If you use Python for your Panda3D programs, you can skip this section.

On UNIX, it is very good possible to compile using the g++ compiler. The following page describes how to do that:

  • Linux version

On Windows, compiling using g++ will not work. It is however possible to compile your Panda3D project using the MSVC compiler. The following page will explain how to compile pview, a sample C++ application,using MSVC:

  • Using Microsoft Visual Studio

Some have also managed to build Panda3D under OSX. This is the related forum thread:

  • [[1]]


Panda3D Manual: How to compile a C++ Panda3D program on Linux
<<prev top next>>

This short guide explains how to build a Panda3D game written in C++ game under Linux. If you use Python for programming, you can skip this page.

First of all, download the following files:

  1. Python, the development package
  2. The GNU G++ compiler. On most Linux versions, this is already pre-installed.

Now, first of all, we need to create a .o file from our cxx file. We need to link to the Panda3D include files and to the Python include files. Please change the paths in these commands to the appropiate locations.

g++ -c filename.cxx -o filename.o -fPIC -O2 -I{pythoninclude} -I{panda3dinclude}

To generate an executable, you can use the following command:

g++ filename.o -o filename -fPIC -L{panda3dlibs} -lp3framework -lpanda
     -lpandafx -lpandaexpress -lp3dtoolconfig -lp3dtool -lp3pystub -lp3direct

Note: In this two commands, you need to change a few paths:

  • {pythoninclude}: The path to your Python include folder. For version 2.4, this is /usr/include/python2.4 by default.
  • {panda3dinclude}: Change this to the path to your Panda3D include directory. This would probably look like /usr/include/panda3d/.
  • {panda3dlibs}: Change this to the path to your Panda3D libraries. Usually this is just /usr/lib/panda3d or sometimes /usr/lib.

To run your newly created executable, type:

./filename

If it runs, congratulations! You have successfully compiled your own Panda3D program!


Panda3D Manual: The Window Framework

Note: The WindowFramework class is for use in C++ only. If you use Python, you can just use DirectStart to open a window and skip this page.

This page will explain how to use the WindowFramework class in C++ to open a blank window. First of all, we need to include the appropiate header files:

#include "pandaFramework.h"
#include "pandaSystem.h"

Second, we need to create an instance of the WindowFramework class and open the main int of course.

PandaFramework framework;
int main(int argc, char *argv[]) {

Now, we must open the framework, give the window a nice title and open the window:

framework.open_framework(argc, argv);
framework.set_window_title("Hello World!");
//open it!
WindowFramework *window = framework.open_window();

Optionally, we can enable keyboard support, in case we want to check for keyboard presses, and we can enable the default camera trackball.

//enable keyboard detection
window->enable_keyboard();
//enable default camera movement
window->setup_trackball();

Now, we're going to check if the window has opened successfully. If so, the main loop must be called, using the function framework.main_loop(). This is equal to the run() function in Python.

if (window != (WindowFramework *)NULL) {
  nout << "Opened the window successfully!\n";

  //put here your own code, such as the loading of your models

  framework.main_loop();
} else {
  nout << "Could not load the window!\n";
}

Afterwards, we need to close the framework:

  framework.close_framework();
  return (0);
}

Now, compile and run your file and you have your own window opened!

For the lazy ones among us, here is the full code:

//include all the stuff
#include "pandaFramework.h"
#include "pandaSystem.h"

//init the PandaFramework class
PandaFramework framework;

int main(int argc, char *argv[]) {
  //open the framework
  framework.open_framework(argc, argv);
  //set a nice title
  framework.set_window_title("Hello World!");
  //open it!
  WindowFramework *window = framework.open_window();

  //check whether the window is loaded correctly
  if (window != (WindowFramework *)NULL) {
    nout << "Opened the window successfully!\n";

    window->enable_keyboard(); //enable keyboard detection
    window->setup_trackball(); //enable default camera movement

    //put here your own code, such as the loading of your models

    //do the main loop
    framework.main_loop();
  } else {
    nout << "Could not load the window!\n";
  }
  //close the framework
  framework.close_framework();
  return (0);
}

The WindowFramework class also provides all the basic things the python ShowBase /DirectStart equivalent would normally take care of:

const NodePath &get_render();
const NodePath &get_render_2d();
const NodePath &get_aspect_2d();

void set_wireframe(bool enable);
void set_texture(bool enable);
void set_two_sided(bool enable);
void set_one_sided_reverse(bool enable);
void set_lighting(bool enable);

const NodePath &get_camera_group();

INLINE int get_num_cameras() const;
INLINE Camera *get_camera(int n) const;

//WindowFramework also provides access to the GraphicsWindow.
//for example, to set the background color to black, you can do this:
window->get_graphics_window()->set_clear_color(Colorf(0,0,0,1));

It's very useful to study the file panda/src/framework/windowFramework.h, since you will need to use it often.


Panda3D Manual: Texturing in C++

Note: This page is related to C++ usage of Panda3D and NOT to Python usage. If you are a Python user, please skip this page.

Texturing in C++

Panda's C++ interface to texturing is basically the same as it's Python interface, That's why it is very recommended to read the entire section including the Python examples.

However, there are two major differences, which are explained in this page.

1. C++ does not have a loader.loadTexture. You need to use the TexturePool to load textures. Please note that the Texture class is reference counted.

include "texturePool.h"

PT(Texture) tex;
tex = TexturePool::load_texture("maps/noise.rgb");

NodePath smiley;
smiley = window->load_model(window->get_render(),"smiley.egg");
smiley.setTexture(tex, 1);

2. You don't directly need CardMaker to display your texture in a 2D or 3D scene. You can load it as if it were a model, and treat it like any other model.

NodePath noiseplane;
noiseplane = window->load_model(window->get_render(),"maps/noise.rgb");

This short piece of code will result in a single polygon in the scene with the noise texture applied to it. If you need it in the 2d scene, you can use get_aspect2d() or get_render2d() instead of get_render().


Panda3D Manual: Reference Counting

WRITEME: this article is a stub. Please help by expanding it.

This page will explain about Panda3D's smart Reference Counting system.

Some Panda3D classes use this system. You can check if such a class is Reference Counted by checking in the List of Classes if it inherits from ReferenceCount.

Such classes, for example Texture, or TextNode, are created like this:

  //  Create the PointerTo object
PT(TextNode) myVar;
  // Assign a new TextNode to it
myVar = new TextNode("title");

  // Now, you can do with it what you want, treat it like a pointer:

myVar->set_text("I am a reference counted TextNode!");


Panda3D Manual: Panda Tools

This section lists a number of tools to support programming in Panda.

Many of the tools provided are invoked by pressing a hot-key inside of the panda main window. These hot-key driven tools are called the "direct tools." The direct tools are initially disabled, because the hot-keys used to invoke them might interfere with the program's own keys and events.

To enable the hot-keys for the direct tools, you need to set the 'want-directtools' variable in config.prc, the main panda configuration file. Additionally, set 'want-tk' to '#t' in order to enable the Direct Session Panel. More information about this configuration file is available in the Configuring Panda section.

If you want to quickly enable the Direct Session browser, add the following code before importing DirectStart:

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "want-directtools #t")
loadPrcFileData("", "want-tk #t")

After enabling direct tools and starting panda, the Direct Session window should appear:


Panda3D Manual: The Scene Graph Browser

The Scene Graph Browser is only available when 'Direct Tools' are enabled. Information on enabling 'Direct Tools' is available in the Panda Tools section.

In the main Direct Session window, there are several scene placement tools available through the tabs at the right side of the screen. Clicking on these tabs will bring up a dialog box with the various properties for these aspects. On the left side of the panel is a collapsible scene graph for the render parent node. Right click any of the objects to bring up a list of possible commands.

Of particular interest is the placer panel, selected by the place command for an object. This brings up a separate window that may alter the position, orientation, and scale for the selected object. A dialog box at the top of the window allows these movements to be relative to another object in the program.


Panda3D Manual: Enhanced Mouse Navigation

Enhanced mouse navigation is only available when 'Direct Tools' are enabled. Information on enabling 'Direct Tools' is available in the Panda Tools section.

Direct Tools gives more functionality to the middle mouse button. First, clicking the middle mouse button changes the pivot point it uses to rotate around the environment. The middle mouse button will also move the camera depending on where the cursor is on the screen.

Middle Mouse Click | Sets pivot point for rotating around the world
Middle Mouse + Middle Region | Move camera parallel to ground
Shift + Middle Mouse + Middle Region | Move camera vertically
Middle Mouse + Edge Region | Rotate camera around pivot point
Middle Mouse + Corner Region | Roll camera around pivot point
Shift + Middle Mouse + Edge Region | Changes pitch of the camera

The left mouse button is now used to select and manipulate objects in the environment. Once an object is selected, it may be moved and rotated.

Left Mouse Click | Select an object
Left Mouse + Middle Region | Move object vertically
Shift + Left Mouse + Middle Region | Move object parallel to ground
Left Mouse + Edge Region | Rotate object around its pivot point
Left Mouse + Corner Region | Roll object around its pivot point
Control + Left Mouse | Rescale the model

Direct Tools uses a large number of hot keys for camera control, rendering styles, and object control. The full list is in the table below.

Camera Control |  | Render Style |
+ | Zoom in | Shift + A | Show all
- | Zoom out | Control + F | Flash selected
1 | Front view (relative to render) | B | Toggle backface
2 | Back view (relative to render) | L | Toggle lights
3 | Right view (relative to render) | T | Toggle texture
4 | Left view (relative to render) | W | Toggle wireframe
5 | Top view (relative to render) |  |
6 | Bottom view (relative to render) | Direct Controls |
7 | ¾ view (relative to render) | Delete | Delete selected object
8 | Roll view about axis relative to camera’s axis | Escape | Delete all
9 | Rotate around hot point | Page Down | Move down selected object’s hierarchy
0 | Rotate around hot point | Page Up | Move up selected object’s hierarchy
C | Center on hot point | Tab | Toggle widget mode
F | Fit on hot point | Shift + F | Grow widget to fit current window
H | Move to (0,0,0) | I | Plant selected object at cursor intersection point
Shift + L | Toggle camera pivot point lock | M | Move widget in front of camera
N | Select next possible camera COA (along last intersection ray) | P | Set active parent to selected object
U | Orbit upright camera about hot point | R | WRT reparent selected to active parent
Shift + U | Upright camera | Shift + R | Reparent selected to active parent
` | Kill camera move task | S | Reselect last selected object
 |  | V | Toggle widget visibility
Undo/Redo |  | Shift + V | Toggle COA marker visibility
[ | Undo | < | Shrink widget
] | Redo | > | Expand widget


Panda3D Manual: Interrogate

Interrogate

Interrogate is a program to parse a body of C++ code and build up a table of classes, methods, functions, and symbols found, for the purposes of calling into the codebase via a non-C++ scripting language like Python (Scheme and Smalltalk where also tried at some point) The design of interrogate is such that it should be able to produce wrappers for any other language without too much trouble. You'll have to be responsible for writing and maintaining the interface layer to produce the wrappers, though.

In addition to identifying all the classes and their relationships, interrogate will generate a wrapper function for each callable function. The wrapper functions will be callable directly from the scripting language, with no understanding of C++ necessary; these wrapper functions will in turn call the actual C++ functions or methods.

Most exportable features of C++ are supported, including templates, default parameters, and function overloading.

How to use it

There are a few steps involved in generating python wrappers using interrogate. Steps for Interrogate:

  • In this example it is assumed you have a header file called test.h and a source file called test.cxx.
  • Run Interrogate on your .h file which will generate a test_igate.cxx and a test.in file, for example:
interrogate -oc test_igate.cxx -od test.in -python-native test.h
  • Most likely, you want to specify more flags to the interrogate command, like -string, -fnames, -refcount, or -assert. Consult the interrogate help file for more information about that (run interrogate with -h option)
  • Now, you will need to call interrogate_module and generate an interrogate_module.cxx file based on your two files from the previous step:
interrogate_module -oc test_module.cxx -python-native test.in
  • Note that you can also run interrogate with the -do-module option which will automatically make sure interrogate_module gets called too.
  • Now you can compile all your source files, and, the test_module.cxx and test_igate.cxx files.
  • When you want to link your C++ files together to form a library, also include the _igate and _module object files. This will make sure it will include the python wrappers.
  • You can now put it on your python path (or make sure it's in the same directory) and import it directly by the name of your library.
from libtest import TestClass
  • Note: In python versions 2.5 and higher, you can not directly load .dll files on windows this way. You will either need to rename your dll into a pyd extension (which will load without problems, hopefully) or you need to preload your library using the imp module.

Calling Interrogate

This section will explain how to call interrogate and will briefly address the most important options. For the full documentation, however, refer to the interrogate help file (accessible by calling interrogate with the -h option).

When calling interrogate, you will need to include the -oc and -od options, which specify where the generated code and function tables, respectively, will be written.

The -module and -library options are used to specify the name of your module and library. These options are mainly code-organizational. You can omit both options.

With -D you can ignore or make interrogate interpret symbols differently. For example, if your code uses a non-standard C macro like __inline, you would need to call interrogate with -D__inline. Or, if you would like certain defines to be defined differently, you can use -Ddefvar=value.

Furthermore, there are a few special flags that you most likely want to include. There is the -string option, which treats the C++ char* and STL strings as special cases, and maps them to the scripting language's string equivalent, instead of a wrapper to basic_string<char>. The option -refcount makes the wrappers compatible with Panda3D's smart reference counting system, if your library depends on Panda3D you will want to include it too. The -assert option is just used for Python wrappers and specifies that when the C++ code throws an assert, this will be translated to an AssertionError exception in python.

Wrapper functions

There are three/four options that specify how to generate the wrapper functions:

  • The -c option will generate function wrappers using the C calling convention. Any scripting language that can call a C function should be able to make advantage of the interrogate database.
  • The -python option will generate function wrappers using the Python calling convention. In this case, the shared library will directly be loadable as python module (after interrogate_module is called), although C++ objects and methods will be converted into an object handle and a list of independent Python functions.
  • The -python-native option generates true python objects for C++ objects, and translates all C++ methods to true Python methods. This is the option you will most likely want to use.
  • There is also the -python-obj option, but I think it does the same as -python-native.

You can also specify a combination of any of those. If all are omitted, the default is -c.

Here's a small example:

interrogate -D__inline -DCPPPARSER -D__cplusplus -S/usr/include/panda3d/parser-inc -S/usr/include/ -I/usr/include/panda3d/
-oc myModule_igate.cxx -od myModule.in -fnames -string -refcount -assert -python-native
-module libMyModule -library libMyModule myModule.h

interrogate_module -oc myModule_module.cxx -module libMyModule
-library libMyModule -python-native myModule.in

More Information

  • You can run the interrogate commands with the -h option to get a more detailed explanation of the options available.
  • There is a sample C++ extension in the skel/ directory in the Panda3D source to use as reference and sandbox.
  • David Rose, from Walt Disney VR Studio, has held a lecture about interrogate. You can watch a video recording of it here. (Recorded June 4, 2008)


Panda3D Manual: Python Editors

Using a well featured python editor or editor with python support will greatly ease the process of writing python scripts. Here are some of the features that you could look for when choosing an editor. Free editors don't usually have them all. There are quite a few capable editors out there though. In addition to all the features listed below, it should in general be a good and easy to use text editor.

  • Syntax highlighting
  • Smart indentation
  • Auto-completion of text
  • Call tips
  • Tree view function/class selection
  • Integrated shell windows
  • Inbuilt debugger support
  • Code folding support
  • Customibility and extensibility
  • Multi-platform support
  • Unicode support

The python install itself comes with a very capable editor called IDLE, short for Integrated Development Environment. One of the features it has that the ones listed above do not is that it has an enhanced integrated shell. More information about IDLE can be found here.

Below is a list of a few free text editors. There is a comprehensive list of editors at http://www.python.org/moin/PythonEditors. Some of the information about the editors doesn't seem to be upto date at this listing. Try them yourself to find your favourite one.

PyPE | Syntax highlighting, auto-completion, smart indentation, calltips, tree view function/class selection, integrated shell, drag&drop, macros, customizable keyboard shortcuts, and more. Works on Windows and X. Written in python!
Emacs | Very powerful text editor, multiplatform, python support. Smart indentation, auto-completion, syntax highlighting, extensible.
Vim | Very powerful text editor, multiplatform, python support. Smart indentation, syntax highlighting, Scriptable in python.
ConText | Windows based free text editor with python syntax highlighting, and indentation support.
SPE | Syntax highlighting, smart indentation, auto-completion, call tips, class explorer, integrated shell, debugger, code folding, syntax coloring, UML viewer, syntax highlighting, source index, sticky notes, drag&drop, context help, and much more. Runs on Windows, Linux and Mac OS X. Also links to Blender. GUI is extensible with wxGlade. GPL'ed and written in Python.
PyDev | PyDev is a plugin that enables users to use Eclipse for Python and Jython development -- making Eclipse a first class Python IDE -- It comes with many goodies such as code completion, syntax highlighting, syntax analysis, refactor, debuging and many thers.
Komodo Edit | Komodo Edit, based on the award-winning Komodo IDE, offers sophisticated support for all major scripting languages, including in-depth autocomplete and calltips, multi-language file support, syntax coloring and syntax checking, Vi emulation, Emacs key bindings.


Panda3D Manual: SPE

Installing SPE for use with Panda3D

Since Panda 1.3.0 the windows installer has the option to add panda's python to the registry. If you don't register or if you use an earlier version of panda, SPE cannot detect panda modules. Without them, features like autocomplete, the built-in console, debugger etc. do not work for Panda3D scripts. To correct this, SPE must first be integrated properly with python.

Integrate it manualy in this order:

1. download and install Panda3D

This installs python also. There should now be a directory "\Panda3D-x.x.x" where the x's are the version number.

2. download and install python

This installs the standard python interpreter required by the other installers. There should now be a directory "\Pythonxx" (again, x's are the version number)

This directory can be deleted after step 5 if you wish. (not before!)

3. download and install wxpython

SPE requires wxpython. This installs in "\Pythonxx\Lib\site-packages"

4. download and install SPE

This autodetects the standart python install, and also installs in "\Pythonxx\Lib\site-packages"

5. Manually copy the new files and folders in "\Pythonxx\Lib\site-packages" to "\Panda3D-x.x.x\python\lib\site-packages"

When SPE is run from the new location using python it should have access to all the panda3D modules.

6. Write a script for convenience

e.g. in Windows create a new shortcut with target:

"C:\Panda3D-x.x.x\python\python.exe" "C:\Panda3D-x.x.x\python\lib\site-packages\_spe\SPE.py"

This is just as easy in Linux using a shell script.

Importing DirectStart

SPE may need to or require you to import DirectStart for its PyDoc generation and calltips. By default this also pops up the main window. This can get annoying, but fortunately opening the window can be easily deferred by either adding the line

window-type none

to your Config.prc file or by adding the line

loadPrcFileData("", "window-type none")

to your script before you import direct.directbase.DirectStart. You may open the window later with the line

base.openMainWindow(type = 'onscreen')


Panda3D Manual: Pipeline Tips

This section isn't totally related to Panda3D. However, these are a few good pointers on how to keep the 'art to programmer' pipeline running smoothly.

How artists can help programmers with the pipeline:

"Keep renaming to a minimum, preferably with good names to start with, and especially for parts or joints that the programmer will have to manipulate."
Programmers generally deal with objects by their names, not with a graphical tool like artists. Every time a name changes, a programmer has to make the change in his or her code, and often has to hunt through the egg to try and figure out what the name has changed to. In a large model, this can be very time-consuming. If you do have to change a name, make sure to let the programmer know when you give him or her the new model.

"Check your model in pview before handing it off."
The biggest delays in the pipeline come from the back and forth iterations between artist and programmer. A quick check with pview will often find missing textures, backward facing polygons, incorrect normals, mis-tagged collision solids, and a host of other problems.

"Build models with good hierarchy, and don't change the hierarchy unnecessarily."
A well organized hierarchy can make a model much easier for a programmer to work with, and can also have a significant effect on rendering performance. For rendering purposes, good hierarchies group objects that are close to each other together, and don't have more than a few hundred to a few thousand triangles (depending on the target hardware) in each node. (Low-end hardware performs better with only a few hundred triangles per node; high-end hardware performs better with several thousand triangles per node.)

"Put groups of objects that the programmer will have to deal with in a special way under a single node."
For instance, if there's a section of an environment that will be hidden during some point in the game, put that entire section under a single node. The programmer might also like certain classes of collision solids to be under a single node.

"Don't use lossy compression (i.e. jpeg) for textures."
Although jpegs save space on disk, they also degrade your beautiful textures! If textures have to be manipulated later (i.e. downgraded), this degradation will only be compounded. Every time you edit and resave a jpeg, the image quality gets worse. Jpegs may need to be used in the finished product, but it's always best to make this conversion the last step in the process, not the first one. I recommend using the png format, which provides lossless compression and full support for all color depths as well as alpha.

"If there's any chance that an object will be broken apart and used as separate pieces, give each piece a separate texture map."
Nothing hurts worse than having to remap an object after its been painted, or wasting huge swaths of texture space.

"If parts of an object are semi-transparent, make sure those pieces are separate parts in the hierarchy."
Rendering semi-transparent objects is a little tricky. Each object with transparency needs to be sorted back-to-front each frame by the rendering engine. If things aren't going quite as planned, a programmer might need to get a handle on a transparent part in order to manipulate its render order.

"If a semi-transparent object is very large, or you can see through multiple layers (like a glass sphere), break it up into separate pieces."
Objects with multiple layers of transparency won't render correctly depending on which angle they're being view from, because some of the polygons will be drawn before others, and if it's all one object, the rendering engine can't sort them back-to-front.

"Use quads, and higher-order polygons, for collision solids when possible, rather than triangles. But make sure your quads are planar."
In general, dividing a quad into two triangles doubles the time it takes to test a collision with it, so it is better to model collision polygons with quads when possible. The same goes for five-sided and higher-order polygons as well. However, there are two important requirements: (1) the collision polygons must be convex, not concave, and (2) they must be perfectly flat (all of the vertices must lie exactly in the same plane). If either of these is not met, Panda will triangulate the polygon anyway.

Things that don't matter as much, but will give programmers warm-fuzzies:

"Use a consistent naming scheme."
Programmers live in a world where names and naming conventions are incredibly important. Nothing makes them happier than when the names of art assets fit in well with their code. Common naming conventions are: mixedCaseNames, CapitalizedMixedCaseNames, names_with_underscores, names-with-hyphens. Pick one and stick with it.

"Don't misspell things."


Panda3D Manual: Model Export

Panda3D uses a custom file format for its models, called egg. To create an egg file, you will need to use a modeling program (like 3D Studio Max, Blender or Maya) combined with either an export plugin or a file format converter. You can read more about this process in the following sections. Panda3D also provides a binary file format bam, which is quicker to load.

Both file formats contain:

  • Vertices
  • Triangles and larger polygons
  • Joints (aka Bones)
  • Vertex weights
  • Texture pathnames (textures are not stored)
  • Bone-based animation keyframes
  • Morph targets (aka Blend targets)
  • Morph animation keyframes
  • Many control flags

Texture pathnames in an egg file are first assumed to be relative to the egg file itself. If the texture is not found at that location, panda will search its model-path, which is specified in the panda config file. When doing this, panda concatenates the directory which is part of the model-path to the entire string in the egg-file. So if the model-path names the directory "/d/stuff", and the texture-path in the egg file is "mytextures/tex.png", then panda looks in "/d/stuff/mytextures/tex.png."


Panda3D Manual: Converting from 3D Studio Max

NOTE: In Panda3D version 1.5.3, the max exporter was overhauled. Versions prior to that were unstable.

The Max exporter understands only bone-based animation. In other words, all animation must be done by moving bones around. If you move anything else, the Max exporter may not notice! Character studio bipeds work too --- they are indeed collections of bones.

Meshes must be connected to the bones using either the skin or physique modifiers. Any other way of attaching the mesh to the bones will not work. In particular, simply linking the mesh to the bone using the link tool will not work correctly. Do not use the link tool to connect your meshes to your bones. Use the physique or skin modifiers.

If all you want to do is drag a mesh around, then use the following method. Create a single bone - it will look like a small diamond. Put it in the middle of the mesh. Skin the mesh, and connect it to the one bone. Now you can drag the one bone around and the mesh will follow.

In Max, it is a little difficult to create a single bone, because the bone creation tool usually creates two. After clicking to create a bone, look in the object list, and you'll see "Bone01", "Bone02", which proves that there are indeed two. The easiest thing to do is to create the pair of bones, and then delete the second one by pressing the "delete" button.

Using the Exporter

Before installing any plugins, you must install the visual studio runtime system. To do so, execute vcredist_x86-sp1.exe as administrator. Then, to install the plugin, look in the panda3d "plugins" directory. There, you will find the plugins, such as maxegg5.dlo, maxegg6.dlo, and maxegg7.dlo. These are for 3D Studio Max versions 5, 6, and 7 respectively. Copy the correct file from the Panda3D plugins directory into the 3D Studio Max plugins directory, then, restart 3D Studio Max.

The exporter is somewhat unconventional in its design. Max has a menu item "File/Export". Panda's egg format does not show up in this menu. Instead, Panda's exporter is a helper object. This enables the exporter to save your export settings from one session to the next.

To export a scene, your first step is to create the necessary helper object. Go to the creation panel, select "helper objects," choose "exporters," and then click on the button to create a Panda3D export helper:

Drop an egg exporter into the scene:

Switch to the modify panel. You will now be able to see the configuration settings that are stored in the export helper.

The exporter can generate several egg files from a single scene. The exporter therefore contains a list of egg files to generate. To export this particular scene (the one with the blocky humanoid and the biped skeleton), we will create an egg containing the model and one containing the animation. Click the "add..." button on the exporter's modify panel. You will be prompted:

I have filled in the file name, and I have selected the mesh I want to export. When I click "OK," an egg file will be added to the list of eggs to generate. I then click the "add..." button again, and add another egg to the list:

This time, I'm generating an animation egg. I have listed the bones to export, and the range of animation frames. Once I click OK, the modify panel for the egg exporter looks like this:

When I click the "export now" button, the two egg files are generated, and I am asked whether or not I would like to pview them.

When you save your MAX file, the export helper will also be saved. The next time you load it up, it will still remember which meshes go in which egg files.

Materials

The different map types in the 3dsmax Standard material equal Panda ones followingly:

  • Diffuse Slot = Color
  • Bump Slot = Normal
  • Self-Illumination Slot = Glow
  • Specular Color Slot = Gloss
  • Opacity Slot = Modulate

    Known Issues

    Currently, you have to use the option 'Export Entire Scene' when having animated models, otherwise the character hierarchy might not be exported correctly.

    Also, in version 1.5.3, the 'both' option was broken. Instead, you had to export animations separately with the 'model' and 'anim' options. However, this has been fixed in 1.5.4.

    Versions prior to 1.5.3 were unstable and it's not recommended to use them.


Panda3D Manual: Converting from Maya

This section talks about how to prepare a Maya model for export, and then export it. Since exporting is the very last step, the actual instructions for the export process are last.

Contents

  • 1 Material Preparation
    • 1.1 Basic Texturing
    • 1.2 Advanced Texturing
    • 1.3 Merging Textures
  • 2 Flagging Objects From Maya
  • 3 Running the Exporter
  • 4 Using the Graphical Front-End

Material Preparation

Note: the information in this section is for Panda3D 1.5.0 and beyond. It does not apply to older versions of Panda3D.

Basic Texturing

In order for the Panda3D to correctly export your textures, you must texture your model in a manner that Panda3D understands. In fact, you have to follow a fairly strict formula in order for Panda3D to be able to make sense of your scene.

Teaching how to use Maya is beyond the scope of this manual. We assume that you already know how to use maya, and especially the hypershade. The only purpose of this section is to show you what configurations are understood by Panda3D.

The first step in texturing your model is to apply a Phong shader. This is the only kind of shader that Panda3D understands. You can apply a phong shader using the Maya menus:

Next, tell maya that you want to control the color of the surface by clicking the color button:

Now create a file texture - this will control the color of the surface:

Set the filename. Once you do this, you will be able to see your texture in Maya:

You now have a straightforward textured model that Panda3D can export.

Advanced Texturing

You may wish to use Panda's advanced texturing abilities: multitexturing, normal maps, gloss maps, transparency, or glow maps. To use these features, you will need to access Maya's hypershade editor:

A straightforwardly textured model will look like this in the hypershade:

To apply a normal map, create another file texture and connect the "out color" of the normal map to the "normal camera" input of the phong shader. The normal map must be a true normal map (you can recognize them by their light blue color), not a bump map (which are black-and-white.)

Unfortunately, there is a maya button to help you set up a bump map, but what it creates is not correct for Panda3D. If you use the button, you end up with the following incorrect configuration. So far as I know, the only way to get the correct configuration (shown above) is to set it up manually in the hypershade. The incorrect configuration is:

You may wish for parts of your model to be transparent. This is a little tricky, because Panda3D understands "opacity maps," and maya understands "transparency maps," which are opposites of each other. (In an opacity map, white is opaque, in a transparency map, black is opaque.) To set it up, you will need to create a reverser-object:

You may also wish to control which areas of the model are glossy, and which are not. Panda3D calls this a "gloss map." To do this, create a black-and-white image and connect it to the "specularColor" input of the phong shader. Note that it must be black-and-white, or Panda3D won't understand it.

You may also want to create glowing areas on your model. Panda3D calls this a "glow map." I have also heard it called a "self-illumination map" or an "incandescence map." Create a black-and-white image and connect it to the "incandescence" input of the phong shader:

Creating a glow map will cause those parts of the model which are glowing to be bright even when the light is dark. However, it will not by itself create a full-featured bloom effect. To get that, you need to use a bloom filter in your Panda3D program in conjunction with the glow map. There is a Sample Programs: Glow Filter that demonstrates this process.

Finally, there is plain old multitexturing. Create this structure in the hypershade, using a "Layered Texture" (NOT a "Layered Shader!"):

This will cause the two textures to be multiplied, which is the simplest form of multitexturing. You can also obtain certain other effects by changing the settings of the Layered Texture, however, this functionality is very incomplete at this time. For the most part, you can only multiply.

Merging Textures

The Maya exporter may will try to merge textures. For example, if your model has an RGB color map, and a black-and-white opacity map, then the exporter will consider loading them together as a single RGBA texture.

In order to merge two textures, the following rules must be satisfied:

1. Their UV mappings must be identical. 2. Their filenames must have a common prefix.

By "common prefix," I mean that the files "house-color.jpg" and "house-opacity.jpg" can be merged, because they both start with the common prefix "house-". A common prefix consists of a series of alphanumeric characters followed by a dash. If you do not want two files merged, use a different file naming convention.

Flagging Objects From Maya

The Maya exporter is able to automatically flag objects for collisions if the objects are flagged properly in Maya. There is a MELscript in the available in the CVS that helps flag objects in Maya.

Installation

  1. Open up maya's script editor (Window>General Editors>Script Editor)
  2. Open the script in the editor and execute it (Ctrl+Enter or Script>Execute)
  3. Type "eggObjectFlags()" at the script editor and select the text.
  4. Middle mouse drag the selection into your custom shelf.
  5. Optional: go to the shelf editor and give it a custom icon/name

Usage

  1. Select the objects that you want to tag and press the shelf script.
  2. Set the object type by going to the channel box of the object, there should be a new attribute called Egg Object TypesN.
  3. Set the object type to the desired value.
Object Type | Meaning
barrier | The geometry defined at this root and below defines an invisible collision solid.
trigger | The geometry defined at this root and below defines an invisible trigger surface.
sphere | The geometry is replaced with the smallest collision sphere that will enclose it. Typically you model a sphere in polygons and put this flag on it to create a collision sphere of the same size.
tube | As in sphere, above, but the geometry is replaced with a collision tube (a capsule). Typically you will model a capsule or a cylinder in polygons.
bubble | A collision bubble is placed around the geometry, which is otherwise unchanged.
ghost | It means that the geometry beginning at this node and below should never be collided with--characters will pass through it.
backstage | It means that the geometry at this node and below should not be translated. This will normally be used on scale references and other modeling tools.

Running the Exporter

Maya's exporter can be run from the command prompt or via a graphical panel. The command line utility is called maya2egg. There are multiple versions of it corresponding to different versions of maya. For instance, the version to use for Maya 5.0 is maya2egg5, for 6.0 its maya2egg6 and for 6.5 its maya2egg65.

The following is an example of how to convert a file(maya binary .mb) if using Maya 6.0 through the command line.

The egg file will contain the filenames of the textures. These texture pathnames will be stored as relative paths, relative to the location of the egg file. For example, let's say that the files are laid out like this:

c:\My Models\Maya Files\Character.mb
c:\My Models\Egg Files\Character.egg
c:\My Models\Textures\Character.png

In that case, the command to export the model is:

c:\
cd c:\My Models\
maya2egg -o "Egg Files/Character.egg" "Maya Files/Character.mb"

Note that Panda Filename Syntax uses forward slashes, even under Windows, and this applies to the exporter as well. After doing this export, the character egg will contain the following texture reference:

"../Textures/Character.png"

Again, notice that this pathname is relative to the egg file itself. Many artists find it convenient to keep everything in the same directory, to avoid having to think about this. This approach works fine.

The above conversion process will turn the character into a static model. Models which are rigged (they have bones to help them animate), skinned (polygons attached to the bones/skeleton), and are animated need to use one of the following options:

maya2egg -a model -o eggFileName.egg mayaFileName.mb
maya2egg -a chan -o eggFileName.egg mayaFileName.mb
maya2egg -a pose -o eggFileName.egg mayaFileName.mb
maya2egg -a both -o eggFileName.egg mayaFileName.mb

The meanings of these options are:

-a model | Save only the skinned and boned model, ready for animation but with no animation attached. This is ideal for models with several event- or interaction-based animations.
-a chan | Save only the animation data on the current model, but not the model itself. This is ideal applying animations to an already-existing model egg file that is ready to receive animation. A model may have several animation channels applied to it.
-a pose | Save the model in the current key position of the animation applied to it. This position must be selected before choosing to export the file. This format does not save the animation.
-a both | This will export the model and animation out as one file.

There are many options to maya2egg. For a complete list, run maya2egg with the -h argument.

maya2egg -h

Using the Graphical Front-End

As of Panda3D 1.0.4, there is a graphical front-end to maya2egg. To run the graphical Maya exporter, drag MayaPandaTool.mel from the panda plugins directory into the Maya workspace.

The UI window(below) will appear.

You can alternatively load the .mel file from the script editor. To save space, the graphical tool does not have access to all of the features of the exporter. It is designed for rapid verification of assests. The features you can execute with the graphical tool are identical to the respective ones of the command line exporter listed below.

Or, you can integrate the UI interface to Maya. There is a very convenient way to launch the MayaPandaTool, using Maya shelf to store the MEL script :
[1] open the Script Editor :
Window > General Editors > Script Editor
[2] In Script Editor, load the MayaPandaTool MEL script :
File > Open Script
[3] select the MEL text (press Ctrl+A)
[4] using middle mouse button, drag the selected text onto the shelf
[5] (optional) using Shelf Editor, you can change the image of Panda exporter icon to distinguish it from the others.

Anytime you need to open the Panda Exporter, just click the icon on the shelf.

For Windows users :
If you want a faster launch, you can put the MEL file on the QuickLaunch. Press Ctrl while dragging the MEL file onto QuickLaunch bar. If you don't press Ctrl, the actual file dropped to QuickLaunch is only the shortcut to the MEL file.
Anytime you need to open the Panda exporter, just drag it from QuickLaunch bar to the 3D window of Maya.


Panda3D Manual: Converting from Blender

Currently, there are two ways to get data from Blender into Panda3D.

Option 1: The "X" File format

There exists a free plugin for Blender that can export "X" (DirectX native) file format. Save the file from blender as an X file, then load it directly into Panda3D, which can read X file format. Alternately, if you're concerned about long load times (panda has to translate the file at load time), then pre-convert the model from X to Egg to Bam using the conversion programs supplied with Panda3D (x2egg, egg2bam).

Whenever you save a model in a non-native file format, you need to ask yourself: "does this file format support everything I need?" For example, when you save out a model in 3DS file format, you automatically lose all bone and animation data, because the 3DS file format doesn't contain bone and animation data. In the case of the X file format, you're in good shape: it's a fairly powerful file format, supporting vertices and triangles, bones and animation.

Note however, when an animated X file is converted to egg, the resulting egg file only plays the keyframes, but not whats supposed to be in between. For example, an animation could exist that should spawn 200 frames, gets sized down to about 40, and playback looks shakey. This shakeyness happens because the X file format supports the concept of keyframes, with implicit frames interpolated between them. The egg file format is explicit. An egg file must give all of the frames of an animation, even the frames that appear between "keyframes".

Therefore, a run of x2egg with an X file that omits a lot of frames between keyframes, will product a shakey animation. The only solution is to ensure your X files are generated with all frames. Testing of different X file exporters may be required.

Further, panda's native egg file format supports some esoteric things. For example, it supports blend targets (morph animations) and motion path curves, which are not supported by the X file format.

Caution: it has recently been discovered that there are two bugs in panda's X-file parser. One, it is case-sensitive and it should not be. Two, it does not handle hyphens in identifiers correctly. These two bugs will be fixed in an upcoming version of Panda3D. For information on temporary workarounds, search the forums.

Option 2: The Egg export Plugin for Blender

There are several Blender plugins contributed by Panda3D users.

Chicken is the most recently updated, documented and feature complete. It supports static meshes and armature animation, materials, vertex colors, alpha textures, tags, object types, etc. It also has advanced features such as automatic invocation of Panda tools (egg2bam, egg-optchar and pview) and Motion Extraction. You can find it at http://chicken-export.sf.net

EggX is another exporter that does materials, non-procedural textures, armature animation, static meshes. It can be found at http://www.wickwack.com/panda

Another exporter that only supports static meshes can be found at http://xoomer.virgilio.it/glabro1/panda.html


Panda3D Manual: Converting from SoftImage

WRITE ME: Add information about X file format.


Panda3D Manual: Converting from Milkshape 3D

Currently, the best way to get data from Milkshape into Panda is to use X (DirectX native) file format. Save the model as X from Milkshape, then load it directly into Panda, which can read X file format. Alternately, if you're concerned about long load times (panda has to translate the file at load time), then pre-convert the model from X to Egg to Bam using the conversion programs supplied with Panda3D (x2egg, egg2bam).

Note that Milkshape 3D contains two X export plugins. I have heard that one of them does not work correctly. This may require some experimentation.

Whenever you save a model in a non-native file format, you need to ask yourself: "does this file format support everything I need?" For example, when you save out a model in 3DS file format, you automatically lose all bone and animation data, because the 3DS file format doesn't contain bone and animation data. In the case of the X file format, you're in good shape: it's a fairly powerful file format, supporting vertices and triangles, bones and animation.

However, egg file format supports some esoteric things. For example, it supports blend targets (morph animations), which are not supported by the X file format.

More Detailled explanations for MS3D users

You can use MS3D to create X Files (both static or animated) to be converted by Panda3d x2eggconverter. </br>

In MS3D there is two Direct X .X exporter: Direct 8.0 and DirectX(JT). So far i've managed to use only DirectX 8.0 File. (DirectX JT got a lot more parameters and only a few combination of it seemed to work but not on a predictable basis). I'll talk only for animation with bones (not tested other ones).But this exporter work also for static meshes.

A) Before Exporting to .X you must ensure: - no null material or null name in texture in your model (MS3D won't block you but will crashes during the export) - no hyphen in your bones names (underscore is ok) (No issue in MS3D but issue with panda converter). - animation mode is NOT enabled

B) To export , use Direct 8.0 file export. Select the required boxes. (Meshes, Materials ,Animations) if you selected less than all checboxes (material animations...) you will have to edit manually the x files to remove the last 1 or 2 "}" of the file . before using X2egg to convert . It's ok to leave defaut settings (Lock Root Bone and 1 as Frame offset). Warning: Export can be very long in case of big models/animations.

C) Convert using X2egg converter

Warning:if you run X2egg without special args, you will need to have your textures also in the same directory than the x files. Don't be surprised if .egg file size is 6 times your .X file size, it's pretty normal due to more explicit information in the .egg file format. In case size is an issue, bamming the .egg file will reduce the size and optimize loading time.

Also , before converting to .egg , you can load your .x in pview to check everything is fine.

D)TIPS: depending if you make your models fully in MS3D or import it from Poser, you may find an issue : all animations applied to root bone instead of correct bone. You can solve it in MS3D by regrouping all materials, export to HL SMD (1 or 2 ) then import again and export to .X.

NB: this have been written by a coder not an artist :-)

Bugs in the Process

Caution: at one time, it was discovered that there were two bugs in panda's X-file importer. One, it was case-sensitive and it should not be. Two, it did not handle hyphens in identifiers correctly. It is unknown whether or not these bugs have been fixed.


Panda3D Manual: Converting from GMax

To convert models to Panda from GMax, you must first convert models to .X format, and then load them into Panda as .X files, or convert them further using x2egg and/or egg2bam.

There is a fair amount of work involved in setting up the GMax-to-X converter, but once it is set up the conversion process is reportedly very easy.

Installation

There are several plug-ins required to export .X files from gmax:

1. First download the ‘Gmax gamepack SDK’ found at this link: FlightSimulator exporter plugin. Size is about 15Mb. Although only 3 files are actually needed, they are not available as separate downloads, so unfortunately you’ll need to download the whole thing.

2. Next, download programs 'MDLCommander' and 'Middleman'.

3. After download, you’ll see that the 'fs2004_sdk-gmax-setup' is an exe. If you install it in the default gmax directory, you’ll end up with a lot of extra stuff that you don’t need. So create a new folder somewhere on your hard drive and install it there.

4. When done, open the folder, go to gamepacks > fs2004 > plugins. And copy all 3 files: 'FSModelExp.dle', 'makemdl.exe' and 'makemdl.parts.xml' to your main ../gmax/plugins folder.

5. You need to rename two of the files. Right click on 'makemdl.exe' and rename it to to 'mkmdl.exe'. Then right click on 'makemdl.parts.xml' and rename it to 'mkmdl.parts.xml' (without the quotes).

6. Next, unzip 'MDLCommander.zip'. Then copy 'mdlcommander.exe' to your main ../gmax/plugins folder. This file also needs to be renamed. Right click on 'mdlcommander.exe' and rename it to 'makem.exe'.

7. Finally, unzip 'Middleman13beta3.zip'. Then copy 'makemdl.exe' to your main ../gmax/plugins folder. That’s it, you’re done!

Using

To convert your gmax model to .X format: Go to ‘File > Export’ and select ‘Flightsim Aircraft Object (.MDL) from the file type dropdown. Type in a filename and click Save. The Middleman dialog window should now appear. Click the ‘Options’ tab and check ‘SaveXFile’ (this saves the x file) and ‘nocompile’ (this tells mdlcommander to only create an .X file not mdl/bgl). Then click the GO button.

After a few seconds the dialog will close and your newly exported .X model should be in the directory where you saved it to.

Bugs in the Process

The GMax converter writes slightly nonstandard .X files; it writes the name "TextureFileName" instead of "TextureFilename" for each texture reference. It may also generate hyphens in identifiers. Both of these can confuse the X file parser in Panda3D version 1.0.4 and earlier (this will be fixed in a future release of Panda). In the meantime, the temporary workaround is to edit the .X file by hand to rename these strings to the correct case and remove hyphens.


Panda3D Manual: Converting from other Formats

There are several tools included with panda that can convert various file formats into egg file format:

lwo2egg
dxf2egg
flt2egg
vrml2egg
x2egg

Note that panda can load any of these file formats without conversion, doing so causes the conversion to occur at runtime.

Also, be aware: many of these file formats are limited. Most do not include bone or animation data. Some do not store normals. Currently the .x format is the only one of these that stores bones, joints and animations.


Panda3D Manual: Converting Egg to Bam

Panda's native egg file format is human-readable. This is convenient, but the files can get very large, and they can a little bit slow to load. To accelerate loading, Panda supports a second native format, bam. These files are smaller and are loaded very rapidly, but they cannot be viewed or edited in a text editor. Also, bam files are specific to the version of Panda they are created with, so they are not a good choice for long-term storage of your models.

Texture pathnames in an egg file are first assumed to be relative to the egg file itself. If the texture is not found at that location, panda will search its model-path, which is specified in the panda config file. When doing this, panda concatenates the directory which is part of the model-path to the entire string in the egg-file. So if the model-path names the directory "/d/stuff", and the texture-path in the egg file is "mytextures/tex.png", then panda looks in "/d/stuff/mytextures/tex.png."

Texture pathnames in a bam file may be stored relative to the bam file itself, relative to a directory on the model-path, or with a full pathname to the file, depending on the parameters given to the egg2bam program.

The program egg2bam is used to convert egg files to bam files. Egg2bam will complain if the textures aren't present. You must install the textures (into your model path) before you convert the bam file. You can run the egg2bam program as follows:

egg2bam -ps rel -o bamFileName.bam eggFileName.egg

Here, "-ps rel" means to record the textures in the bam filename relative to the filename itself; if you use this option, you should ensure that you do not move the bam file later without also moving the textures. (The default option is to assume the textures have already been installed along the model path, and record them relative to the model path. If you use the default option, you should ensure the textures are already installed in their appropriate place, and the model-path is defined, before you run egg2bam.)

The egg2bam program accepts a number of other parameters that may be seen by running egg2bam –h.


Panda3D Manual: Parsing and Generating Egg Files

Transforms and Vertices

The egg syntax defines all transforms, including joint transforms, relative to the parent node only. When the animation is played, Panda accumulates the transforms for each joint.

Although joints are defined using a local transform, vertices are defined in an egg file using global coordinates, which is irrespective of transforms appearing within the egg file. This means when Panda loads the egg file is loaded, the vertex coordinates given in the egg file must be pre-transformed by the appropriate inverse matrix to compensate.

Custom .egg Readers/Writers

When writing an importer or exporter for panda, you have two choices.

One option is to use the panda runtime library, which includes code for reading, parsing, storing, and emitting Egg files. This approach can save you a great deal of effort. However, it does require that you link with the panda runtime system, which may be inconvenient if you wish to distribute a small, standalone file translator.

If you decide to use the panda runtime system, the classes you will need to use are the ones whose names start with "Egg," ie, pandac.EggData, pandac.EggVertex, pandac.EggPolygon, pandac.EggGroup, and so forth. Like all panda classes, these are documented in the API reference manual.

The other alternative is to parse/generate the Egg file entirely by yourself. In this case, you will need to read the syntax documentation for egg files. This documentation is part of the source code on sourceforge. The file format is human-readable, and fairly straightforward.

If you are writing a program to generate Egg files, either approach is equally good. However, if you are writing a program to parse Egg files, we do recommend using the panda runtime library, rather than writing your own parser, for the simple reason that it is difficult to write a parser that accepts all valid Egg files. Also, the Egg syntax might be extended from time to time, and relying on the runtime library to parse the Egg syntax will ensure that your program continues to parse future Egg files.


Panda3D Manual: Egg Syntax

The Egg Syntax

This is a condensed version of the Egg Syntax document, incorporating only syntax definitions in common use. For complete documentation, please see the Egg Syntax documentation from the Panda3D CVS.

Contents

  • 1 General Syntax
  • 2 Local Information Entries
  • 3 Global Information Entries
    • 3.1 Coordinate System
    • 3.2 Textures
      • 3.2.1 Texture Scalars
    • 3.3 Materials
      • 3.3.1 Material Scalars
    • 3.4 Vertex Pool
      • 3.4.1 Vertices
        • 3.4.1.1 Vertex Attributes
  • 4 Geometry Entries
    • 4.1 Polygons
      • 4.1.1 Polygon Attributes
  • 5 Grouping Entries
    • 5.1 Grouping Attributes
    • 5.2 Group Scalars
    • 5.3 Other Group Attributes
      • 5.3.1 Billboard
      • 5.3.2 SwitchCondition
      • 5.3.3 Tag
      • 5.3.4 Collide
      • 5.3.5 ObjectType
  • 6 Joint Nodes
  • 7 Bundle and Table entries

General Syntax

Egg files consist of a series of sequential and hierarchically-nested entries. In general, the syntax of each entry is:

 <Entry-type> name { contents }

Comment text should be enclosed in quotation marks:

 <Comment> { text }

Local Information Entries

These nodes contain information relevant to the current level of nesting only.

 <Scalar> name { value }

Scalars are attributes of the entry the Scalar is in. Name is the attribute name with value as the contents of that attribute.

Global Information Entries

These nodes contain information relevant to the file as a whole. They can be nested along with geometry nodes, but this nesting is irrelevant and the only significant placement rule is that they should appear before they are referenced.

Coordinate System

 <CoordinateSystem> { string }

This entry indicates the coordinate system used in the egg file; the egg loader will automatically make a conversion if necessary. The following strings are valid: Y-up, Z-up, Y-up-right, Z-up-right, Y-up-left, or Z-up-left. (Y-up is the same as Y-up-right, and Z-up is the same as Z-up-right.)

Textures

This describes a texture file that can be referenced later with <TRef> { name }.

<Texture> name {
  filename
  [scalars]
}

Texture Scalars

This is not a complete list of texture attributes. Please refer to the full document for all scalars and their value definitions

 <Scalar> alpha-file { alpha-filename }
 <Scalar> alpha-file-channel { channel }
 <Scalar> format { format-definition }
 <Scalar> type { texture-type }
 <Scalar> envtype { environment-type }
 <Scalar> tex-gen { mode }
 <Scalar> stage-name { name }
 <Scalar> uv-name { name }
 <Scalar> rgb-scale { scale }
 <Scalar> alpha-scale { scale }
 <Scalar> alpha { alpha-type }
 <Transform> { transform-definition }

Materials

This defines a set of material attributes that may later be referenced with <MRef> { name }.

<Material> name {
  [scalars]
}

Material Scalars

 <Scalar> diffr { number }
 <Scalar> diffg { number }
 <Scalar> diffb { number }
 <Scalar> diffa { number }
 <Scalar> ambr { number }
 <Scalar> ambg { number }
 <Scalar> ambb { number }
 <Scalar> amba { number }
 <Scalar> emitr { number }
 <Scalar> emitg { number }
 <Scalar> emitb { number }
 <Scalar> emita { number }
 <Scalar> specr { number }
 <Scalar> specg { number }
 <Scalar> specb { number }
 <Scalar> speca { number }
 <Scalar> shininess { number }
 <Scalar> local { flag }

Vertex Pool

A vertex pool is a set of vertices. All geometry is created by referring to vertices by number in a particular vertex pool. There may be one or several vertex pools in an egg file, but all vertices that make up a single polygon must come from the same vertex pool. The body of a <VertexPool> entry is simply a list of one or more <Vertex> entries, as follows:

<VertexPool> name {
  <Vertex> number1 {
  }
  <Vertex> numer2 {
  }
  ...
}

Vertices

A <Vertex> entry is only valid within a vertex pool definition. The number is the index by which this vertex will be referenced. It is optional; if it is omitted, the vertices are implicitly numbered consecutively beginning at one. If the number is supplied, the vertices need not be consecutive.

The vertex's coordinates are always given in world space, regardless of any transforms before the vertex pool or before the referencing geometry. If the vertex is referenced by geometry under a transform, the egg loader will do an inverse transform to move the vertex into the proper coordinate space without changing its position in world space. One exception is geometry under an <Instance> node; in this case the vertex coordinates are given in the space of the <Instance> node. (Another exception is a <DynamicVertexPool>; see below.)

  <Vertex> number {
    x y z [w]
    [attributes]
  }
Vertex Attributes
 <Normal> { x y z [morph-list] }
 <RGBA> { r g b a [morph-list] }
 <UV> [name] { u v [w] [tangent] [binormal] [morph-list] }
 <Dxyz> target { x y z }

Geometry Entries

Geometry entries reference Vertex pool entries to generate renderable geometry for Panda to use.

Polygons

A polygon consists of a sequence of vertices from a single vertex pool. Vertices are identified by pool-name and index number within the pool; indices is a list of vertex numbers within the given vertex pool. Vertices are listed in counterclockwise order. Although the vertices must all come from the same vertex pool, they may have been assigned to arbitrarily many different joints regardless of joint connectivity (there is no "straddle-polygon" limitation). See Joints, below.

The polygon syntax is quite verbose, and there isn't any way to specify a set of attributes that applies to a group of polygons--the attributes list must be repeated for each polygon. This is why egg files tend to be very large.

<Polygon> name {
    [attributes]
    <VertexRef> {
        indices
        <Ref> { pool-name }
    }
}

Polygon Attributes

 <TRef> { texture-name }
 <Texture> { filename }
 <MRef> { material-name }
 <Normal> { x y z [morph-list] }
 <RGBA> { r g b a [morph-list] }
 <BFace> { boolean-value }
 <Scalar> bin { bin-name }
 <Scalar> draw_order { number }
 <Scalar> visibility { hidden | normal }

Grouping Entries

A <Group> node is the primary means of providing structure to the egg file. Groups can contain vertex pools and polygons, as well as other groups. The egg loader translates <Group> nodes directly into PandaNodes in the scene graph (although the egg loader reserves the right to arbitrarily remove nodes that it deems unimportant--see the <Model> flag, below to avoid this). In addition, the following entries can be given specifically within a <Group> node to specify attributes of the group.

<Group> name {
  [attributes]
  [scalars]
  [SwitchCondition]
  [Tag]
  [Collide]
  [ObjectType]
}

Grouping Attributes

 <DCS> { boolean-value }
 <DCS> { dcs-type }
 <Model> { boolean-value }
 <Dart> { boolean-value }
 <Switch> { boolean-value }

Group Scalars

 <Scalar> fps { frame-rate }
 <Scalar> bin { bin-name }
 <Scalar> draw_order { number }
 <Scalar> visibility { hidden | normal }
 <Scalar> decal { boolean-value }
 <Scalar> decalbase { boolean-value }
 <Scalar> collide-mask { value }
 <Scalar> from-collide-mask { value }
 <Scalar> into-collide-mask { value }
 <Scalar> blend { mode }
 <Scalar> blendop-a { mode }
 <Scalar> blendop-b { mode }
 <Scalar> blendr { red-value }
 <Scalar> blendg { green-value }
 <Scalar> blendb { blue-value }
 <Scalar> blenda { alpha-value }

Other Group Attributes

Billboard

 <Billboard> { type }

This entry indicates that all geometry defined at or below this group level is part of a billboard that will rotate to face the camera. Type is either "axis" or "point", describing the type of rotation.

Billboards rotate about their local axis. In the case of a Y-up file, the billboards rotate about the Y axis; in a Z-up file, they rotate about the Z axis. Point-rotation billboards rotate about the origin.

There is an implicit <Instance> around billboard geometry. This means that the geometry within a billboard is not specified in world coordinates, but in the local billboard space. Thus, a vertex drawn at point 0,0,0 will appear to be at the pivot point of the billboard, not at the origin of the scene.

SwitchCondition

 <SwitchCondition> {
    <Distance> {
       in out [fade] <Vertex> { x y z }
    }
 }

The subtree beginning at this node and below represents a single level of detail for a particular model. Sibling nodes represent the additional levels of detail. The geometry at this node will be visible when the point (x, y, z) is closer than "in" units, but further than "out" units, from the camera. "fade" is presently ignored.

Tag

 <Tag> key { value }

This attribute defines the indicated tag (as a key/value pair), retrievable via NodePath::get_tag() and related interfaces, on this node.

Collide

 <Collide> name { type [flags] }

This entry indicates that geometry defined at this group level is actually an invisible collision surface, and is not true geometry. The geometry is used to define the extents of the collision surface. If there is no geometry defined at this level, then a child is searched for with the same collision type specified, and its geometry is used to define the extent of the collision surface (unless the "descend" flag is given; see below).

   Valid types so far are:

   Plane

     The geometry represents an infinite plane.  The first polygon
     found in the group will define the plane.

   Polygon

     The geometry represents a single polygon.  The first polygon is
     used.

   Polyset

     The geometry represents a complex shape made up of several
     polygons.  This collision type should not be overused, as it
     provides the least optimization benefit.

   Sphere

     The geometry represents a sphere.  The vertices in the group are
     averaged together to determine the sphere's center and radius.

   InvSphere

     The geometry represents an inverse sphere.  This is the same as
     Sphere, with the normal inverted, so that the solid part of an
     inverse sphere is the entire world outside of it.  Note that an
     inverse sphere is in infinitely large solid with a finite hole
     cut into it.

   Tube

     The geometry represents a tube.  This is a cylinder-like shape
     with hemispherical endcaps; it is sometimes called a capsule or
     a lozenge in other packages.  The smallest tube shape that will
     fit around the vertices is used.

   The flags may be any zero or more of:

   event

     Throws the name of the <Collide> entry, or the name of the
     surface if the <Collide> entry has no name, as an event whenever
     an avatar strikes the solid.  This is the default if the
     <Collide> entry has a name.

   intangible

     Rather than being a solid collision surface, the defined surface
     represents a boundary.  The name of the surface will be thrown
     as an event when an avatar crosses into the interior, and
     name-out will be thrown when an avater exits.

   descend

     Instead of creating only one collision object of the given type,
     each group descended from this node that contains geometry will
     define a new collision object of the given type.  The event
     name, if any, will also be inherited from the top node and
     shared among all the collision objects.

   keep

     Don't discard the visible geometry after using it to define a
     collision surface; create both an invisible collision surface
     and the visible geometry.

   level

     Stores a special effective normal with the collision solid that
     points up, regardless of the actual shape or orientation of the
     solid.  This can be used to allow an avatar to stand on a
     sloping surface without having a tendency to slide downward.

ObjectType

 <ObjectType> { type }

This is a short form to indicate one of several pre-canned sets of attributes. Type may be any word, and a Config definition will be searched for by the name "egg-object-type-word", where "word" is the type word. This definition may contain any arbitrary egg syntax to be parsed in at this group level.

   A number of predefined ObjectType definitions are provided:

   barrier

     This is equivalent to <Collide> { Polyset descend }.  The
     geometry defined at this root and below defines an invisible
     collision solid.

   trigger

     This is equivalent to <Collide> { Polyset descend intangible }.
     The geometry defined at this root and below defines an invisible
     trigger surface.

   sphere

     Equivalent to <Collide> { Sphere descend }.  The geometry is
     replaced with the smallest collision sphere that will enclose
     it.  Typically you model a sphere in polygons and put this flag
     on it to create a collision sphere of the same size.

   tube

     Equivalent to <Collide> { Tube descend }.  As in sphere, above,
     but the geometry is replaced with a collision tube (a capsule).
     Typically you will model a capsule or a cylinder in polygons.

   bubble

     Equivalent to <Collide> { Sphere keep descend }.  A collision
     bubble is placed around the geometry, which is otherwise
     unchanged.

   ghost

     Equivalent to <Scalar> collide-mask { 0 }.  It means that the
     geometry beginning at this node and below should never be
     collided with--characters will pass through it.

   backstage

     This has no equivalent; it is treated as a special case.  It
     means that the geometry at this node and below should not be
     translated.  This will normally be used on scale references and
     other modeling tools.

   There may also be additional predefined egg object types not
   listed here; see the *.pp files that are installed into the etc
   directory for a complete list.

 <Transform> { transform-definition }
 <VertexRef> { indices <Ref> { pool-name } }

Joint Nodes

<Joint> name {
  [transform]
  [ref-list]
  [joint-list]
}

A joint is a highly specialized kind of grouping node. A tree of joints is used to specify the skeletal structure of an animated character.

A joint may only contain one of three things. It may contain a <Transform> entry, as above, which defines the joint's unanimated (rest) position; it may contain lists of assigned vertices or CV's; and it may contain other joints.

A tree of <Joint> nodes only makes sense within a character definition, which is created by applying the <DART> flag to a group. See <DART>, above.

The vertex assignment is crucial. This is how the geometry of a character is made to move with the joints. The character's geometry is actually defined outside the joint tree, and each vertex must be assigned to one or more joints within the tree.

This is done with zero or more <VertexRef> entries per joint, as the following:

 <VertexRef> { indices [<Scalar> membership { m }] <Ref> { pool-name } }

This is syntactically similar to the way vertices are assigned to polygons. Each <VertexRef> entry can assign vertices from only one vertex pool (but there may be many <VertexRef> entries per joint). Indices is a list of vertex numbers from the specied vertex pool, in an arbitrary order.

The membership scalar is optional. If specified, it is a value between 0.0 and 1.0 that indicates the fraction of dominance this joint has over the vertices. This is used to implement soft-skinning, so that each vertex may have partial ownership in several joints.

The <VertexRef> entry may also be given to ordinary <Group> nodes. In this case, it treats the geometry as if it was parented under the group in the first place. Non-total membership assignments are meaningless.

Bundle and Table entries

A table is a set of animated values for joints. A tree of tables with the same structure as the corresponding tree of joints must be defined for each character to be animated. Such a tree is placed under a <Bundle> node, which provides a handle within Panda to the tree as a whole.

Bundles may only contain tables; tables may contain more tables, bundles, or any one of the following (<Scalar> entries are optional, and default as shown):

<S$Anim> name {
  <Scalar> fps { 24 }
  <V> { values }
}


Panda3D Manual: Previewing 3D Models in Pview

Pview or Panda Viewer is a model and animation viewer for egg and bam files. This allows users to see if their files have converted correctly without having to create a Panda3D program. Pview is accessed through a command prompt.

To view a model that has been converted to an egg or bam file, type the following:

pview modelFile.egg

To view a character model with animations, simply add the name of the file with the animation.

pview modelFile.egg animationFile.egg

Here's an example based on the panda model distributed with panda source.

pview panda

A new window should pop-up and here's what you should see -

There are some controls and hotkeys available while using pview. To see the whole list press shift-question mark in the pview window. To turn this list off press shift-question mark again. For convenience here is the full list of as the time of writing:

Left-click and drag Mouse | Moves the model up, down, left, and right relative to the camera
Middle-click and drag Mouse | Rotates the model around its pivot
Right-click and drag Mouse | Moves the model away and towards the camera
f | Report framerate. The current framerate is output on the console window.
w | Toggle wireframe mode
t | Toggle texturing
b | Toggle back face (double-sided) rendering
i | Invert (reverse) single-sided faces
l | Toggle lighting
p | Toggle per-pixel lighting
c | Recenter view on object
shift-c | Toggle collision surfaces
shift-b | Report bounding volume
shift-l | List hierarchy
shift-a | Analyze hierarchy
h | Highight node
arrow-up | Move highlight to parent
arrown-down | Move highlight to child
arrow-left | Move highlight to sibling
arrow-right | Move highlight to sibling
shift-s | Activate PStats
f9 | Take screenshot
, | Cycle through background colors
shift-w | Open new window
alt-enter | Toggle between full-screen and windowed mode
2 | Split the window
W | Toggle wireframe
escape | Close Window
q | Close Window

As of this writing there is a small caveat in loading textures for models into pview. Textures are searched for relative to the directory that pview is called from, not the directory that the model is in. Either make sure that the texture path is set correctly in the Config.prc file or ensure the bam or egg file look for the textures correctly (relative to where you are calling pview). The easiest way to get around this is to call pview from the directory that the model is placed.


Panda3D Manual: Building an installer using packpanda

Note: In version 1.6.0, support was added for Linux. Before this version, you will only be able to use packpanda on a windows system.

Packpanda is a utility that lets you create an automatic self-extracting installer for a Panda3D game. On linux, it is capable of creating an .rpm or .deb package. On Windows, it creates an installation wizard, that will look like this:

When the installation is done, the end-user will find your game in his start menu:

The end-user doesn't need to have a copy of panda. He doesn't even need to know that he is using panda. He just installs the game and plays it.

Files that your Game should Contain

Before you pack up your game, you need to put all of your game files into a single directory (which may have as many subdirectories as you desire). This directory will be packed up and shipped to the user, along with the panda runtime system. Your game directory needs to contain several files:

main.py. This is your main program. When the user clicks on the start-menu entry for the game, this is the file that will get executed.

installer.bmp. Windows only. This image will appear on the installer screen. If present, it must be a 164x314 windows BMP file. This file is not required.

license.txt. This is your game's software license. The file, if present, must be plain ascii. The game's license will appear inside the installer, and will also be copied to the game's installation directory. Of course, your license only covers the code that you wrote, not panda itself, which is covered by the panda license. The license file is not required.

icon.ico. Windows only. This is your game's icon, which will appear in the start menu. If you don't supply an icon, the panda icon will be used instead. This file is not required.

Packing up your Game

The command to pack up your game is "packpanda", and you must specify the "--dir" command line option to tell it the name of the directory containing your game. Packpanda will immediately analyze your game and print out a status report:

In this example, packpanda has inferred that the name of the game is "Airblade," based on the directory name. On Windows, it will install the game into "C:\Airblade", and to add the start menu folder "Airblade". It plans to call the installer "Airblade.exe" (append .rpm or .deb for linux). Later, we will tell you how to override some of these defaults.

As you can see, packpanda is looking inside the game directory for the files mentioned above: main.py, installer.bmp, icon.ico, and license.txt. It notes that some of those files are "missing", which is not a problem. The only file that is required is main.py.

Packpanda can clean up your source tree before shipping it. When doing so, packpanda never modifies your original copy of the game. Instead, it copies the game to a temporary directory, as seen above.

Automatically generating BAM and PYC files while Packing

Packpanda can optionally create BAM and PYC files and ship them to the end-user. To ask it to do so, use the following command-line options;

packpanda --bam # Generate and ship BAM files
packpanda --pyc # Generate and ship PYC files

These command line options do not remove the corresponding EGG and PY files from the distribution. If you wish to remove EGG and PY files, you need to use the --rmext option, documented below.

When packpanda generates a BAM or PYC file, it puts it in the same directory as the corresponding EGG or PY file. If an EGG file contains a texture path, then the generated BAM will contain a relative texture path that is relative to the game's root directory. Packpanda makes sure that your game's root directory ends up on the model path.

EGG Verification and PY Verification

Packpanda will check all of your EGG and PY files to make sure that they compile correctly. It checks EGG files by running them through egg2bam. It checks PY files by running the python compiler on them. If any file fails, the game will not be packed.

In fact, this is related to the --bam and --pyc options mentioned earlier. In fact, packpanda always generates bam and pyc files. However, if you do not specify the --bam and --pyc options, then these files will be generated, verified, and then discarded. The effect of the --bam and --pyc options is to cause these files to be shipped to the end-user.

Preloading the Model Cache

If your distribution includes EGG files, and if you choose not to remove those using --rmext egg, then panda will convert them to BAM files at install time. This is a somewhat time consuming process, it makes the install take longer. If you want to avoid this, do not distribute EGG files --- instead, distribute BAM files, as explained above.

Stripping Files from the Distribution

Often, your master copy of a game contains files that should not be shipped to the end-user. For situations like this, packpanda contains command-line options to strip out unnecessary files:

packpanda --rmdir dir # Strip all directories with given name
packpanda --rmext ext # Strip all files with given extension

These options are particularly useful in several common situations:

To remove CVS directories: packpanda --rmdir CVS

To ship BAM instead of EGG: packpanda --bam --rmext egg

To ship PYC instead of PY: packpanda --pyc --rmext py

Changing the Game's Name

Normally, packpanda infers the game's name to be the same as the directory name. That isn't always convenient, especially when the game has a long name. The following command line option allows you to tell packpanda the name of the game:

packpanda --name "Evil Space Monkeys of The Planet Zort"

This string will show up in a number of places: in particular, throughout the installation dialogs, and in the start menu.

Version Numbers

If you wish, you can assign your game a version number using this command line option:

packpanda --version X.Y.Z # Assign a version number

The only thing this does is to add "X.Y.Z" to the install directory and to the start menu item. That, in turn, makes it possible for two versions of the same game to coexist on a machine without conflict.

Compression Speed

On Windows, packpanda uses a very good compression algorithm, but it's excruciatingly slow to compress. You can specify this command line option to make it go faster, at the cost of compression effectiveness: (not available on Linux)

packpanda --fast # Quick but not so great compression

Moving Beyond Packpanda

Packpanda has a lot of limitations. However, packpanda is actually a front end to more powerful systems. On Linux, it uses the system tools dpkg-deb and rpmbuild. On Windows, it uses NSIS, the "Nullsoft Scriptable Install System." NSIS is incredibly powerful, and very flexible, but unfortunately rather complicated to use. Packpanda hides all that complexity from you, but unfortunately, in so doing, it limits your options.

If you find yourself outgrowing packpanda, one sensible thing to do would be to learn how to use those systems directly. This is an easy transition to make. The first step is to simply watch packpanda in action. It will show you all of the commands it is executing. You can then copy those commands into a batch file. If you run that batch file, you're executing those base tools directly.

Once you have direct control over NSIS, you can begin editing the NSIS command-line options and the NSIS configuration file (packpanda.nsi). Of course, to do so, you'll need to first read the NSIS manual (available on the web). From that point forward, you have unlimited flexibility.


Panda3D Manual: Building Panda from Source

Note that in the past, it was very difficult to build panda. Things have improved. It is now fairly straightforward to download and compile panda. In particular, this is a sensible thing to do if you wish to add some functionality to panda.

To avoid possible consistency problems, the documentation for building the source is included with the source, not in the manual. Download a source package from the panda website, then look in the directory "doc," where you will find two files: INSTALL-MK and INSTALL-PP. These contain the instructions on the two different build-systems, "makepanda" and "ppremake".


Panda3D Manual: Troubleshooting ppremake on Windows

This page describes potential build issues that may arise when building Panda3D using the PPremake build system on the Windows platform, and possible solutions.

ppremake doesnt run (access violation 0xc0000005)

The April/May 2005 version of Cygwin may have had an issue with getopt.

Simply open config.h in the ppremake directory, replace "#define HAVE_GETOPT 1" with "#undef HAVE_GETOPT", then rerun "make" and "make install".

invalid link option /DEBUG, when running make

This error (or something similar) is caused by the make running Cygwin's link rather than your msvc link.exe. Simply rename /usr/bin/link to /usr/bin/_link, and rerun make.

Building using Microsoft Visual C++ Toolkit 2003

Basically follow the official build instructions, using Cygwin as configuration platform and Microsoft Visual C++ Toolkit 2003 as compiler (you have to pretty much, this is very close to the primary configuration they support).

Pre-requisites:

  • Microsoft Visual C++ Toolkit 2003
  • msvcrt.lib and msvcprt.lib (see See Notes on Microsoft Visual CPP Toolkit 2003)
  • DirectX (even though we're not using it, you need the headers
  • Cygwin
  • Panda source-code

The Panda source-code zip contains Python.

  • First, you'll need to set up your variables for MSVC, within Cygwin. What you can do is to personalize the following file for yourself, and save it to /usr/local/panda/bin/setvars:
PATH=$PATH:"/cygdrive/f/Program Files/Microsoft Visual C++ Toolkit 2003/bin"
PATH=$PATH:/usr/local/panda/bin
PATH=$PATH:/usr/local/panda/lib
PATH=$PATH:/cygdrive/f/dev/panda3d-1.0.2-cyg/thirdparty/win-python

export CL=" /I\"F:\Program Files\Microsoft Visual C++ Toolkit 2003\include\" "
export CL="$CL /I\"F:\Program Files\Microsoft Visual C++ Toolkit 2003\include\" "
export CL="$CL /I\"F:\program Files\Microsoft SDK\include\" "
export CL="$CL /I\"F:\Program Files\Microsoft DirectX 9.0 SDK\include\" "
export CL="$CL /I\"F:\dev\panda3d-1.0.2-cyg\thirdparty\win-python\include\" "

export LINK=" /LIBPATH:\"F:\Program Files\Microsoft Visual C++ Toolkit 2003\lib\" "
export LINK="$LINK /LIBPATH:\"F:\Program Files\Microsoft Visual C++ Toolkit 2003\lib\" "
export LINK="$LINK /LIBPATH:\"F:\Program Files\Microsoft SDK\lib\" "
export LINK="$LINK /LIBPATH:\"F:\Program Files\Microsoft DirectX 9.0 SDK\lib\" "
export LINK="$LINK /LIBPATH:\"F:\dev\panda3d-1.0.2-cyg\thirdparty\win-python\libs\" "

export PANDA_ROOT='F:\Cygwin'

export PYTHONPATH="f:\dev\panda3d-1.0.2-cyg\thirdparty\win-python;f:\cygwin\usr\local\panda\lib"
  • then run this file from Cygwin
  . /usr/local/panda/bin/setvars

(note that there's a . at the start, and a space between the . and the rest of the command)

  • also, you will need to rename /usr/bin/link to /usr/bin/_link , in order that cygwin finds msvc link, and not the gcc link
  • Open cgywin/usr/local/panda/Config.pp (create the file, if you didnt already), and add the following lines:
  #define HAVE_DX
  • The HAVE_DX line means that you will not use DirectX (sic), which is a good thing, unless you happen to have an old DirectX 8 SDK lieing around (current version at microsoft.com is 9)
  • Note that linking dynamically is the default; and this the configuration which builds easiest
  • The Visual C++ Toolkit doesnt contain lib, only link, which can do the same thing, so open, in the panda source directory, dtool/pptempl/compilerSettings.pp, and replace
  #define LIBBER lib

with

  #define LIBBER link /lib
  • Now, you're ready to run ppremake, make and so on in dtool, then panda, as per the instructions.

(These notes taken from: http://manageddreams.com/osmpwiki/index.php?title=Panda3D).


Panda3D Manual: Troubleshooting ppremake on Linux

This page describes potential build issues that may arise when building Panda3D using the PPremake build system on the Linux platform, and possible solutions.

Undeclared 'vector_string'

If you compile Panda3D 1.5.3 using ppremake, you might get this error:

filename.h:136: error: ‘vector_string’ has not been declared

This error occurs because some header files in the "ppremake" directory were accidentally truncated in the 1.5.3 source. This bug has been fixed in 1.5.4. If you still want to compile on 1.5.3, you will need to grab a newer version of the "ppremake" directory from CVS, for example using this command:

rm -rf ppremake
cvs -d :pserver:anonymous@panda3d.cvs.sourceforge.net:/cvsroot/panda3d checkout -A -r panda3d_1_5_4 ppremake

Make sure you have cd'ed to the root of the source tree before executing those commands.


Panda3D Manual: Troubleshooting makepanda on Windows

This page describes potential build issues that may arise when building Panda3D using the Makepanda build system on the Windows platform, and possible solutions.

(no known problems at this time)


Panda3D Manual: Troubleshooting makepanda on Linux

This page describes potential build issues that may arise when building Panda3D using the Makepanda build system on the Linux platform, and possible solutions.

Undefined reference to `__isoc99_sscanf'

If you're building Panda3D on 64-bits linux, and run into this error:

g++ -o built/bin/apply_patch -Lbuilt/lib -L/usr/X11R6/lib built/tmp/apply_patch_apply_patch.o
-lpanda -lpandaexpress -lp3dtool -lp3dtoolconfig -lp3pystub -lz -lssl -lpthread -ldl
built/lib/libpanda.so: undefined reference to `__isoc99_sscanf'
collect2: ld returned 1 exit status

This error is related to the ffmpeg thirdparty package, which has been compiled with a very recent version of glibc. You either need to upgrade your system's glibc version (recommended) or you can compile with --no-ffmpeg .


Panda3D Manual: Tutorial: Compiling the Panda 3D Source on Windows
<<prev top next>>

This is a short tutorial of how to get Panda 3D to compile under Visual C++ 2005 Express edition

Step 1: Installing Visual C++ 2005, the Microsoft platform SDK and DirectX SDK:

First download and install visual C++ from:

http://www.microsoft.com/express/2005/

Next download and install the windows platform SDK from:

http://www.microsoft.com/downloads/details.aspx?familyid=0baf2b35-c656-4969-ace8-e4c0c0716adb&displaylang=en

Next download and install the Directx SDK from (yes I know it's huge :'( ):

http://www.microsoft.com/downloads/details.aspx?FamilyID=C72D9F1E-53F3-4747-8490-6801D8E8B4EF&displaylang=en

Follow this tutorial to set-up Visual C++ Express 2005 with the platform SDK.

http://www.microsoft.com/express/2005/platformsdk/default.aspx

If all goes well and you can get your test program to compile in the above tutorial you are all set to use makepanda.bat or .py to compile panda.

Step 2: Download the source

Download the latest version of the Panda 3D source available at:

http://panda3d.net/download.php

Extract to an easily accessible place on your hdd (example: c:\panda_source)

Step 3: Open a command prompt

Easiest way is: START->RUN and type cmd, hit ok.

You will now have a command prompt open.

You will most likely be in the C:\Documents and Settings\<your user name> folder.

Type: cd C:\panda_source

This command cd "change directory" changes the directory to C:\panda_source

Type: makepanda\makepanda.bat

This brings up all the command options for makepanda.bat

For more info on dos commands: http://www.google.com.au/search?q=dos+prompt+for+beginners

Step 4: "The simplest way to compile panda is to just type..."

As the help text says:

Type: makepanda\makepanda.bat --everything

This process will take an hour or so, so it is best to go do something else because your comp will be using most of its resources on compiling.

For more information on using the makepanda tool to compile, please read the INSTALL-MK document, which is also available within the doc directory of your source tree.

Step 4: Make an installer

Type: makepanda\makepanda.bat --everything --installer

This should take much less time because if you notice there will be a "built" folder in your C:\panda_source this was created in the previous step

Conclusion

And that's it. Depending on your needs you can configure Panda 3D any way you wish.


Panda3D Manual: List of Panda Executables

This is meant to be a list of the executables in the /bin/ folder of Panda 3D. You can get a detailed synopsis of what the executables do by running them with -h as the arguement.

Filename | Description
bam-info.exe | Scans one or more .bam files and outputs their contents. See executable for more information.
bam2egg.exe | Converts models in the .bam format to the .egg format. For more information see Converting Egg to Bam.
bin2c.exe | Reads a file from disk and produces a table that when compiled by C compiler reproduces the same data. See executable for more informatiion.
cgc.exe | A compiler for NVidia's Cg language. For more information see Using Cg Shaders.
check_md5.exe | Outputs the MD5 hash for one or more files. See Executable for more information.
dxf-points.exe | Reads in an AutoCad .dxf file and prints out the points contained in it. See executable for more information.
dxf2egg.exe | Converts models from the Autocad format to the .egg format. For more information see Converting from other Formats
egg-crop.exe | Strips an .egg file of all parts that fall outside the given bounding volume. See executable for more information.
egg-make-tube.exe | Creates an .egg file representing a "tube" model. See executable for more information.
egg-mkfont.exe | Makes a .egg file from a FreeType (.ttf) font. For more information see Text Fonts.
egg-optchar.exe | Optimizes models by removing unused joints. Also allow you to label parts of the model. For more information see Manipulating a Piece of a Model.
egg-palettize.exe | Tries to combine textures in an egg file. Also performs some texture manipulation. See executable for more information.
egg-qtess.exe | Performs a tesselation on all of the NURBS surfaces in a .egg file. See executable for more information.
egg-texture-cards.exe | Creates an egg that automatically rotates through multiple textures. For more information see Automatic Texture Animation.
egg-topstrip.exe | Unnaplies the animations from one of the top joints in a model. Useful for character models that stack on top of each other. See executable for more information
egg-trans.exe | Produces out essentially the same .egg file. Useful for applying rotational and positional tronsformations. See executable for more information.
egg2bam.exe | Converts files in the .egg format to the .bam format. For more information see Converting Egg to Bam.
egg2c.exe | Reads a .egg file and produce C/C++ code that will almost compile. See executable for more information.
egg2dxf.exe | Converts files in the .egg format to the AutoCad format.For more information see Converting from other Formats.
egg2flt.exe | Converts files in the .egg format to the Open Flight format.For more information see Converting from other Formats.
egg2x.exe | Converts files in the .egg format to the DirectX format. Especially useful because it holds bone, joint and animation data. For more information see Converting from other Formats.
flt-info.exe | Reads an OpenFlight file and prints out information about its contents. See executable for more information.
flt-trans.exe | Produces esentially the same .flt file. Useful for postional and rotational transformations. See executable for more information.
flt2egg.exe | Converts files in the OpenFlight format to the .egg format. For more information see Converting from other Formats.
genpycode.exe | Genreates the Python wrappings necessary to inteface with the C++ libraries that are the backbone of Panda. Also generates the API Reference Manual in <Panda Directory>/pandac/docs. For more information see API Reference Materials
httpbackup.exe | Used to retreive a document from an HTTP server and save it on disk. See executable for more information.
image-info.exe | Reports the sizes of one or more images. See executable for more information.
image-resize.exe | Resizes an image. See executable for more information.
image-trans.exe | Produces an identical picture. Can also be used for file format conversion. See executable for more information.
indexify.exe | Takes image directories and creates HTML pages with thumbnails of these pictures. See executable for more information.
interrogate.exe | Parses C++ code and creates wrappers so that it can be called in a Scrtipting language. For more information see Interrogate
lwo-scan.exe | Prints the contents of a .lwo file. See executable for more information.
lwo2egg.exe | Converts files in the LightWave 3D format to the .egg format. For more information see Converting from other Formats.
make-prc-key.exe | Generates one or more new key to be used for signing a prc file. See executable for more information.
maya2egg5.exe | Converts files in the Maya 5 format to the .egg format. For more information see Converting from Maya.
maya2egg6.exe | Converts files in the Maya 6 format to the .egg format. For more information see Converting from Maya.
maya2egg65.exe | Converts files in the Maya 6.5 format to the .egg format. For more information see Converting from Maya.
multify.exe | Stores and extracts files from a Panda MultiFile. Can also extract file in program using the VitrualFileSystem (see API for usage). For more information see executable.
pdecrypt.exe | Decompress a file compressed by pencrypt. See executable for more inforamtion.
pencrypt.exe | Runs an encryption algorithm on the specified file. The original file can only be recovered by using pdecrypt. See executable for more information.
python.exe | Used to start Panda 3D. For more information see Starting Panda3D
pstats.exe | Panda's built in performance tool. For more information see Measuring Performance with PStats
pview.exe | Used to view models in the .egg or .bam format without having to create a Panda program. For more information see Previewing 3D Models in Pview.
stitch-command.exe | Checks the synatx and preproecees and sticth command file. See executable for more information.
vrml2egg.exe | Converts files in the Virtual Reality Modeling Language format to the .egg format. For more information see Converting from other Formats.
x2egg.exe | Converts files in the Direct X format to the .egg format. Especially useful because it holds bone, joint and animation data. For more information see Converting from other Formats.


Panda3D Manual: Getting Started on OSX

Congratulations! You've installed Panda3D on your machine. Now, what next?

First of all, you should prepare by learning Python. You can also use C++ with Panda3D, but since most of its users use Python, and it is a very easy language to master, Python is the most recommended choice.

Then, you can dive into the Manual and try the Hello World samples. The manual is a great resource for learning Panda3D and getting in-depth information about various subjects. However, for more concrete examples, you can dive into the Sample Programs, which ship with the Panda3D build.

Running the Sample Programs

Panda3D comes with a handful of Sample Programs, which are stored in /Applications/Panda3D/1.6.0/samples. After opening a terminal window navigate to the directory of the sample program you want to see.

Then, you can run the program by using the 'python' command:

python Tut-Asteroids.py

Troubleshooting Common Issues

What to do if you see the Error Message:
dyld: Library not loaded: @executable_path/../Library/Frameworks/Cg.framework/Cg
Referenced from: /Applications/Panda3D/1.6.0/lib/libpanda.dylib
Reason: image not found
Trace/BPT trap

This means you need to download and install the NVIDIA Cg Toolkit before you will be able to use Panda3D.

What to do if you see the Error Message:
Fatal Python error: Interpreter not initialized (version mismatch?)
Abort trap

You are running a version of Python that is not compatible with the version Panda3D has been compiled with. However, OSX always ships with a system version of Python that should be compatible. The fact that it doesn't work simply indicating that the wrong copy of python is ran.

You need to make sure you are running Apple's version of Python. Fortunately, Panda3D ships with a file called 'ppython' that is a mere symlink to /usr/bin/python, where Apple's copy of Python resides. So, instead of 'python', you should call 'ppython'.

What to do if you see the Error Message:
Warning: DirectNotify: category 'Interval' already exists

This error is of no consequence. Ignore it.

What to do if you see the Error Message:
display(error): The application requested harware acceleration, but your OpenGL
display(error): driver, GDI Generic, only supports software rendering.
display(error): You need to install a hardware-accelerated OpenGL driver, or,
display(error): if you actually *want* to use a software renderer, then
display(error): alter the hardware/software configuration in your Config.prc file.
display(error): Window wouldn't open; abandoning window.

This error is fairly self-explanatory: it means your video drivers are inadequate. Obtain updated drivers by running Software Update or, if you have installed a 3rd party video card, installing new drivers from the video card vendor.

Alternatively, you can use Panda3D in software rendering mode, You will not be able to use fancy shaders and it will be much slower than in hardware mode. However, if you still want to use it, you need to edit your Config.prc file (which can be found in the "etc" dir of your Panda3D installation) and find this line:

load-display pandagl

Replace that line with this instead:

load-display tinydisplay


Panda3D Manual: More Panda Resources

Panda Specific Resources

The BVW Panda Tutorials. This site has the tutorials that are used in Carnegie Mellon's Building Virtual Worlds class.

Alice Gallery. This site holds many of the models created for use with Alice but exported into the .egg format. Be advised though, some of these models may not work properly.

Python Libraries

Pygame. GNU LGPL. Pygame is a set of Python modules designed for writing games. It includes Python bindings for SDL. Recommended for joystick support. Sound support is a free alternative to FMOD, though not a as capable (no 3D stereo sound for instance)

Psyco. MIT-style license. Python JIT optimizer. Can often make Python code run faster than code written in C (on Intel compatible chips.) Panda3D is mostly written in C++, but usually programed in Python. If Python function calls become a bottleneck, try using Psyco.

Weave. BSD-style license. The weave package allows the inclusion of C/C++ within Python code and is useful in accelerating Python code. Weave is a subpackage of scipy.

Twisted MIT-style license. An event-driven networking framework.

Useful Tools

Blender. 3D modeling and animation. Extensible in Python. Very fast user interface. The chicken egg exporter is available.

The GIMP. A very capable free software raster image editor. Useful for converting image formats, creating and editing textures.

SWIG. A software development tool that connects programs written in C and C++ with a variety of high-level programming languages including Python.


Panda3D Manual: Project Hosting

If you want some webspace to host your Panda3D project, you can do so at Panda3DProjects. Panda3DProjects is a website set up by the community and for the community. It provides free hosting for any Panda3D user which wants some room to put their Panda3D-related stuff. It also provides a wiki for each user where the user can create pages at and show their progress.

You can visit Panda3DProjects at one of these links:

  • www.p3dp.com
  • www.panda3dprojects.com

There is also an IRC channel for Panda3DProjects; channel #panda3dprojects at irc.freenode.net.

Also, there is a TeamSpeak server available, the server name is p3dp.com and the port settings are the default.


Panda3D Manual: The IRC Channel

There is an (unofficial) IRC channel for help with Panda3D:

Network | FreeNode
Server | irc.freenode.net
Channel | #panda3d

There is also a bot available at the channel, nicknamed bertha. You can ask her by using an exclamation point followed by a keyword or a command. Like:

!py2exe

She also offers some useful commands to display an API or Manual page:

!apiref NodePath
!manual The IRC Channel
!source panda/src/grutil/heightfieldTesselator.cxx


Panda3D Manual: FAQ

Contents

  • 1 Q: What is the .pz files I am seeing the samples?
    • 1.1 A: Those are zipped files that are zipped with panda pzip/punzip command line tools tools that panda3d knows how to read
  • 2 Q: What is the .pyc files i that are created every where?
    • 2.1 A: .pyc and .pyo are python compiled and python optimized compiled files
  • 3 Q: I have a bunch of Maya Animations of one model in different mb files. I used maya2egg to port them into panda, but only one of the animations work.
    • 3.1 A: The key is to use the -cn <character's name> flag in maya2egg for every file. This ensures that the files work together.
  • 4 Q: I'm using the lookAt function on a nodepath to point it at another object. It works fine until I point upwards, and then it starts to spin my object around randomly
    • 4.1 A: lookAt works as long as you aren't telling it to look in the direction of its up vector. Luckily, you can specify the up vector as the second argument.
  • 5 Q: I'm building a 3d game, and I have a huge world. When my world starts up, the program hangs for a few seconds the first time I look around. Is there any way to avoid this?
    • 5.1 A: It can take a while to prepare objects to be rendered.
  • 6 Q: Is there a way to hide the mouse pointer so that it doesn't show up on my screen?
    • 6.1 A: You can change to properties of the panda window so that it doesn't show the cursor:
  • 7 Q: If a model has an animation, is that animation necessarily represented by an additional .egg file?
    • 7.1 A: No. An .egg file can either be just geometry, just an animation, or a combination of the two.
  • 8 Q: I have a model with an animation. When i try to play the animation I get this error:
    • 8.1 A: This often happens when you are trying to load animations onto a model that wasn't exported to have animations.
  • 9 Q: I called setTexture('tex.jpg') and it didn't change or send an error.
    • 9.1 A: To override an existing texture, you need to specify a priority: setTexture('tex.jpg', 1).
  • 10 Q: I get this error:
    • 10.1 A: Sequences and Parallels are a way to combine intervals. You can't put anything inside them that isn't an interval. If you want this code to work you want something like:
  • 11 Q: Does panda use degrees or radians?
    • 11.1 A: Degrees. But see also the deg2Rad() and rad2Deg() functions.
  • 12 Q: Why do all my flat objects look weird when lit?
    • 12.1 A: Flats don't often have a lot of vertices.
  • 13 Q: To smooth my animations, I used "interpolate-frames 1" option but it doesn't work somehow. Why?
    • 13.1 A: Interpolate-frames flag gets set in the PartBundle at the time it is first created, and then baked into the bam cache.
  • 14 Q: I'm trying to redirect the output of some commands like "myNode.ls()" to a file but the usual method "python >>file, myNode.ls()" doesn't work. What's the alternative?
    • 14.1 A: You can try:
  • 15 Q: How do we create a node from a string containing .egg source?
    • 15.1 A: Use the EggData class:
  • 16 Q: How can we know which letter was bellow the pointer when we click on a TextNode?
    • 16.1 A: Use the TextAssembler class like this:

Q: What is the .pz files I am seeing the samples?

A: Those are zipped files that are zipped with panda pzip/punzip command line tools tools that panda3d knows how to read

Usage:

  pzip file [file2 file3 ...]
  pzip -o dest_file file

Usage:

  punzip file.pz [file2.pz file3.pz ...]
  punzip -o dest_file file.pz

Q: What is the .pyc files i that are created every where?

A: .pyc and .pyo are python compiled and python optimized compiled files

As an important speed-up of the start-up time for short programs that use a lot of standard modules, if a file called "spam.pyc" exists in the directory where "spam.py" is found, this is assumed to contain an already-``byte-compiled version of the module spam. The modification time of the version of "spam.py" used to create "spam.pyc" is recorded in "spam.pyc", and the file is ignored if these don't match. http://www.python.org/doc/1.5.1p1/tut/node43.html

Q: I have a bunch of Maya Animations of one model in different mb files. I used maya2egg to port them into panda, but only one of the animations work.

A: The key is to use the -cn <character's name> flag in maya2egg for every file. This ensures that the files work together.

Let's say you are making an animated dog. You have the following animations:

   dog-walk.mb
   dog-sit.mb
   dog-run.mb

To convert these into panda, you would call

   maya2egg6 dog-walk.mb -a model -cn dog -o dog-model.egg

Note, we can grab the model from any of the animations, as long as they are all using the exact same rig:

   maya2egg6 dog-walk.mb -a chan -cn dog -o dog-walk.egg
   maya2egg6 dog-sit.mb -a chan -cn dog -o dog-sit.egg
   maya2egg6 dog-run.mb -a chan -cn dog -o dog-run.egg

Q: I'm using the lookAt function on a nodepath to point it at another object. It works fine until I point upwards, and then it starts to spin my object around randomly

A: lookAt works as long as you aren't telling it to look in the direction of its up vector. Luckily, you can specify the up vector as the second argument.

   lookAt(object,Vec3(0,0,1))

Q: I'm building a 3d game, and I have a huge world. When my world starts up, the program hangs for a few seconds the first time I look around. Is there any way to avoid this?

A: It can take a while to prepare objects to be rendered.

Ideally, you don't want this to happen the first time you see an object. You can offload the wait time to the beginning by calling:

   # self.myWorld is a nodepath that contains a ton of objects
   self.myWorld.prepareScene(base.win.getGsg())

This will walk through the scene graph, starting at self.myWorld, and prepare each object for rendering.

Q: Is there a way to hide the mouse pointer so that it doesn't show up on my screen?

A: You can change to properties of the panda window so that it doesn't show the cursor:

   props = WindowProperties()
   props.setCursorHidden(True)
   base.win.requestProperties(props)

Q: If a model has an animation, is that animation necessarily represented by an additional .egg file?

A: No. An .egg file can either be just geometry, just an animation, or a combination of the two.

It's often easiest, however, to create a separate egg for every animation and an egg that contains just the model/skeleton information.

Q: I have a model with an animation. When i try to play the animation I get this error:

   KeyError: lodRoot
   display: Closing wglGraphicsWindow

A: This often happens when you are trying to load animations onto a model that wasn't exported to have animations.

There are two pieces to objects that have animations; their geometry and their skeleton. The geometry is what you see when you load a model, the skeleton is what controls the geometry in an animation. If only the geometry was used to make the egg file, you will have problems when you try to play animations. Look at the manual for more details about exporting models as eggs.

Q: I called setTexture('tex.jpg') and it didn't change or send an error.

A: To override an existing texture, you need to specify a priority: setTexture('tex.jpg', 1).

The setTexture() call includes an optional priority parameter, and if the priority is less than 1 the texture will not change.

Q: I get this error:

   assert(self.validateComponents(self.ivals))
   AssertionError

It happens at this line of code:

   move = Sequence(obj.setX(5))

A: Sequences and Parallels are a way to combine intervals. You can't put anything inside them that isn't an interval. If you want this code to work you want something like:

   self.move = Sequence(Func(obj.setX, 5))

This will start the execution of the function, but not wait for it to finish.

Q: Does panda use degrees or radians?

A: Degrees. But see also the deg2Rad() and rad2Deg() functions.

   But note that the math.sin, math.cos, math.tan, etc. functions are calculated in radians.
   Don't forget to convert the values!

Q: Why do all my flat objects look weird when lit?

A: Flats don't often have a lot of vertices.

Lighting is only calculated at the vertices, and then linearly interpolated between the vertices. If your vertices are very far apart, lighting can look very strange--for instance, a point light in the center of a large polygon might not show up at all (the light is far from all four vertices, even though it's very near the polygon's center).

One solution is to create a model with a lot of polygons to pick up the lighting. It also helps to make a flat surface slightly curved to improve its appearance. Another approach might be to create an ambient light that only affects this object. See the manual for more detail about attaching lights to objects in your scene.

Q: To smooth my animations, I used "interpolate-frames 1" option but it doesn't work somehow. Why?

A: Interpolate-frames flag gets set in the PartBundle at the time it is first created, and then baked into the bam cache.

Thenceforth, later changes to the interpolate-frames variable mean nothing. If you changed interpolate-frames flag, you will also need to empty your modelcache folder.

Actually, it is not recommended to use interpolate-frames; it is a global setting. It's better to achieve the same effect via actor.setBlend(frameBlend = True), which is a per-actor setting (and doesn't get baked into the model cache).

Q: I'm trying to redirect the output of some commands like "myNode.ls()" to a file but the usual method "python >>file, myNode.ls()" doesn't work. What's the alternative?

A: You can try:

   strm = StringStream()
   render.ls(strm)
   open('out.txt', 'w').write(strm.getData())

In alternative:

   strm = StringStream()
   cvMgr.write(strm)
   open('out.txt', 'w').write(strm.getData())

If you don't want to use a StringStream you can do this:

   strm = MultiplexStream()
   strm.addFile(Filename('out.txt'))
   render.ls(strm)

There is also a way to specify the file to write to in the config file.

   notify-output out.txt

Q: How do we create a node from a string containing .egg source?

A: Use the EggData class:

   egg = EggData()
   egg.read(StringStream(eggText))
   model = NodePath(loadEggData(egg))

Q: How can we know which letter was bellow the pointer when we click on a TextNode?

A: Use the TextAssembler class like this:

   tn = TextNode('tn')
   tn.setText('abcdef\nghi')
   ta = TextAssembler(tn)
   ta.setWtext(tn.getWtext())
   for ri in range(ta.getNumRows()):
       for ci in range(ta.getNumCols(ri)):
           print "ri = %s, ci = %s, char = %s, pos = %s, %s" % (ri, ci, chr(ta.getCharacter(ri, ci)), ta.getXpos(ri, ci), ta.getYpos(ri, ci))


Panda3D Manual: Class Reference
  • direct:
    • actor:
      • Actor
      • DistributedActor
    • cluster:
      • ClusterClient:
        • ClusterClient
        • ClusterClientSync
        • ClusterConfigItem
        • DisplayConnection
        • DummyClusterClient
      • ClusterMsgs:
        • ClusterMsgHandler
      • ClusterServer
    • controls:
      • BattleWalker
      • ControlManager
      • DevWalker
      • GhostWalker
      • GravityWalker
      • InputState:
        • InputState
        • InputStateForceToken
        • InputStateToken
        • InputStateTokenGroup
        • InputStateWatchToken
      • NonPhysicsWalker
      • ObserverWalker
      • PhysicsWalker
      • ShipPilot
      • ShipPilot2
      • SwimWalker
    • dcparser:
      • DCArrayParameter
      • DCAtomicField
      • dcClass:
        • DCClass
        • DCParameter
      • dcClassParameter:
        • DCClass
        • DCClassParameter
      • dcDeclaration:
        • DCClass
        • DCDeclaration
        • DCSwitch
      • dcField:
        • DCAtomicField
        • DCClass
        • DCField
        • DCMolecularField
        • DCPacker
        • DCParameter
        • DCSwitch
      • dcFile:
        • DCClass
        • DCDeclaration
        • DCField
        • DCFile
        • DCKeyword
        • DCSwitch
        • DCTypedef
      • dcKeyword:
        • DCKeyword
        • DCParameter
      • dcKeywordList:
        • DCKeyword
        • DCKeywordList
      • dcMolecularField:
        • DCAtomicField
        • DCMolecularField
        • DCParameter
      • DCPackData
      • dcPacker:
        • DCClass
        • DCPacker
        • DCSwitchParameter
      • dcPackerCatalog:
        • DCPacker
        • DCPackerInterface
        • DCSwitchParameter
      • dcPackerInterface:
        • DCArrayParameter
        • DCAtomicField
        • DCClassParameter
        • DCField
        • DCFile
        • DCMolecularField
        • DCPackData
        • DCPackerInterface
        • DCSimpleParameter
        • DCSwitchParameter
      • dcParameter:
        • DCArrayParameter
        • DCClassParameter
        • DCParameter
        • DCSimpleParameter
        • DCTypedef
      • dcParserDefs:
        • DCAtomicField
        • DCClass
        • DCField
        • DCFile
        • DCKeyword
        • DCPacker
        • DCParameter
        • DCSwitch
      • DCSimpleParameter
      • dcSwitch:
        • DCField
        • DCParameter
        • DCSwitch
      • dcSwitchParameter:
        • DCSwitch
        • DCSwitchParameter
      • dcTypedef:
        • DCParameter
        • DCTypedef
    • deadrec:
      • SmoothMover
    • directdevices:
      • DirectDeviceManager:
        • DirectAnalogs
        • DirectButtons
        • DirectDeviceManager
        • DirectDials
        • DirectTimecodeReader
        • DirectTracker
      • DirectFastrak
      • DirectJoybox
      • DirectRadamec
    • directnotify:
      • DirectNotify
      • Logger
      • Notifier
      • RotatingLog
    • directtools:
      • DirectCameraControl
      • DirectGeometry:
        • LineNodePath
      • DirectGrid
      • DirectLights:
        • DirectLight
        • DirectLights
      • DirectManipulation:
        • DirectManipulationControl
        • ObjectHandles
      • DirectSelection:
        • DirectBoundingBox
        • DirectNodePath
        • SelectedNodePaths
        • SelectionQueue
        • SelectionRay
        • SelectionSegment
        • SelectionSphere
      • DirectSession:
        • DirectSession
        • DisplayRegionContext
        • DisplayRegionList
    • directutil:
      • DeltaProfiler
      • DistributedLargeBlobSender
      • DistributedLargeBlobSenderAI
      • Mopath
      • WeightedChoice
    • distributed:
      • AsyncRequest
      • CRCache
      • CartesianGridBase
      • ClientRepository
      • ClientRepositoryBase
      • ClockDelta
      • ConnectionRepository
      • DelayDelete
      • DistributedCartesianGrid
      • DistributedCartesianGridAI
      • DistributedNode
      • DistributedNodeAI
      • DistributedNodeUD
      • DistributedObject
      • DistributedObjectAI
      • DistributedObjectBase
      • DistributedObjectGlobal
      • DistributedObjectGlobalAI
      • DistributedObjectGlobalUD
      • DistributedObjectOV
      • DistributedObjectUD
      • DistributedSmoothNode
      • DistributedSmoothNodeAI
      • DistributedSmoothNodeBase
      • DoCollectionManager
      • DoHierarchy
      • DoInterestManager:
        • DoInterestManager
        • InterestHandle
        • InterestState
      • GridParent
      • InterestWatcher
      • NetMessenger
      • OldClientRepository
      • ParentMgr
      • PyDatagram
      • PyDatagramIterator
      • RelatedObjectMgr
      • SampleObject
      • ServerRepository
      • cConnectionRepository:
        • CConnectionRepository
        • HTTPChannel
        • SocketStream
        • URLSpec
      • cDistributedSmoothNodeBase:
        • CConnectionRepository
        • CDistributedSmoothNodeBase
        • DCClass
    • fsm:
      • ClassicFSM
      • FSM:
        • AlreadyInTransition
        • FSM
        • FSMException
        • RequestDenied
      • FourState
      • FourStateAI
      • SampleFSM:
        • ClassicStyle
        • NewStyle
        • ToonEyes
      • State
      • StateData
    • gui:
      • DirectButton
      • DirectCheckBox
      • DirectCheckButton
      • DirectDialog:
        • DirectDialog
        • OkCancelDialog
        • OkDialog
        • RetryCancelDialog
        • YesNoCancelDialog
        • YesNoDialog
      • DirectEntry
      • DirectFrame
      • DirectGuiBase:
        • DirectGuiBase
        • DirectGuiWidget
      • DirectLabel
      • DirectOptionMenu
      • DirectScrollBar
      • DirectScrolledFrame
      • DirectScrolledList:
        • DirectScrolledList
        • DirectScrolledListItem
      • DirectSlider
      • DirectWaitBar
      • OnscreenGeom
      • OnscreenImage
      • OnscreenText
    • http:
      • http_bufferedreader:
        • [1]
      • ringbuffer_slide:
        • [2]
      • strtargetbuffer:
        • [3]
      • webAIInspector:
        • ClassInspector
        • CodeInspector
        • ComplexInspector
        • DictionaryInspector
        • FunctionInspector
        • Inspector
        • InstanceInspector
        • InstanceMethodInspector
        • ModuleInspector
        • SequenceInspector
        • SliceInspector
    • interval:
      • ActorInterval:
        • ActorInterval
        • LerpAnimInterval
      • FunctionInterval:
        • AcceptInterval
        • EventInterval
        • Func
        • FunctionInterval
        • HprInterval
        • HprScaleInterval
        • IgnoreInterval
        • ParentInterval
        • PosHprInterval
        • PosHprScaleInterval
        • PosInterval
        • ScaleInterval
        • Wait
        • WrtParentInterval
      • IndirectInterval
      • Interval
      • IntervalManager
      • LerpInterval:
        • LerpColorInterval
        • LerpColorScaleInterval
        • LerpFunc
        • LerpFunctionInterval
        • LerpHprInterval
        • LerpHprScaleInterval
        • LerpNodePathInterval
        • LerpPosHprInterval
        • LerpPosHprScaleInterval
        • LerpPosHprScaleShearInterval
        • LerpPosInterval
        • LerpPosQuatInterval
        • LerpPosQuatScaleInterval
        • LerpPosQuatScaleShearInterval
        • LerpQuatInterval
        • LerpQuatScaleInterval
        • LerpScaleInterval
        • LerpShearInterval
        • LerpTexOffsetInterval
        • LerpTexRotateInterval
        • LerpTexScaleInterval
      • MetaInterval:
        • MetaInterval
        • Parallel
        • ParallelEndTogether
        • Sequence
        • Track
      • MopathInterval
      • ParticleInterval
      • ProjectileInterval
      • SoundInterval
      • TestInterval
      • cInterval:
        • CInterval
        • CIntervalManager
      • cIntervalManager:
        • CIntervalManager
        • EventQueue
      • CLerpAnimEffectInterval
      • CLerpInterval
      • CLerpNodePathInterval
      • CMetaInterval
      • HideInterval
      • ShowInterval
      • WaitInterval
    • leveleditor:
      • LevelEditor:
        • DNABaselineStyle
        • DNAFlatBuildingStyle
        • DNAWallStyle
        • LevelAttribute
        • LevelEditor
        • LevelEditorPanel
        • LevelStyleManager
        • OldLevelEditor
        • VisGroupsEditor
      • PieMenu:
        • PieMenu
        • TextPieMenu
    • MotionTrail
    • particles:
      • ForceGroup
      • GlobalForceGroup
      • ParticleEffect
      • ParticleFloorTest
      • Particles
      • SpriteParticleRendererExt
    • physics:
      • FallTest
      • RotationTest
    • pyinst:
      • Builder:
        • ArchiveTarget
        • CollectTarget
        • ExeTarget
        • FullExeTarget
        • InstallTarget
        • PYZTarget
        • Target
      • archive:
        • Archive
        • ZlibArchive
      • archive_rt:
        • Archive
        • ZlibArchive
      • carchive:
        • CArchive
        • CTOC
      • carchive_rt:
        • CArchive
        • CTOC
      • icon:
        • GRPICONDIR
        • GRPICONDIRENTRY
        • ICONDIRENTRY
        • ICONDIRHEADER
        • IconFile
        • Structure
      • imputil:
        • DirectoryImporter
        • FuncImporter
        • Importer
        • PackageArchive
        • PackageArchiveImporter
      • lTOC
      • mkarchive:
        • MkImporter
      • modulefinder:
        • Module
        • ModuleFinder
      • resource:
        • archiveresource
        • binaryresource
        • dataresource
        • dirresource
        • moduleresource
        • pkgresource
        • pythonresource
        • resource
        • scriptresource
        • treeresource
        • zlibresource
      • tocfilter:
        • DirFilter
        • ExtFilter
        • FileFilter
        • ModFilter
        • PatternFilter
        • PkgFilter
        • StdLibFilter
        • TypeFilter
        • _ExtFilter
        • _Filter
        • _NameFilter
        • _PathFilter
        • _PatternFilter
        • _TypeFilter
    • showbase:
      • Audio3DManager
      • BufferViewer
      • BulletinBoard
      • BulletinBoardWatcher
      • ContainerLeakDetector:
        • CheckContainers
        • ContainerLeakDetector
        • FindContainers
        • Indirection
        • NoDictKey
        • ObjectRef
        • PruneObjectRefs
      • ContainerReport
      • CountedResource
      • DirectObject
      • DistancePhasedNode:
        • BufferedDistancePhasedNode
        • DistancePhasedNode
      • EventGroup
      • EventManager
      • ExcelHandler
      • Factory
      • GarbageReport:
        • FakeObject
        • GarbageLogger
        • GarbageReport
      • GarbageReportScheduler
      • Job
      • JobManager
      • LeakDetectors:
        • CppMemoryUsage
        • GarbageLeakDetector
        • LeakDetector
        • SceneGraphLeakDetector
      • Loader
      • Messenger
      • MessengerLeakDetector:
        • MessengerLeakDetector
        • MessengerLeakObject
      • ObjectPool:
        • Diff
        • ObjectPool
      • ObjectReport:
        • ExclusiveObjectPool
        • ObjectReport
      • OnScreenDebug
      • PhasedObject
      • Pool
      • PythonUtil:
        • ArgumentEater
        • Camera
        • ClassTree
        • DelayedCall
        • DelayedFunctor
        • Enum
        • EnumIter
        • FrameDelayedCall
        • Functor
        • GoldenRectangle
        • HotkeyBreaker
        • POD
        • ParamObj
        • Queue
        • RefCounter
        • ScratchPad
        • SerialNumGen
        • Signature
        • Singleton
        • SingletonError
        • Stack
        • StackTrace
        • SubframeCall
        • Sync
      • RandomNumGen
      • SfxPlayer
      • ShadowDemo:
        • ShadowCaster
      • ShadowPlacer
      • ShowBase:
        • ShowBase
        • WindowControls
      • TaskThreaded:
        • TaskThread
        • TaskThreaded
      • ThreeUpShow
      • Transitions
      • pandaSqueezeTool:
        • Loader
        • Squeezer
      • showBase:
        • Camera
        • CollisionTraverser
        • GraphicsEngine
    • showutil:
      • Rope
    • task:
      • Task:
        • Task
        • TaskManager
        • TaskPriorityList
      • Timer
    • tkpanels:
      • AnimPanel:
        • ActorControl
        • AnimPanel
      • DirectSessionPanel
      • FSMInspector:
        • FSMInspector
        • StateInspector
      • Inspector:
        • ClassInspector
        • CodeInspector
        • ComplexInspector
        • DictionaryInspector
        • FunctionInspector
        • Inspector
        • InspectorWindow
        • InstanceInspector
        • InstanceMethodInspector
        • ModuleInspector
        • SequenceInspector
        • SliceInspector
      • MopathRecorder
      • NotifyPanel
      • ParticlePanel
      • Placer
      • TaskManagerPanel:
        • TaskManagerPanel
        • TaskManagerWidget
    • tkwidgets:
      • AppShell:
        • AppShell
        • TestAppShell
      • Dial:
        • AngleDial
        • Dial
        • DialWidget
      • EntryScale:
        • EntryScale
        • EntryScaleGroup
      • Floater:
        • Floater
        • FloaterGroup
        • FloaterWidget
      • ProgressBar
      • SceneGraphExplorer:
        • SceneGraphExplorer
        • SceneGraphExplorerItem
      • Slider:
        • Slider
        • SliderWidget
      • Tree:
        • TreeItem
        • TreeNode
      • Valuator:
        • Valuator
        • ValuatorGroup
        • ValuatorGroupPanel
      • VectorWidgets:
        • ColorEntry
        • Vector2Entry
        • Vector3Entry
        • Vector4Entry
        • VectorEntry
      • WidgetPropertiesDialog
  • dtool:
    • dtoolbase:
      • dtoolbase_cc:
        • ReferenceCount
      • NeverFreeMemory
      • typeHandle:
        • TypeHandle
        • TypedObject
      • typeRegistry:
        • TypeHandle
        • TypeRegistry
        • TypedObject
      • TypedObject
    • dtoolutil:
      • DSearchPath
      • ExecutionEnvironment
      • filename:
        • DSearchPath
        • Filename
      • PandaSystem
    • prc:
      • ConfigFlags
      • ConfigPage
      • configPageManager:
        • ConfigPage
        • ConfigPageManager
      • ConfigVariable
      • ConfigVariableBase
      • ConfigVariableBool
      • ConfigVariableDouble
      • ConfigVariableFilename
      • ConfigVariableInt
      • ConfigVariableList
      • ConfigVariableManager
      • ConfigVariableSearchPath
      • ConfigVariableString
      • config_prc:
        • ConfigVariableBool
      • GlobPattern
      • NotifyCategory
      • pnotify:
        • Notify
        • NotifyCategory
      • StreamReader
      • StreamWriter
  • makepanda:
    • makepandacore:
      • Target
  • panda:
    • audio:
      • AudioManager
      • audioSound:
        • AudioManager
        • AudioSound
      • FilterProperties
    • chan:
      • AnimBundle
      • AnimBundleNode
      • AnimChannelBase
      • AnimChannelMatrixDynamic
      • AnimChannelMatrixXfmTable
      • animChannelScalarDynamic:
        • AnimChannelScalarDynamic
        • PandaNode
        • TransformState
      • AnimChannelScalarTable
      • animControl:
        • AnimChannelBase
        • AnimControl
        • PartBundle
      • AnimControlCollection
      • animGroup:
        • AnimBundle
        • AnimGroup
      • auto_bind:
        • PandaNode
      • MovingPartBase
      • MovingPartMatrix
      • MovingPartScalar
      • partBundle:
        • AnimBundle
        • PartBundle
        • PartBundleNode
        • TransformState
      • PartBundleNode
      • partGroup:
        • AnimChannelBase
        • AnimControl
        • AnimGroup
        • BitArray
        • PandaNode
        • PartBundle
        • PartGroup
        • PartSubset
        • TransformState
      • PartSubset
    • char:
      • character:
        • Character
        • CharacterJointBundle
      • characterJoint:
        • Character
        • CharacterJoint
        • JointVertexTransform
      • characterJointBundle:
        • Character
        • CharacterJointBundle
      • CharacterJointEffect
      • characterSlider:
        • CharacterSlider
        • CharacterVertexSlider
      • CharacterVertexSlider
      • JointVertexTransform
    • collide:
      • CollisionDSSolid
      • CollisionEntry
      • collisionFloorMesh:
        • CollisionFloorMesh
        • GeomNode
      • collisionHandler:
        • CollisionEntry
        • CollisionHandler
      • CollisionHandlerEvent
      • CollisionHandlerFloor
      • CollisionHandlerGravity
      • CollisionHandlerPhysical
      • CollisionHandlerPusher
      • CollisionHandlerQueue
      • CollisionInvSphere
      • collisionLevelStateBase:
        • CollisionNode
        • CollisionSolid
      • CollisionLine
      • CollisionNode
      • collisionParabola:
        • LensNode
      • CollisionPlane
      • collisionPolygon:
        • CollisionPolygon
        • GeomNode
      • collisionRay:
        • CollisionRay
        • LensNode
      • collisionRecorder:
        • CollisionEntry
        • CollisionRecorder
        • CollisionTraverser
      • collisionSegment:
        • CollisionSegment
        • LensNode
      • collisionSolid:
        • CollisionEntry
        • CollisionHandler
        • CollisionNode
        • CollisionSolid
        • CollisionSphere
        • GeomNode
      • CollisionSphere
      • collisionTraverser:
        • CollisionEntry
        • CollisionNode
        • CollisionRecorder
        • CollisionTraverser
        • CollisionVisualizer
        • Geom
        • NodePath
      • CollisionTube
      • CollisionVisualizer
    • cull:
      • config_cull:
        • DSearchPath
      • drawCullHandler:
        • GraphicsStateGuardianBase
    • device:
      • AnalogNode
      • ButtonNode
      • ClientBase
      • clientDevice:
        • ClientBase
      • DialNode
      • MouseAndKeyboard
      • TrackerNode
      • VirtualMouse
    • dgraph:
      • dataGraphTraverser:
        • DataGraphTraverser
        • DataNode
        • PandaNode
      • dataNode:
        • DataGraphTraverser
        • DataNode
      • dataNodeTransmit:
        • Datagram
        • DatagramIterator
    • display:
      • DisplayInformation
      • displayRegion:
        • Camera
        • CullTraverser
        • DisplayRegion
        • GraphicsOutput
        • GraphicsPipe
        • PNMImage
      • DisplaySearchParameters
      • DrawableRegion
      • FrameBufferProperties
      • GraphicsBuffer
      • graphicsDevice:
        • GraphicsDevice
        • GraphicsPipe
      • graphicsEngine:
        • DisplayRegion
        • FrameBufferProperties
        • GraphicsEngine
        • GraphicsPipe
        • Texture
      • graphicsOutput:
        • GraphicsOutput
        • PNMImage
      • graphicsPipe:
        • FrameBufferProperties
        • GraphicsBuffer
        • GraphicsOutput
        • GraphicsPipe
        • GraphicsStateGuardian
        • GraphicsWindow
        • Texture
        • WindowProperties
      • graphicsPipeSelection:
        • GraphicsPipeSelection
        • GraphicsWindow
      • graphicsStateGuardian:
        • DrawableRegion
        • GraphicsEngine
        • GraphicsStateGuardian
      • GraphicsThreadingModel
      • GraphicsWindow
      • ParasiteBuffer
      • renderBuffer:
        • GraphicsStateGuardian
      • stencilRenderStates:
        • GraphicsStateGuardian
        • StencilRenderStates
      • WindowProperties
    • distort:
      • CylindricalLens
      • FisheyeLens
      • nonlinearImager:
        • GraphicsEngine
        • GraphicsOutput
        • GraphicsStateGuardian
        • NonlinearImager
      • PSphereLens
      • projectionScreen:
        • Geom
        • ProjectionScreen
    • downloader:
      • bioPtr:
        • URLSpec
      • chunkedStream:
        • HTTPChannel
      • decompressor:
        • Decompressor
        • Ramfile
      • DocumentSpec
      • downloadDb:
        • DownloadDb
        • Ramfile
        • StreamReader
        • StreamWriter
      • Extractor
      • httpAuthorization:
        • URLSpec
      • httpChannel:
        • HTTPChannel
        • HTTPClient
        • Ramfile
      • httpClient:
        • Filename
        • HTTPChannel
        • HTTPClient
      • HTTPCookie
      • HTTPDate
      • HTTPEntityTag
      • HTTPEnum
      • identityStream:
        • HTTPChannel
      • MultiplexStream
      • Patcher
      • socketStream:
        • ISocketStream
        • OSocketStream
        • SSReader
        • SSWriter
        • SocketStream
      • URLSpec
    • dxgsg8:
      • dxGraphicsStateGuardian8:
        • Light
    • dxgsg9:
      • dxGraphicsStateGuardian9:
        • Light
      • dxOcclusionQueryContext9:
        • GraphicsStateGuardian
    • effects:
      • lensFlareNode:
        • ClockObject
        • GraphicsStateGuardian
    • egg:
      • EggAnimData
      • EggAttributes
      • EggBin
      • eggBinMaker:
        • EggBin
        • EggBinMaker
        • EggGroup
        • EggGroupNode
        • EggNode
      • EggComment
      • EggCompositePrimitive
      • EggCurve
      • eggData:
        • BamCacheRecord
        • EggData
      • EggExternalReference
      • EggFilenameNode
      • EggGroup
      • eggGroupNode:
        • BamCacheRecord
        • DSearchPath
        • EggGroupNode
        • EggMaterialCollection
        • EggPolygon
        • EggTextureCollection
        • EggVertex
        • EggVertexPool
      • EggGroupUniquifier
      • EggLine
      • EggMaterial
      • EggMaterialCollection
      • eggNameUniquifier:
        • EggNameUniquifier
        • EggNode
      • EggNamedObject
      • eggNode:
        • EggGroupNode
        • EggNode
        • EggRenderMode
        • EggTextureCollection
      • EggNurbsCurve
      • eggObject:
        • EggObject
        • EggTransform
      • EggPoint
      • EggPolygon
      • EggPolysetMaker
      • EggPoolUniquifier
      • eggPrimitive:
        • EggPrimitive
        • EggVertexPool
      • EggRenderMode
      • EggSAnimData
      • EggSurface
      • EggSwitchCondition
      • EggTable
      • EggTexture
      • EggTextureCollection
      • EggTransform
      • EggTriangleFan
      • EggTriangleStrip
      • EggUserData
      • eggUtilities:
        • EggNode
        • EggVertex
      • eggVertex:
        • EggGroup
        • EggPrimitive
        • EggVertex
        • EggVertexPool
      • EggVertexPool
      • EggVertexUV
      • EggXfmAnimData
      • eggXfmSAnim:
        • EggXfmAnimData
        • EggXfmSAnim
      • parserDefs:
        • EggGroupNode
        • Mutex
    • egg2pg:
      • animBundleMaker:
        • AnimBundle
        • AnimBundleNode
        • AnimChannelMatrixXfmTable
        • AnimChannelScalarTable
        • AnimGroup
        • EggGroupNode
        • EggNode
        • EggSAnimData
        • EggTable
        • EggXfmSAnim
      • characterMaker:
        • Character
        • CharacterJointBundle
        • CharacterSlider
        • EggBin
        • EggGroup
        • EggGroupNode
        • EggNode
        • EggPrimitive
        • GeomNode
        • MovingPartBase
        • PandaNode
        • PartGroup
      • deferredNodeProperty:
        • PandaNode
      • eggLoader:
        • CollisionNode
        • CollisionPlane
        • CollisionPolygon
        • CollisionSolid
        • EggBin
        • EggMaterial
        • EggNode
        • EggNurbsCurve
        • EggPolygon
        • EggPrimitive
        • EggTable
        • PolylightNode
        • PortalNode
      • eggRenderState:
        • EggMaterial
        • EggPrimitive
        • EggTexture
      • load_egg_file:
        • BamCacheRecord
    • event:
      • asyncTask:
        • AsyncTask
        • AsyncTaskManager
      • AsyncTaskManager
      • buttonEvent:
        • Datagram
        • DatagramIterator
      • buttonEventList:
        • Datagram
        • DatagramIterator
        • ModifierButtons
      • Event
      • eventHandler:
        • EventHandler
        • EventQueue
      • eventParameter:
        • EventParameter
        • EventStoreTypedRefCount
        • EventStoreValueBase
      • EventQueue
      • pointerEvent:
        • Datagram
        • DatagramIterator
      • pointerEventList:
        • Datagram
        • DatagramIterator
        • PointerEventList
    • express:
      • Buffer
      • Datagram
      • datagramGenerator:
        • VirtualFile
      • DatagramIterator
      • HashVal
      • memoryInfo:
        • ReferenceCount
        • TypedObject
      • memoryUsage:
        • ReferenceCount
      • Multifile
      • Namable
      • NodeReferenceCount
      • PStatCollectorForwardBase
      • Patchfile
      • PointerToVoid
      • ProfileTimer
      • Ramfile
      • ReferenceCount
      • TextEncoder
      • TrueClock
      • TypedReferenceCount
      • virtualFile:
        • VirtualFile
        • VirtualFileList
        • VirtualFileSystem
      • VirtualFileComposite
      • VirtualFileList
      • virtualFileMount:
        • VirtualFileSystem
      • VirtualFileSimple
      • virtualFileSystem:
        • Multifile
        • VirtualFileComposite
        • VirtualFileSystem
      • WeakPointerToVoid
      • weakReferenceList:
        • WeakPointerToVoid
    • framework:
      • windowFramework:
        • AmbientLight
        • DirectionalLight
        • DisplayRegion
        • GraphicsEngine
        • GraphicsPipe
    • glstuff:
      • glGraphicsStateGuardian_src:
        • Light
        • PlaneNode
      • glOcclusionQueryContext_src:
        • GraphicsStateGuardian
    • glxdisplay:
      • glxGraphicsPipe:
        • FrameBufferProperties
    • gobj:
      • bufferContext:
        • BufferContext
        • PreparedGraphicsObjects
      • bufferContextChain:
        • BufferContext
      • bufferResidencyTracker:
        • BufferContext
      • geom:
        • Geom
        • GeomContext
        • PreparedGraphicsObjects
      • geomCacheEntry:
        • Geom
        • GeomPrimitive
      • GeomCacheManager
      • GeomContext
      • GeomEnums
      • GeomLines
      • GeomLinestrips
      • geomMunger:
        • Geom
        • GraphicsStateGuardianBase
        • RenderState
      • GeomPoints
      • geomPrimitive:
        • GeomPrimitive
        • GraphicsStateGuardianBase
        • IndexBufferContext
        • PreparedGraphicsObjects
      • GeomTriangles
      • GeomTrifans
      • GeomTristrips
      • geomVertexAnimationSpec:
        • Datagram
        • DatagramIterator
        • GeomVertexAnimationSpec
      • geomVertexArrayData:
        • GeomVertexArrayData
        • GeomVertexArrayDataHandle
        • GraphicsStateGuardianBase
        • PreparedGraphicsObjects
        • SimpleAllocatorBlock
        • VertexBufferContext
        • VertexDataBook
      • geomVertexArrayFormat:
        • GeomVertexArrayData
        • GeomVertexArrayFormat
        • GeomVertexData
        • GeomVertexFormat
        • InternalName
      • geomVertexColumn:
        • Datagram
        • DatagramIterator
        • GeomVertexColumn
        • GeomVertexReader
        • GeomVertexWriter
        • TypedWritable
      • geomVertexData:
        • GeomVertexColumn
        • GeomVertexData
      • geomVertexFormat:
        • GeomVertexData
        • GeomVertexFormat
      • GeomVertexReader
      • GeomVertexRewriter
      • GeomVertexWriter
      • IndexBufferContext
      • InternalName
      • lens:
        • BoundingVolume
        • Lens
      • Material
      • MaterialPool
      • MatrixLens
      • OrthographicLens
      • PerspectiveLens
      • preparedGraphicsObjects:
        • GeomContext
        • GraphicsStateGuardianBase
        • IndexBufferContext
        • PreparedGraphicsObjects
        • ShaderContext
        • TextureContext
        • VertexBufferContext
      • queryContext:
        • PreparedGraphicsObjects
      • SavedContext
      • Shader
      • ShaderContext
      • simpleAllocator:
        • SimpleAllocator
        • SimpleAllocatorBlock
      • simpleLru:
        • SimpleLru
        • SimpleLruPage
      • SliderTable
      • texture:
        • BamCacheRecord
        • CullTraverser
        • PNMImage
        • PreparedGraphicsObjects
        • Texture
        • TextureContext
      • TextureContext
      • TexturePool
      • TextureStage
      • TransformBlend
      • TransformBlendTable
      • TransformTable
      • UserVertexSlider
      • UserVertexTransform
      • VertexBufferContext
      • vertexDataBlock:
        • VertexDataBlock
        • VertexDataPage
      • vertexDataBook:
        • VertexDataBlock
        • VertexDataBook
      • vertexDataPage:
        • VertexDataBlock
        • VertexDataBook
        • VertexDataPage
      • VertexDataSaveFile
      • vertexSlider:
        • SliderTable
        • VertexSlider
      • vertexTransform:
        • TransformTable
        • VertexTransform
      • VideoTexture
    • grutil:
      • CardMaker
      • FFMpegTexture
      • fisheyeMaker:
        • FisheyeMaker
        • GeomVertexWriter
      • frameRateMeter:
        • ClockObject
        • FrameRateMeter
      • HeightfieldTesselator
      • LineSegs
      • MovieTexture
      • multitexReducer:
        • GraphicsOutput
        • MultitexReducer
        • PandaNode
        • RenderState
        • TransformState
      • NodeVertexTransform
      • pipeOcclusionCullTraverser:
        • GraphicsEngine
        • GraphicsPipe
        • GraphicsStateGuardian
        • PipeOcclusionCullTraverser
      • rigidBodyCombiner:
        • NodePath
        • RigidBodyCombiner
    • gsgbase:
      • graphicsStateGuardianBase:
        • AlphaTestAttrib
        • AmbientLight
        • AntialiasAttrib
        • ClipPlaneAttrib
        • ColorAttrib
        • ColorBlendAttrib
        • ColorScaleAttrib
        • ColorWriteAttrib
        • CullFaceAttrib
        • DepthOffsetAttrib
        • DepthTestAttrib
        • DepthWriteAttrib
        • DirectionalLight
        • DisplayRegion
        • FogAttrib
        • Geom
        • GeomContext
        • GeomLines
        • GeomLinestrips
        • GeomNode
        • GeomPoints
        • GeomPrimitive
        • GeomTriangles
        • GeomTrifans
        • GeomTristrips
        • GeomVertexArrayData
        • GeomVertexData
        • GraphicsOutput
        • GraphicsStateGuardianBase
        • GraphicsWindow
        • IndexBufferContext
        • Lens
        • LightAttrib
        • Material
        • MaterialAttrib
        • NodePath
        • PointLight
        • PreparedGraphicsObjects
        • RenderModeAttrib
        • RenderState
        • RescaleNormalAttrib
        • ShadeModelAttrib
        • Shader
        • ShaderAttrib
        • ShaderContext
        • Spotlight
        • TexGenAttrib
        • TexMatrixAttrib
        • Texture
        • TextureAttrib
        • TextureContext
        • Thread
        • TransformState
        • TransparencyAttrib
        • VertexBufferContext
    • lerp:
      • lerp:
        • AutonomousLerp
        • Lerp
      • lerpblend:
        • EaseInBlendType
        • EaseInOutBlendType
        • EaseOutBlendType
        • LerpBlendType
        • NoBlendType
      • lerpfunctor:
        • LerpFunctor
        • MultiLerpFunctor
    • linmath:
      • lvecBase2:
        • Datagram
        • DatagramIterator
      • lvecBase3:
        • Datagram
        • DatagramIterator
      • lvecBase4:
        • Datagram
        • DatagramIterator
      • MathNumbers
    • mathutil:
      • BoundingBox
      • BoundingHexahedron
      • BoundingLine
      • BoundingPlane
      • BoundingSphere
      • boundingVolume:
        • BoundingBox
        • BoundingHexahedron
        • BoundingLine
        • BoundingPlane
        • BoundingSphere
        • BoundingVolume
        • FiniteBoundingVolume
        • GeometricBoundingVolume
      • fftCompressor:
        • Datagram
        • DatagramIterator
      • FiniteBoundingVolume
      • GeometricBoundingVolume
      • Mersenne
      • OmniBoundingVolume
      • PerlinNoise
      • PerlinNoise2
      • PerlinNoise3
      • plane:
        • Datagram
        • DatagramIterator
      • StackedPerlinNoise2
      • StackedPerlinNoise3
      • Triangulator
    • mesadisplay:
      • osMesaGraphicsPipe:
        • FrameBufferProperties
    • movies:
      • ffmpegAudio:
        • FfmpegAudio
        • FfmpegAudioCursor
      • FfmpegAudioCursor
      • ffmpegVideo:
        • FfmpegVideo
        • FfmpegVideoCursor
      • FfmpegVideoCursor
      • inkblotVideo:
        • InkblotVideo
        • InkblotVideoCursor
      • InkblotVideoCursor
      • movieAudio:
        • MovieAudio
        • MovieAudioCursor
      • movieAudioCursor:
        • MovieAudio
        • MovieAudioCursor
      • movieVideo:
        • MovieVideo
        • MovieVideoCursor
      • movieVideoCursor:
        • MovieVideo
        • MovieVideoCursor
    • nativenet:
      • Buffered_DatagramConnection
      • buffered_datagramwriter:
        • [4]
      • ringbuffer:
        • [5]
      • Socket_Address
      • socket_ip:
        • Socket_IP
        • Socket_TCP
        • Socket_TCP_Listen
        • Socket_UDP
        • Socket_UDP_Incoming
        • Socket_UDP_Outgoing
      • Socket_TCP
      • Socket_TCP_Listen
      • Socket_UDP
      • Socket_UDP_Incoming
      • Socket_UDP_Outgoing
    • net:
      • connection:
        • Connection
        • ConnectionManager
        • NetDatagram
        • Socket_IP
      • connectionListener:
        • ConnectionListener
        • NetAddress
      • connectionManager:
        • ConnectionManager
        • ConnectionReader
        • ConnectionWriter
        • NetAddress
      • connectionReader:
        • ConnectionManager
        • ConnectionReader
        • NetDatagram
        • Socket_Address
        • Socket_IP
      • connectionWriter:
        • ConnectionManager
        • ConnectionWriter
        • NetAddress
      • datagramTCPHeader:
        • NetDatagram
      • datagramUDPHeader:
        • NetDatagram
      • NetAddress
      • NetDatagram
      • QueuedConnectionListener
      • QueuedConnectionManager
      • QueuedConnectionReader
      • RecentConnectionReader
    • osxdisplay:
      • osxGraphicsPipe:
        • PNMImage
    • parametrics:
      • CubicCurveseg
      • curveFitter:
        • CurveFitter
        • HermiteCurve
        • NurbsCurve
        • ParametricCurve
      • HermiteCurve
      • NurbsCurve
      • NurbsCurveEvaluator
      • nurbsCurveInterface:
        • NurbsCurveInterface
        • ParametricCurve
      • NurbsCurveResult
      • NurbsSurfaceEvaluator
      • NurbsSurfaceResult
      • parametricCurve:
        • HermiteCurve
        • NurbsCurve
        • NurbsCurveInterface
        • ParametricCurve
      • ParametricCurveCollection
      • PiecewiseCurve
      • ropeNode:
        • GeomVertexData
        • RopeNode
      • SheetNode
    • particlesystem:
      • ArcEmitter
      • BaseParticleEmitter
      • BaseParticleFactory
      • BaseParticleRenderer
      • BoxEmitter
      • colorInterpolationManager:
        • ColorInterpolationFunctionConstant
        • ColorInterpolationFunctionLinear
        • ColorInterpolationFunctionSinusoid
        • ColorInterpolationFunctionStepwave
        • ColorInterpolationManager
        • ColorInterpolationSegment
      • DiscEmitter
      • GeomParticleRenderer
      • LineEmitter
      • LineParticleRenderer
      • particleSystem:
        • ParticleSystem
        • ParticleSystemManager
      • ParticleSystemManager
      • PointEmitter
      • PointParticleFactory
      • PointParticleRenderer
      • RectangleEmitter
      • RingEmitter
      • SparkleParticleRenderer
      • SphereSurfaceEmitter
      • SphereVolumeEmitter
      • spriteParticleRenderer:
        • NodePath
        • SpriteAnim
        • SpriteParticleRenderer
      • TangentRingEmitter
      • ZSpinParticleFactory
    • pgraph:
      • accumulatedAttribs:
        • PandaNode
      • AlphaTestAttrib
      • AmbientLight
      • AntialiasAttrib
      • AttribNodeRegistry
      • AudioVolumeAttrib
      • AuxSceneData
      • bamFile:
        • BamFile
        • Filename
        • TypedWritable
      • BillboardEffect
      • camera:
        • Camera
        • DisplayRegion
      • ClipPlaneAttrib
      • ColorAttrib
      • ColorBlendAttrib
      • ColorScaleAttrib
      • ColorWriteAttrib
      • CompassEffect
      • config_pgraph:
        • DSearchPath
      • cullBin:
        • GeomNode
        • GraphicsStateGuardianBase
        • PandaNode
        • RenderState
        • TransformState
      • CullBinAttrib
      • CullBinEnums
      • cullBinManager:
        • CullBinManager
        • GraphicsStateGuardianBase
      • CullFaceAttrib
      • cullHandler:
        • CullTraverser
      • cullResult:
        • CullTraverser
        • GraphicsStateGuardianBase
        • RenderState
        • TransformState
      • cullTraverser:
        • CullTraverser
        • GraphicsStateGuardian
        • NodePath
        • PandaNode
      • cullTraverserData:
        • PandaNode
      • cullableObject:
        • CullTraverser
      • DecalEffect
      • DepthOffsetAttrib
      • DepthTestAttrib
      • DepthWriteAttrib
      • DirectionalLight
      • DrawMaskAttrib
      • EventStorePandaNode
      • FadeLODNode
      • findApproxLevelEntry:
        • NodePathCollection
      • findApproxPath:
        • PandaNode
      • fog:
        • Fog
        • TransformState
      • FogAttrib
      • geomNode:
        • GeomNode
        • GraphicsStateGuardianBase
      • geomTransformer:
        • GeomNode
        • InternalName
        • RenderState
      • InternalNameCollection
      • LensNode
      • light:
        • GraphicsStateGuardianBase
        • Light
        • NodePath
        • PandaNode
      • LightAttrib
      • LightLensNode
      • LightNode
      • loader:
        • Loader
        • LoaderFileType
      • loaderFileType:
        • BamCacheRecord
        • LoaderFileType
        • LoaderOptions
      • loaderFileTypeRegistry:
        • Filename
        • LoaderFileType
        • LoaderFileTypeRegistry
      • LoaderOptions
      • LODNode
      • MaterialAttrib
      • MaterialCollection
      • ModelFlattenRequest
      • ModelLoadRequest
      • ModelNode
      • ModelPool
      • ModelRoot
      • nodePath:
        • Fog
        • GlobPattern
        • InternalNameCollection
        • Light
        • Material
        • MaterialCollection
        • NodePath
        • NodePathCollection
        • PolylightNode
        • PreparedGraphicsObjects
        • Shader
        • ShaderInput
        • Texture
        • TextureCollection
        • TextureStage
        • TextureStageCollection
      • NodePathCollection
      • nodePathLerps:
        • ColorLerpFunctor
        • ColorScaleLerpFunctor
        • HprLerpFunctor
        • HprScaleLerpFunctor
        • PosHprLerpFunctor
        • PosHprScaleLerpFunctor
        • PosLerpFunctor
        • ScaleLerpFunctor
      • pandaNode:
        • CullTraverser
        • Light
        • PandaNode
      • pandaNodeChain:
        • PandaNode
      • PlaneNode
      • PointLight
      • PolylightEffect
      • PolylightNode
      • portalClipper:
        • NodePath
        • PandaNode
        • PortalNode
      • PortalNode
      • renderAttrib:
        • CullTraverser
        • GraphicsStateGuardianBase
        • RenderAttrib
      • renderEffect:
        • CullTraverser
        • PandaNode
        • RenderEffect
      • renderEffects:
        • CullTraverser
        • RenderEffects
      • RenderModeAttrib
      • renderState:
        • AudioVolumeAttrib
        • ClipPlaneAttrib
        • ColorAttrib
        • ColorScaleAttrib
        • CullBinAttrib
        • FogAttrib
        • GraphicsStateGuardianBase
        • RenderState
        • ShaderAttrib
        • TexGenAttrib
        • TextureAttrib
        • TransparencyAttrib
      • RescaleNormalAttrib
      • sceneGraphAnalyzer:
        • Geom
        • GeomNode
        • GeomVertexArrayData
        • GeomVertexData
        • GeomVertexFormat
        • PandaNode
        • SceneGraphAnalyzer
        • Texture
      • sceneGraphReducer:
        • PandaNode
        • SceneGraphReducer
      • SelectiveChildNode
      • SequenceNode
      • ShadeModelAttrib
      • ShaderAttrib
      • shaderGenerator:
        • AmbientLight
        • DirectionalLight
        • LightAttrib
        • PointLight
        • ShaderAttrib
        • Spotlight
      • ShaderInput
      • ShaderPool
      • ShowBoundsEffect
      • spotlight:
        • Spotlight
        • Texture
      • StencilAttrib
      • SwitchNode
      • TexGenAttrib
      • TexMatrixAttrib
      • texProjectorEffect:
        • LensNode
        • TexProjectorEffect
      • TextureAttrib
      • TextureCollection
      • TextureStageCollection
      • transformState:
        • GraphicsStateGuardianBase
        • TransformState
      • TransparencyAttrib
    • pgui:
      • PGButton
      • pgButtonNotify:
        • PGButton
      • PGEntry
      • pgFrameStyle:
        • NodePath
        • PGFrameStyle
        • PandaNode
      • pgItem:
        • AudioSound
        • ClipPlaneAttrib
        • MouseWatcherParameter
        • PGItem
        • PGTop
      • pgItemNotify:
        • MouseWatcherParameter
        • PGItem
      • PGMouseWatcherBackground
      • pgMouseWatcherGroup:
        • PGTop
      • PGMouseWatcherParameter
      • pgMouseWatcherRegion:
        • PGItem
      • PGScrollFrame
      • PGSliderBar
      • pgSliderBarNotify:
        • PGSliderBar
      • pgTop:
        • GraphicsStateGuardian
        • PGTop
      • pgVirtualFrame:
        • PGVirtualFrame
        • TransformState
      • PGWaitBar
    • physics:
      • ActorNode
      • AngularEulerIntegrator
      • AngularForce
      • AngularIntegrator
      • AngularVectorForce
      • baseForce:
        • BaseForce
        • ForceNode
      • baseIntegrator:
        • Physical
      • ForceNode
      • LinearControlForce
      • LinearCylinderVortexForce
      • LinearDistanceForce
      • LinearEulerIntegrator
      • LinearForce
      • LinearFrictionForce
      • LinearIntegrator
      • LinearJitterForce
      • LinearNoiseForce
      • LinearRandomForce
      • LinearSinkForce
      • LinearSourceForce
      • LinearUserDefinedForce
      • LinearVectorForce
      • physical:
        • Physical
        • PhysicalNode
        • PhysicsManager
      • PhysicalNode
      • PhysicsCollisionHandler
      • PhysicsManager
      • PhysicsObject
      • PhysicsObjectCollection
    • pipeline:
      • ConditionVar
      • ConditionVarDirect
      • ConditionVarFull
      • ConditionVarFullDirect
      • cycleData:
        • Datagram
        • DatagramIterator
        • TypedWritable
      • ExternalThread
      • MainThread
      • mutexDirect:
        • MutexDirect
        • Thread
      • pmutex:
        • Mutex
      • PythonThread
      • ReMutex
      • reMutexDirect:
        • ReMutexDirect
        • Thread
      • reMutexHolder:
        • Thread
      • thread:
        • Mutex
        • ReMutex
        • Thread
      • threadDummyImpl:
        • Thread
      • threadPosixImpl:
        • Thread
      • threadSimpleImpl:
        • Thread
      • threadSimpleManager:
        • Thread
      • threadWin32Impl:
        • Thread
    • pnmimage:
      • pnmBrush:
        • PNMBrush
        • PNMImage
      • PNMFileType
      • pnmFileTypeRegistry:
        • PNMFileType
        • PNMFileTypeRegistry
      • pnmImage:
        • PNMFileType
        • PNMImage
      • pnmImageHeader:
        • PNMFileType
        • PNMImageHeader
      • pnmPainter:
        • PNMImage
        • PNMPainter
    • pnmtext:
      • config_pnmtext:
        • DSearchPath
      • FreetypeFont
      • pnmTextMaker:
        • Filename
        • PNMImage
        • PNMTextMaker
    • pstatclient:
      • pStatClient:
        • PStatClient
        • PStatCollector
        • PStatThread
      • pStatClientControlMessage:
        • Datagram
      • pStatClientImpl:
        • PStatClient
        • PStatCollector
        • PStatThread
      • pStatCollector:
        • PStatCollector
        • Thread
      • pStatCollectorDef:
        • Datagram
        • DatagramIterator
        • PStatClient
      • PStatCollectorForward
      • pStatFrameData:
        • Datagram
        • DatagramIterator
        • PStatClient
      • pStatProperties:
        • PStatClient
      • pStatServerControlMessage:
        • Datagram
      • pStatThread:
        • PStatThread
        • Thread
      • pStatTimer:
        • Thread
    • putil:
      • animInterface:
        • AnimInterface
        • Datagram
        • DatagramIterator
      • BamCache
      • bamCacheRecord:
        • BamCacheRecord
        • Datagram
        • DatagramIterator
      • bamReaderParam:
        • DatagramIterator
      • bitArray:
        • BitArray
        • Datagram
        • DatagramIterator
        • SparseArray
      • ButtonHandle
      • ButtonRegistry
      • CachedTypedWritableReferenceCount
      • clockObject:
        • ClockObject
        • TimeVal
      • config_util:
        • DSearchPath
      • CopyOnWriteObject
      • Factory
      • ioPtaDatagramFloat:
        • Datagram
        • DatagramIterator
      • ioPtaDatagramInt:
        • Datagram
        • DatagramIterator
      • ioPtaDatagramShort:
        • Datagram
        • DatagramIterator
      • KeyboardButton
      • LineStream
      • load_prc_file:
        • ConfigPage
        • HashVal
      • ModifierButtons
      • MouseButton
      • MouseData
      • NodeCachedReferenceCount
      • sparseArray:
        • BitArray
        • Datagram
        • DatagramIterator
        • SparseArray
      • StringStream
      • timedCycle:
        • Datagram
        • DatagramIterator
      • typedWritable:
        • Datagram
        • DatagramIterator
        • TypedWritable
      • TypedWritableReferenceCount
      • UniqueIdAllocator
      • UpdateSeq
      • vector_typedWritable:
        • TypedWritable
      • WritableConfigurable
    • recorder:
      • MouseRecorder
      • recorderBase:
        • Datagram
        • DatagramIterator
        • RecorderBase
        • TypedWritable
      • recorderController:
        • RecorderBase
        • RecorderController
      • socketStreamRecorder:
        • DatagramIterator
        • SocketStreamRecorder
    • skel:
      • BasicSkel
      • TypedSkel
    • text:
      • config_text:
        • DSearchPath
      • dynamicTextFont:
        • DynamicTextFont
        • NurbsCurveResult
      • dynamicTextGlyph:
        • DynamicTextPage
      • dynamicTextPage:
        • DynamicTextFont
        • DynamicTextPage
      • FontPool
      • GeomTextGlyph
      • StaticTextFont
      • textAssembler:
        • TextEncoder
        • TextGraphic
      • TextFont
      • TextGraphic
      • TextNode
      • TextProperties
      • TextPropertiesManager
    • tform:
      • ButtonThrower
      • DriveInterface
      • MouseInterfaceNode
      • MouseSubregion
      • mouseWatcher:
        • DisplayRegion
        • MouseWatcher
        • MouseWatcherParameter
      • MouseWatcherGroup
      • MouseWatcherParameter
      • mouseWatcherRegion:
        • MouseWatcherParameter
        • MouseWatcherRegion
      • Trackball
      • Transform2SG
    • vrpn:
      • vrpnAnalogDevice:
        • VrpnClient
      • vrpnButtonDevice:
        • VrpnClient
      • VrpnClient
      • vrpnDialDevice:
        • VrpnClient
      • vrpnTrackerDevice:
        • VrpnClient
  • pandaapp:
    • indexify:
      • fontSamples:
        • PNMTextMaker
      • indexImage:
        • PNMImage
        • PNMImageHeader
        • PNMTextMaker
      • indexify:
        • PNMTextMaker
      • rollDirectory:
        • PNMTextMaker
  • pandatool:
    • bam:
      • bamInfo:
        • PandaNode
        • Texture
        • TypedWritable
      • bamToEgg:
        • EggGroup
        • EggGroupNode
        • EggTexture
        • EggVertexPool
        • GeomNode
        • GeomTriangles
        • GeomVertexData
        • LODNode
        • PandaNode
        • RenderState
        • Texture
      • eggToBam:
        • GraphicsEngine
        • GraphicsOutput
        • GraphicsStateGuardian
        • PandaNode
        • RenderState
        • Texture
    • converter:
      • somethingToEggConverter:
        • EggData
        • EggGroupNode
    • dxfegg:
      • dxfToEggLayer:
        • EggGroupNode
        • EggVertex
    • dxfprogs:
      • eggToDXF:
        • EggGroupNode
      • eggToDXFLayer:
        • EggGroupNode
        • EggPolygon
    • egg-mkfont:
      • eggMakeFont:
        • EggGroup
        • EggVertexPool
        • PNMTextMaker
    • egg-optchar:
      • eggOptchar:
        • EggGroupNode
      • vertexMembership:
        • EggGroup
    • egg-qtess:
      • isoPlacer:
        • NurbsSurfaceResult
    • eggbase:
      • eggMultiBase:
        • Filename
      • eggReader:
        • PNMFileType
      • eggSingleBase:
        • EggNode
    • eggcharbase:
      • eggCharacterCollection:
        • EggAttributes
        • EggTable
    • eggprogs:
      • eggCrop:
        • EggGroupNode
      • eggMakeTube:
        • EggGroup
        • EggVertex
        • EggVertexPool
      • eggTextureCards:
        • EggVertex
        • EggVertexPool
      • eggToC:
        • EggBin
        • EggNode
        • EggVertexPool
    • flt:
      • fltMaterial:
        • DatagramIterator
      • fltRecord:
        • DatagramIterator
    • fltegg:
      • fltToEggConverter:
        • EggGroupNode
        • EggPrimitive
      • fltToEggLevelState:
        • EggGroup
        • EggGroupNode
    • fltprogs:
      • eggToFlt:
        • EggGroup
        • EggPrimitive
        • EggTexture
        • EggTransform
        • EggVertex
    • lwo:
      • iffInputFile:
        • Datagram
    • lwoegg:
      • cLwoSurface:
        • EggPrimitive
    • maxegg:
      • maxEggLoader:
        • EggData
      • maxLogger:
        • Logger
      • maxNodeDesc:
        • EggGroup
        • EggTable
        • EggXfmSAnim
      • maxNodeTree:
        • EggData
        • EggGroupNode
    • maxeggimport:
      • maxEggLoader:
        • EggData
    • maya:
      • mayaApi:
        • Filename
    • mayaegg:
      • mayaBlendDesc:
        • EggSAnimData
        • EggTable
      • mayaEggLoader:
        • EggData
      • mayaNodeDesc:
        • EggGroup
        • EggTable
        • EggXfmSAnim
      • mayaNodeTree:
        • EggData
        • EggGroupNode
        • EggSAnimData
        • EggTable
        • EggXfmSAnim
      • mayaToEggConverter:
        • EggData
        • EggGroup
        • EggNurbsCurve
        • EggPrimitive
        • EggTable
        • EggVertexPool
        • EggXfmSAnim
    • palettizer:
      • imageFile:
        • EggTexture
        • PNMImage
      • pal_string_utils:
        • PNMFileType
      • palettizer:
        • PNMFileType
      • sourceTextureImage:
        • PNMImageHeader
      • texturePlacement:
        • PNMImage
      • textureProperties:
        • PNMFileType
      • textureReference:
        • EggData
        • EggGroupNode
        • EggPrimitive
        • EggTexture
        • Filename
      • txaLine:
        • PNMFileType
    • softegg:
      • softNodeDesc:
        • EggGroup
        • EggTable
        • EggXfmSAnim
      • softNodeTree:
        • EggData
        • EggGroup
        • EggGroupNode
        • EggTable
        • EggXfmSAnim
      • softToEggConverter:
        • EggData
        • EggGroup
        • EggNurbsCurve
        • EggPrimitive
        • EggSAnimData
        • EggTable
        • EggVertexPool
        • EggXfmSAnim
    • softprogs:
      • softCVS:
        • Multifile
    • vrmlegg:
      • indexedFaceSet:
        • EggData
        • EggGroup
        • EggVertexPool
        • LMatrix4d
      • vrmlToEggConverter:
        • EggGroup
        • EggGroupNode
        • LMatrix4d
    • xfile:
      • xFileNode:
        • Filename
    • xfileegg:
      • xFileAnimationSet:
        • EggGroup
        • EggTable
        • EggXfmSAnim
      • xFileFace:
        • EggPolygon
      • xFileMaker:
        • Datagram
        • EggBin
        • EggData
        • EggGroup
        • EggGroupNode
        • EggNode
        • EggVertexPool
      • xFileMaterial:
        • Datagram
        • EggPrimitive
      • xFileMesh:
        • Datagram
        • EggGroupNode
        • EggPolygon
        • EggPrimitive
        • EggVertex
      • xFileNormal:
        • EggPrimitive
        • EggVertex
      • xFileToEggConverter:
        • Datagram
        • EggGroup
        • EggGroupNode
        • EggMaterial
        • EggTexture
      • xFileVertex:
        • EggPrimitive
        • EggVertex
  • ppremake:
    • DSearchPath
    • ExecutionEnvironment
    • filename:
      • DSearchPath
      • Filename
    • GlobPattern
<<prev top next>>


Panda3D Manual: API Reference Materials

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.


Panda3D Manual: Cg Tutorial Part 3

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.

Cg Tutorial Part 3: The Simplest Useful Shader

Here our shader will actually have useful output. It won't be anything fancy, just the silhouettes of the boxes since we're not doing anything with the lighting just yet. To recap, there are two types of shaders. Vertex shaders and fragment shaders. In DirectX these are called vertex shaders and pixel shaders. Fragment shader is a more accurate name for it but for the moment think of fragments as the equivalent of pixels.

The Shader

void vshader(
    uniform float4x4 mat_modelproj,
    in float4 vtx_position : POSITION,
    out float4 l_position : POSITION)
{
    l_position = mul(mat_modelproj, vtx_position);
}

void fshader(
    out float4 o_color : COLOR)
{
    o_color = float4(1.0, 0.0, 1.0, 1.0);
}

The vshader function is called once for every processed vertex while fshader is called for every drawn pixel. Because our cube has 24 vertices, vshader is called 24 times per cube in this example. fshader is called for every visible pixel of this cube. The larger the cube on the screen, the more often fshader needs to be called. We cannot say if it is called 100 times or 1000 times per cube. If the cube is far away and we only see one pixel on the screen then vshader is still called 24 times while fshader may only be called once. The vertex shader is always called before the fragment shader. As mentioned in the previous tutorial, a vertex that is being processed knows nothing about the other vertices and this allows shader processing to be parallelized, that is the GPU can process multiple shader calls at the same time.

Given a 800x600 screen, the GPU needs to process 480,000 fragment shader calls. As even the highest end GPU right now doesn't have that many processors, each processor will run the fragment shader multiple times. If your fragment or vertex shader is too complex, the GPU would not be able to process it in a timely manner and the FPS would drop. Today shaders can be complex but you should not expect a single shader to be able to do everything. Often you would need to write many specialized shaders which you then carefully apply to your scene. The Auto Shader generator in Panda3D is an example of this. A normal mapped node would have a different shader from a node that has a glow map applied to it but you do not see this as the Auto Shader Generator creates the necessary specialized shader for you.

Now, lets look at the shader that we have in this tutorial. As previously mentioned, the vshader function handles vertices. The only thing a vertex shader can do is calculate properties for vertices. One such property is the position. A vertex shader can move vertices around. In this tutorial we only move around the vertices but in the next tutorial we will calculate more properties for the vertex. A vertex shader cannot create new vertices nor can it delete vertices. This is a limitation that geometry shaders try to solve but as of now there is no support for geometry shaders in Panda3d.

If we take a closer look at the vertex shader, we can see a new line with the keyword "uniform" and a line with the "in" keyword. The "in" keyword means that there is some input from somewhere, in this case the input is named vtx_position. Referencing the List of Possible Shader Inputs, you can see that vtx_position is a reserved name. The input vtx_position gives us is the vertex coordinates for the vertex as it appears in the egg file.


Panda3D Manual: Client-Server Connection

The first step in network communication is to establish the client-server connection. This entails two sets of operations: one for the server side (which listens for incoming connections), and one for the client side (which establishes a connection to the server). Both of these processes are described below.

Preparing the server for connection

An average Panda program acting as a server will need to create four classes:

  • A QueuedConnectionManager, which handles the low-level connection processes, establishes connections, and handles unexpected network termination
  • A QueuedConnectionListener, which waits for clients to request connection
  • A QueuedConnectionReader, which buffers incoming data from an active connection
  • A ConnectionWriter, which allows PyDatagrams to be transmitted out along an active connection

The first step is to instantiate these four classes.

cManager = QueuedConnectionManager()
cListener = QueuedConnectionListener(self.cManager, 0)
cReader = QueuedConnectionReader(self.cManager, 0)
cWriter = ConnectionWriter(self.cManager,0)

activeConnections=[] # We'll want to keep track of these later

This method of instantiation prepares the classes in single-thread mode, which that realtime communication requires them to be polled periodically.

To accept client connections, the server opens a special "rendezvous" socket at a specific port address. This port address must be known by both the client and the server. Additionally, a backlog is speicified; this is the number of incoming connection requests that the connection will track before it starts rejecting connection attempts. The responsibility for managing the rendezvous socket is passed to the QueuedConnectionListener, and a task is spawned to periodically poll the listener.

port_address=9099 #No-other TCP/IP services are using this port
backlog=1000 #If we ignore 1,000 connection attempts, something is wrong!
tcpSocket = cManager.openTCPServerRendezvous(port_address,backlog)

cListener.addConnection(tcpSocket)

Since the network handlers we instantiated are polled, we'll create some tasks to do the polling.

taskMgr.add(tskListenerPolling,"Poll the connection listener",-39)
taskMgr.add(tskReaderPolling,"Poll the connection reader",-40)

When a connection comes in, the tskListenerPolling function below handles the incoming connection and hands it to the QueuedConnectionReader. The connection is now established.

def tskListenerPolling(taskdata):
  if cListener.newConnectionAvailable():

    rendezvous = PointerToConnection()
    netAddress = NetAddress()
    newConnection = PointerToConnection()

    if cListener.getNewConnection(rendezvous,netAddress,newConnection):
      newConnection = newConnection.p()
      activeConnections.append(newConnection) # Remember connection
      cReader.addConnection(newConnection)     # Begin reading connection
  return Task.cont

Once a connection has been opened, the QueuedConnectionReader may begin processing incoming packets. This is similar to the flow of the listener's task, but it is up to the server code to handle the incoming data.

def tskReaderPolling(taskdata):
  if cReader.dataAvailable():
    datagram=NetDatagram()  # catch the incoming data in this instance
    # Check the return value; if we were threaded, someone else could have
    # snagged this data before we did
    if cReader.getData(datagram):
      myProcessDataFunction(datagram)
  return Task.cont

Note that the QueuedConnectionReader retrieves data from all clients connected to the server. The NetDatagram can be queried using NetDatagram.getConnection to determine which client sent the message.

If the server wishes to send data to the client, it can use the ConnectionWriter to transmit back along the connection.

 # broadcast a message to all clients
myPyDatagram=myNewPyDatagram()  # build a datagram to send
for aClient in activeConnections:
  cWriter.send(myPyDatagram,aClient)

Finally, the server may terminate a connection by removing it from the QueuedConnectionReader's responsibility. It may also deactivate its listener so that no more connections are received

 # terminate connection to all clients

for aClient in activeConnections:
  cReader.removeConnection(aClient)
activeConnections=[]

 # close down our listener
cManager.closeConnection(tcpSocket)

Connecting with a client

The process the client undertakes to connect to a server is extremely similar to the process the server undertakes to receive connections. Like the server, a client instantiates a QueuedConnectionManager, QueuedConnectionReader, and ConnectionWriter. However, there are some differences in the process. In general, a client has no need to open a rendezvous socket or create a QueuedConnectionListener, since it will be doing the connecting itself. Instead, the client connects to a specific server by specifying the server's IP address and the correct socket ID.

port_address=9099  # same for client and server

 # a valid server URL. You can also use a DNS name
 # if the server has one, such as "localhost" or "panda3d.org"
ip_address="192.168.0.50"

 # how long until we give up trying to reach the server?
timeout_in_miliseconds=3000  # 3 seconds

myConnection=cManager.openTCPClientConnection(ip_address,port_address,timeout_in_miliseconds)
if myConnection:
  cReader.addConnection(myConnection)  # receive messages from server

When the client has finished communicating with the server, it can close the connection.

cManager.closeConnection(myConnection)


Panda3D Manual: Datagram Protocol

Underpinning Panda's networking capabilities are the classes that compose the datagram protocol. These classes allow for developer-defined packets to be transmitted using either the UDP or TCP protocols. Panda's datagram layer can serve as a solid foundation for developing higher-level networking abstractions.

This section describes the classes used to establish a connection (QueuedConnectionManager,QueuedConnectionListener, QueuedConnectionReader, and ConnectionWriter), as well as the classes that transmit information (NetDatagram, PyDatagram, and PyDatagramIterator).


Panda3D Manual: Deprecated pages
There is currently no text in this page, you can search for this page title in other pages or edit this page.


Panda3D Manual: Search

There is no page titled "Deprecated_pages". You can create this page.

For more information about searching Panda3D Manual, see Searching Panda3D Manual.

Showing below 0 results starting with #1.

No page title matches

No page text matches

Note: Unsuccessful searches are often caused by searching for common words like "have" and "from", which are not indexed, or by specifying more than one search term (only pages containing all of the search terms will appear in the result).

Search in namespaces:

List redirects
Search for
Back to the Manual


Panda3D Manual: Panda Filename Syntax

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.

Loading Models


Panda3D Manual: DisplayRegion 1.jpg
  • Image
  • File history
  • Links

DisplayRegion_1.jpg‎ (4KB, MIME type: image/jpeg)

This image was generated with the following code:

from direct.directbase.DirectStart import *
from pandac.PandaModules import *
from direct.actor.Actor import Actor

a = Actor('panda.egg', {'walk' : 'panda-walk.egg'})
a.pose('walk', 0)

a.reparentTo(render)

dlight = NodePath(DirectionalLight('dlight'))
dlight.reparentTo(base.cam)
render.setLight(dlight)

base.disableMouse()
camera.setPosHpr(-41, -23, 18, -61, -15, 0)

def makeNewDr():
    dr2 = base.win.makeDisplayRegion(0.1, 0.4, 0.2, 0.6)
    dr2.setClearColor(VBase4(0, 0, 0, 1))
    dr2.setClearColorActive(True)
    dr2.setClearDepthActive(True)

    render2 = NodePath('render2')
    cam2 = render2.attachNewNode(Camera('cam2'))
    dr2.setCamera(cam2)

    env = loader.loadModel('environment.egg')
    env.reparentTo(render2)

    cam2.setPos(-22.5, -387.3, 58.1999)
    return cam2

def splitScreen(cam, cam2):
    dr = cam.node().getDisplayRegion(0)
    dr2 = cam2.node().getDisplayRegion(0)

    dr.setDimensions(0, 0.5, 0, 1)
    dr2.setDimensions(0.5, 1, 0, 1)

    cam.node().getLens().setAspectRatio(float(dr.getPixelWidth()) / float(dr.getPixelHeight()))
    cam2.node().getLens().setAspectRatio(float(dr2.getPixelWidth()) / float(dr2.getPixelHeight()))

#cam2 = makeNewDr()
#splitScreen(base.cam, cam2)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 12:39, 5 October 2008 . . Drwr (Talk | contribs) . . 214×170 (3,979 bytes)
  • (del) (rev) 12:31, 5 October 2008 . . Drwr (Talk | contribs) . . 324×257 (6,574 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Display Regions
Back to the Manual


Panda3D Manual: Maxexp1.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp1.jpg‎ (44KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:58, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 380×242 (44,128 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:46, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Maxexp2.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp2.jpg‎ (83KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:58, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 414×431 (84,093 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:48, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Maxexp3.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp3.jpg‎ (27KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:58, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 171×334 (26,942 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:50, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Maxexp4.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp4.jpg‎ (75KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:58, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 638×348 (76,538 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:53, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Maxexp5.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp5.jpg‎ (78KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:59, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 629×338 (79,675 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:55, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Maxexp6.jpg
  • Image
  • File history
  • Links
  • Metadata

Maxexp6.jpg‎ (30KB, MIME type: image/jpeg)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 20:59, 17 May 2005 . . Josh Yelon (Talk | contribs) . . 171×334 (30,339 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Converting from 3D Studio Max

Metadata

This file contains additional information, probably added from the digital camera or scanner used to create or digitize it. If the file has been modified from its original state, some details may not fully reflect the modified image.

Orientation | Normal
Horizontal resolution | 72 dpi
Vertical resolution | 72 dpi
Software used | Adobe Photoshop 7.0
File change date and time | 16:57, 17 May 2005
Color space | 65535
Back to the Manual


Panda3D Manual: Projected bamboo.jpg
  • Image
  • File history
  • Links

Projected_bamboo.jpg‎ (36KB, MIME type: image/jpeg)

This image was generated with the following program:

from direct.directbase.DirectStart import *
from direct.actor import Actor
from pandac.PandaModules import *

base.setBackgroundColor(1, 1, 1, 1)

ripple = Actor.Actor('ripple.egg')
ripple.reparentTo(render)
ripple.setScale(10)
ripple.pose('animation', 17)

dl = DirectionalLight('dl')
dlnp = camera.attachNewNode(dl)
ripple.setLight(dlnp)

proj = render.attachNewNode(LensNode('proj'))
lens = PerspectiveLens()
proj.node().setLens(lens)
proj.node().showFrustum()
proj.find('frustum').setColor(1, 0, 0, 1)
camModel = loader.loadModel('camera.egg')
camModel.reparentTo(proj)
proj.reparentTo(render)
proj.setPos(1.5, -7.3, 2.9)
proj.setHpr(22, -15, 0)

tex = loader.loadTexture('maps/envir-reeds.png')
tex.setWrapU(Texture.WMBorderColor)
tex.setWrapV(Texture.WMBorderColor)
tex.setBorderColor(VBase4(1, 1, 1, 0))
ts = TextureStage('ts')
ts.setSort(1)
ts.setMode(TextureStage.MDecal)
ripple.projectTexture(ts, tex, proj)

base.disableMouse()
camera.setPos(-7.8, -22.4, 0)
camera.setHpr(-21, 0, 0)

base.graphicsEngine.renderFrame()
base.screenshot('projected_bamboo.jpg', defaultFilename = 0)

File history

Legend: (cur) = this is the current file, (del) = delete this old version, (rev) = revert to this old version.
Click on date to see the file uploaded on that date.

  • (del) (cur) 02:05, 11 October 2005 . . Drwr (Talk | contribs) . . 400×300 (36,421 bytes)
  • (del) (rev) 00:31, 11 October 2005 . . Drwr (Talk | contribs) . . 0×0 (38,832 bytes)

  • Edit this file using an external application

    See the setup instructions for more information.

Links

The following pages link to this file:

  • Projected Textures
Back to the Manual


Panda3D Manual: Sample Cube Map

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.

The following sample code loads up an environment, puts the camera in the center of it, and generates the six faces of a cube map from the point of view of the camera:

scene = loader.loadModel('bvw-f2004--streetscene/street-scene.egg')
scene.reparentTo(render)
scene.setZ(-2)
base.saveCubeMap('streetscene_cube_#.jpg', size = 256)

These are the six faces generated:

Right:

Image:Streetscene cube 0.jpg

Left:

Image:Streetscene cube 1.jpg

Front:

Image:Streetscene cube 2.jpg

Back:

Image:Streetscene cube 3.jpg

Top:

Image:Streetscene cube 4.jpg

Bottom:

Image:Streetscene cube 5.jpg

And when they are assembled into a cube map, it looks like this:

The six faces as a cube

Or, when we apply that cube map to a sphere, you can see there are absolutely no seams between the edges:

The cube map applied to a sphere


Panda3D Manual: Texture Combine Modes

In the Operands sections it says "When used in a setCombineAlpha() call, RGB is automatically aggregated into grayscale.", except this is not at all true. Instead it throws an error that the operand is not suitable for use as an alpha operand. Which is too bad, since I really wanted this behavior. Does the underlying rendering support this behavior and just the parameter check need to be relaxed? Does the underlying rendering need to be improved to support this behavior (is is possible?)? Or does the documentation just need to correct to reflect actual behavior?

Back to the Manual


Panda3D Manual: Transmitting Data

Once a connection has been established, data can be transmitted from one Panda program to another using the classes described in this section. Communication can happen in both directions (i.e. client-to-server or server-to-client); once the connection has been established, either side may send messages along the connection to the other side.

This section describes message passing in detail, first transmission, then receipt of a message.

Sending a message

To send a message along an established connection, the sender must first construct a PyDatagram containing the message. This involves instantiating a PyDatagram object and then populating its contents with the desired data. The type of the data is determined by the functions used to pack it; see the full documentation of the PyDatagram class for more details.

 #Developer-defined constants, telling the server what to do.
 #Your style of how to store this information may differ; this is
 #only one way to tackle the problem
PRINT_MESSAGE=1

def myNewPyDatagram(self):
  # send a test message
  myPyDatagram=PyDatagram()
  myPyDatagram.addUint8(PRINT_MESSAGE)
  myPyDatagram.addString("Hello, world!")
  return myPyDatagram

As shown in the previous section, once the datagram is constructed you may then send it using a ConnectionWriter.

cWriter.send(myPyDatagram,aConnection)

Receiving a message

As shown in the previous section, when a message is received via a QueuedConnectionReader, it can be retrieved into a NetDatagram:

datagram=NetDatagram
if cReader.getData(datagram):
  myProcessDataFunction(datagram)

A NetDatagram contains the original information that was stored in the transmitted PyDatagram. It also contains knowledge of the connection over which it was received and the address of the connection. To retrieve the connection, use the getConnection method:

sourceOfMessage=datagram.getConnection()

To retrieve the contents of the message, use the PyDatagramIterator. The iterator class acts as the complement of the PyDatagram class; its methods can be used to retrieve the content that was encoded using PyDatagram.

def myProcessDataFunction(netDatagram):
  myIterator=PyDatagramIterator(netDatagram)
  msgID=myIterator.getUint8()
  if msgID==PRINT_MESSAGE:
    messageToPrint=myIterator.getString()
    print messageToPrint

Note: It is assumed that the message recipient will retrieve the same type of content in the same order that the message sender packed the content. No mechanism exists in the PyDatagramIterator to ensure that the data being unpacked matches the requested type. Unpacking the data using a different type function will probably result in unexpected behavior.


Panda3D Manual: Josh Yelon

Hi Josh,

I've been thinking about the Wiki things. There are a few issues:

From your point of view, the issues are:

- you dont really want to advertise the wiki pages, because you dont want people knowing the manual is a wiki, for various reasons. This is understandable.

From my point of view:

- I cant tell anyone about the orphaned wiki (see above), so its just me, on my own, which sucks really

- theres no way for me to backup the contents of the wiki. Whilst I'm reasonably sure you take the necessary precautions to backup your data, accidents do happen, and the easiest solution seems to be that each person has the possibility of running backups themselves. For example, sf.net allows anyone to download a tar of the entire cvs contents, at any time.

- I'm unsure about the implications of the licence text at the bottom of the page. I'd be much more comfortable using GNU Free Documentation Licence 1.2.

So, my solution at this point, because I know you're busy, is that I've set up a new wiki here:

http://manageddreams.com/pandawiki.

  • It's using the GNU Free Documentation Licence 1.2, so anyone can contribute documentation safely, knowing that they can reuse this documentation elsewhere
  • Anyone can download a nightly tarball of the wiki's contents, minus passwords and email addresses, which are stripped for security
  • And obviously, its a separate authentication from the Panda3D manual, so theres no issue with people modifying the main manual by accident

How do you feel about this? What do you think?

Hugh

Edit: by the way, just to be explicit, if you decide that you want to host this wiki yourself, along similar lines, I'll gladly give you a full database dump to load into it. (The backup in the tar strips out email addresses and passwords for security).

Back to the Manual


Panda3D Manual: Using Microsoft Visual Studio

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.

obsolete page.


Panda3D Manual: Woodgrain Example

This page is not in the table of contents. If you want it to be part of the manual, then please insert this page into the table of contents. If you cannot think of anyplace else to put it, insert it as an appendix.

The following program will generate and write out a 3-D texture to simulate woodgrain:

from direct.directbase.DirectStart import *
from pandac.PandaModules import *
import math

# These constants define the RGB colors of the light and dark bands in
# the woodgrain.
lightGrain = (0.72, 0.72, 0.45)
darkGrain = (0.49, 0.33, 0.11)

def chooseGrain(p, xi, yi, radius):
    """ Applies the appropriate color to pixel (xi, yi), based on
    radius, the computed distance from the center of the trunk. """

    # Get the fractional part of radius.
    t = radius - math.floor(radius)

    # Now t ranges from 0 to 1.  Make it see-saw from 0 to 1 and back.
    t = abs(t - 0.5) * 2

    # Now interpolate colors.
    p.setXel(xi, yi,
             lightGrain[0] + t * (darkGrain[0] - lightGrain[0]),
             lightGrain[1] + t * (darkGrain[1] - lightGrain[1]),
             lightGrain[2] + t * (darkGrain[2] - lightGrain[2]))

def calcRadius(xn, yn, x, y, z, noiseAmp):
    """ Calculates radius, the distance from the center of the trunk,
    for the 3-d point (x, y, z).  The point is perturbed with noise to
    make the woodgrain seem more organic. """

    xp = x + xn.noise(x, y, z) * noiseAmp
    yp = y + yn.noise(x, y, z) * noiseAmp

    return math.sqrt(xp * xp + yp * yp)

def makeWoodgrain(texSize, texZSize, noiseScale, noiseZScale,
                  noiseAmp, ringScale):

    """ Generate a 3-D texture of size texSize x texSize x texZSize
    that suggests woodgrain, with the grain running along the Z (W)
    direction.  Since there is not as much detail parallel to the
    grain as across it, the texture does not need to be as large in
    the Z dimension as in the other two dimensions.

    The woodgrain shape is perturbed with Perlin noise to make it more
    organic.  The parameters noiseScale and noiseZScale controls the
    frequency of the noise; larger numbers make smoother rings.  The
    parameter noiseAmp controls the effect of the noise; larger
    numbers make more dramatic distortions.

    ringScale controls the number of rings visible in the cross
    section of the texture.  A larger number makes more, denser rings.
    """

    # First, create the two PerlinNoise objects to perturb the rings
    # in two dimensions.  This class is defined in Panda3D.
    xn = PerlinNoise3(noiseScale, noiseScale, noiseZScale)
    yn = PerlinNoise3(noiseScale, noiseScale, noiseZScale)

    # Start by creating a empty 3-D texture.
    tex = Texture('woodgrain')
    tex.setup3dTexture()

    for zi in range(texZSize):
        z = float(zi) / float(texZSize - 1) - 0.5

        # Walk through the Z slices of the texture one at a time.  For
        # each slice, we create a PNMImage, very much as if we were
        # reading the texture from disk.
        print zi
        p = PNMImage(texSize, texSize)

        # But instead of reading the PNMImage, we fill it in with the
        # ring pattern.
        for yi in range(texSize):
            y = float(yi) / float(texSize - 1) - 0.5
            for xi in range(texSize):
                x = float(xi) / float(texSize - 1) - 0.5

                radius = calcRadius(xn, yn, x, y, z, noiseAmp)
                chooseGrain(p, xi, yi, radius * ringScale)

        # Now load the current slice into the texture.
        tex.load(p, zi)

    return tex

# Create a 3-D texture.
tex = makeWoodgrain(texSize = 256, texZSize = 8, noiseScale = 0.4,
                    noiseZScale = 0.8, noiseAmp = 0.12, ringScale = 40)

# Write out the texture.  This will generate woodgrain_0.png,
# woodgrain_1.png, and so on, in the current directory.
tex.writePages(Filename('woodgrain_#.png'))

The resulting images look like this:

To get consistent (over multiple runs of the programm) values from the PerlinNoise functions a seed value != 0 has to be used, seed values equal to 0 will randomize it
The table_size is 256 by default.

PerlinNoise3( scaleX, scaleY, scaleZ, table_size, seed)
PerlinNoise2( scaleX, scaleY, table_size, seed)
PerlinNoise( table_size, seed )