MULTITEXTURE OVERVIEW

Modern graphics cards are capable of applying more than one texture
image at once to geometry as they render polygons.  This capability is
referred to as multitexture.

The textures are applied in a pipeline fashion, where the output of
each texturing operation is used as the input to the next.  A
particular graphics card will have a certain number of texture units
dedicated to this function, which limits the number of textures that
may be pipelined in this way.

To apply a texture in Panda, you must have a Texture object (which you
might have loaded from disk, or extracted from a model) and a
TextureStage object (which you can create on-the-fly).  The primary
call to add a texture to the pipeline is:

  nodepath.set_texture(texture_stage, texture);

This adds the indicated texture into the pipeline for all the geometry
at nodepath level and below, associating it with the indicated
TextureStage object.

The purpose of the TextureStage object is to represent a single stage
in the texture pipeline.  You can create as many TextureStage objects
as you like; each one can associate a different texture, and each of
those textures will be applied together (within the limits of your
hardware).  If you want to change out a particular texture within the
pipeline without disturbing the others, keep a handle to the
TextureStage object that you used for that stage, and issue a
set_texture() call using the same TextureStage object and a different
texture--this replaces the texture that you assigned previously.  (You
may do this on the same NodePath, or on a lower NodePath level to
override the texture specified from above.)

To undo a set_texture() call for a particular stage or for all stages,
do:

  nodepath.clear_texture(texture_stage)
  nodepath.clear_texture()

Don't confuse this with the calls to actively disable a particular
texture stage or to disable texturing altogether, which are:

  nodepath.set_texture_off(texture_stage)
  nodepath.set_texture_off()

The difference between the two is that set_texture_off() inserts a
command into the scene graph to specifically turn off the texture
associated with the indicated texture stage, while clear_texture()
simply removes the texture stage from this node's list of assigned
textures.  Use clear_texture() to undo a previous call to
set_texture() on a given node.  You need set_texture_off() more
rarely; you might use this when you want to override a particular
setting from above to turn off just one particular stage of the
pipeline (for instance, you may have a set_texture() applied at the
root of a scene to apply a particular effect to everything in the
scene, but use set_texture_off() on one particular model for which you
don't want that effect applied).

There is also a default TextureStage object that is used for all of
the old single-texture Panda interfaces (like
nodepath.set_texture(texture)).  It is also the TextureStage that will
be used to apply Textures onto models (e.g. egg files and/or bam
files) that do not specify the use of multitexturing.  This default
TextureStage can be accessed by TextureStage::get_default().

There are a number of different blend modes that you may specify for
each texture stage in the pipeline; these are specified with
texture_stage.set_mode().  The mode may be one of:

     TextureStage::M_modulate
       Multiplies the incoming color by the texture color.  This
       allows the texture to darken, but not brighten, the incoming
       color.

     TextureStage::M_add
       Adds the incoming color and the texture color.  This allows the
       texture to brighten, but not darken, the incoming color, and
       tends to lead to bright, desaturated colors.

     TextureStage::M_decal
       Shows the texture color where the texture is alpha = 1, and the
       incoming color where the texture is alpha = 0.  This can be
       used to paint a texture on top of the existing texture.

     TextureStage::M_blend
       Defined for grayscale textures only.  You can specify an
       arbitrary color as a separate parameter with
       texture_stage.set_color(), and then the result of M_blend is to
       produced the specified color where the texture is white, and
       the incoming color where the texture is black.  This can be
       used to paint arbitrary color stripes or a similar effect over
       an existing texture.

     TextureStage::M_replace
       Completely replaces the incoming color with the texture color;
       probably not terribly useful in a multitexture environment,
       except for the first texture stage.

     TextureStage::M_combine
       This mode supercedes most of the above with a more powerful
       collection of options, including signed add and/or subtract,
       and linear interpolation between two different colors using a
       third parameter.  You can specify the input(s) as one or more
       combinations of a specified constant color, or the previous
       texture in the pipeline, or the incoming color.  However, very
       old graphics drivers may not support this mode.

       Since combine mode has a number of associated parameters, you
       enable this mode by calling set_combine_rgb() and
       set_combine_alpha() with the appropriate parameters; it's not
       necessary to call set_mode(M_combine).  A complete description
       of this mode is not given here.

Some of the above modes are very order-dependent.  For this reason,
you may use texture_stage.set_sort() to specify the order in which
textures should be applied, using an integer sort parameter.  When
Panda collects the textures together for rendering a particular piece
of geometry, it will sort them in order from lowest sort value to
highest sort value.  The default sort value is 0.  Thus, you can
specify a large positive number to apply a texture on top of existing
textures, or a large negative number to apply it beneath existing
textures.  

The egg loader will create texture stages automatically in the
presence of a multitexturing specification in the egg file, and it
will assign to these stages sort values in multiples of 10: the lowest
texture stage will have a sort value of 0, the next 10, the next 20,
and so on.

Since the number of texture units available on the hardware is
limited, and is usually a small number (and some hardware doesn't
support multitexturing at all, so effectively has only one texture
unit), Panda needs some rule for selecting the subset of textures to
render when you have requested more texture stages than are available.
For this Panda relies on the texture_stage.set_priority() value, which
is an integer value that represents the importance of this particular
texture.  If the requested textures will not fit on the available
number of texture units, Panda will select the n textures with the
highest priority (and then sort them into order by the set_sort()
parameter).  Between two textures with the same priority, Panda will
prefer the one with the lower sort value.  The default priority is 0.

If you need to know the actual limit, you can query your available
number of texture stages from the GraphicsStateGuardian, with the call
gsg->get_max_texture_stages() (e.g. from Python, call
base.win.getGsg().getMaxTextureStages()).


TEXTURE COORDINATES

In many cases, all of the texture stages need to use the same set of
texture coordinates, which is the default behavior.  You can also
apply a different texture matrix on some texture stages to apply a
linear transformation to the texture coordinates (for instance, to
position a decal on the surface).

  nodepath.set_tex_offset(texture_stage, u_offset, v_offset);
  nodepath.set_tex_scale(texture_stage, u_scale, v_scale);
  nodepath.set_tex_rotate(texture_stage, degrees);
  nodepath.set_tex_transform(texture_stage, general_transform);

These operations accumulate through nested nodes just like standard
scene graph transforms.  In fact, you can get and set relative texture
transforms:

  rel_offset = nodepath.get_tex_offset(other, texture_stage);
  nodepath.set_tex_scale(other, texture_stage, u_scale, v_scale);
  (etc.)

You may create LerpIntervals to lerp texture matrices.  There are no
interval types that operate directly on a texture matrix, but you can
set up a TexProjectorEffect to bind a node's transform to the texture
matrix:

  nodepath.set_tex_projector(texture_stage, from, to)

Where "from" and "to" are arbitrary NodePaths.  The TexProjectorEffect
will measure the relative transform between "from" and "to" each frame
and apply it to the nodepath's texture matrix.  Once this is in place,
you may create a LerpPosInterval, or any other Panda construct, to
adjust either the "from" or the "to" NodePath, which will thus
indirectly adjust the texture matrix by the same amount.


Sometimes, a texture stage may need to use a completely different set
of texture coordinates, for instance as provided by the artist who
generated the model.  Panda allows a model to store any number of
different sets of texture coordinates on its vertices, each with a
unique name.  You can associate any texture stage with any set of
texture coordinates you happen to have available on your model:

  texture_stage.set_texcoord_name(name)


Finally, you may need to generate texture coordinates for a particular
texture stage on the fly.  This is particularly useful, for instance,
to apply reflection maps, e.g. sphere maps or cube maps.  To enable
this effect, use:

  nodepath.set_tex_gen(texture_stage, mode)

Where mode is one of the enumerated types named by TexGenAttrib::Mode;
at the present, this may be any of M_world_position,
M_object_position, M_eye_position, or M_sphere_map.  The first three
modes simply apply the X, Y, Z coordinates of the vertex to its U, V
texture coordinates (a texture matrix may then be applied to transform
the generated texture coordinates into the particular U, V coordinate
space that you require).  The remaining modes generate texture
coordinates appropriate to a reflection map of the corresponding type,
based on the position and normal of each vertex, relative to the
camera.


The texture generation mode and the tex projector mode may be combined
to provide hardware-assisted projective texturing, where a texture is
applied to geometry as if it were projected from a particular point in
space, like a slide projector.  This is particularly useful for
applying shadow maps or flashlight effects, for instance.  There is a
convenience function on NodePath that automatically makes the three
separate calls needed to enable projective texturing:

  nodepath.project_texture(texture_stage, texture, projector);

Where projector is a NodePath that references a LensNode.  The
indicated texture is applied to the geometry at nodepath and below, as
if it were projected from the indicated projector.  The lens
properties such as field of view may be adjusted on the fly to adjust
the projection.

(Note that Panda also provides a ProjectionScreen object, which
performs an effect very similar to the project_texture() call, except
that it is performed entirely in the CPU, whereas project_texture()
will offload the work onto the graphics card if the card supports
this.  This may or may not result in a performance improvement over
ProjectionScreen, depending on the nature of your scene and your CPU
load versus your graphics card load.)
