Wednesday, March 18

Lepton Particle Targeting

The other feature I alluded to in my last post was targeting, which allows you to specify specific starting and ending values for particle attributes and interpolate between them over time. This is used often in spontaneous arrangement effects, such as particles "imploding" from random directions into an orderly pattern, such as an image or logo. But many other effects are possible, since it gives you close control over arbitrary attributes and how they change over time without having to manage all of that complexity directly.

Here is how I envision particle targeting to work in Lepton:
  • Create a particle group to contain the "target" particles. Populate this group with particles with the desired final state for your interpolated particles.
  • Create a particle group to contain the interpolated particles with a target controller pointing at the target particle group.
  • Create or emit particles into the interpolated group however you see fit. Each new particle is matched with a particle in the target group. This matching can be done automatically or manually.
  • Each time the interpolated group is updated, particle attributes are interpolated between the initial particle state and the target particle state using a tweening function.
The magic happens in the target controller itself. When you instantiate it, you specify the target particle group, the attributes to interpolate, each attribute's tweening function and the particle age where it reaches its target. Since each interpolated particle can have a different initial age value, this means particles can reach their final state in different times. Typically the target particles would not be drawn or even change, but there would be no restriction on that and that could be used to create even more interesting effects.

Here's some theoretical sample code for targeting:
target_particles = ParticleGroup()
# ... populate target_particles ...
terps = ParticleGroup(
controllers=[
TargetRandom(target_particles,
position=EaseInOutQuad(10),
up=EaseInCirc(8)),
])
There will be several types of target controllers: Target will require interpolated particles to be matched to their targets manually, TargetRandom will randomly select targets, and TargetSequential will select targets in the order they occur in the target group. The interpolation behavior will be the same for these controller classes.

Interpolation of each attribute will be controlled by tweening functions. The keyword arguments passed to the target controller specify which particle attributes to interpolate and which tweening function to use. The simplest tween is just a linear interpolator, but more complex tweens, such as easings will create more natural looking movement and morphing. You can find a great discussion of tweening and easings in this document.

Labels: ,

Tuesday, March 17

Better texture support in Lepton

Now that Lepton 0.9a is out the door, it's time to think about the next steps in the particle engine's evolution. In particular, there are two main features I'd like to work on for the next release:
  • Greatly improved texture support
  • Interpolating particle attributes from a specific starting state to an ending state.

The former will be addressed with "texturizers". These will have the following functionality:
  • Handle the requisite loading of textures into the graphics card
  • Perform necessary texture-related state changes before rendering particles in a group
  • Computing texture coordinates for each active particle in the group
  • Restore the texture state to what is was prior to rendering the group

Here's a tentative interface for a texturizer:
class Texturizer:

def set_state(self):
"""This method sets the appropriate
OpenGL state for rendering. It is called
immediately before rendering a group of particles.
"""

def restore_state(self):
"""Restores the OpenGL state to what it was
before the call to set_state().
It is called after rendering a group of particles.
"""

@property
def tex_dimension(self):
"""The number of texture coordinates per vertex,
either 2 or 3
"""

def generate_tex_coords(self, group, coord_array):
"""Generate texture coordinates for all active
particles in the group, placing them in the
cooresponding places in coord_array, an
array of floats.

Each particle gets four texture positions
cooresponding to its lower left, lower right, upper
right and upper left corners respectively.
The number of coordinates per texture position is
determined by the value of tex_dimension.
"""

The texturizer will be instantiated by the user for use with a renderer. The arguments needed to instantiate the texturizer will vary depending on the type of texturizer. Most will accept one or more texture objects (probably borrow some pyglet code for those) and other ancillary arguments to control specific features. Here's an example of how you might use a simple texturizer:
my_texture = Texture('amazing_image.png')
particles = ParticleGroup(
controllers=[...controllers go here..],
renderer=BillboardRenderer(SpriteTexturizer(my_texture)))

This would simply apply the same texture derived from the image file amazing_image.png to each particle in the group. By default it would perform bilinear filtering to scale the texture and would clamp at the edges (i.e., it would not tile).

Here are some ideas for different texturizers:

SpriteTexturizer
Could apply one or more texture images to a group of particles. The images would be packed into a single texture atlas and the texturizer would select them by varying the texture coordinates per particle. The images could be applied sequentially (i.e., round-robin) to each particle in a group, or randomly. It may also be possible to apply weights to each sprite image to have some applied more often than others.

AnimatedTexturizer
Could apply a sequence of images to all particles in a group, like above the image frames would be packed into a single texture object in OpenGL. The animation frame assigned to each particle would be based on that particle's age. It would have an option for packing the frames into a 3D texture, so you could smoothly interpolate between the frames on the hardware. This would allow smooth animation with fewer frames.

MultiTexturizer
Could apply multiple textures to all particles in a group simultaneously. This would allow various effects by blending multiple textures together. One usage would be to have a large base texture that may also be tiled. The base texture could stay stationary relative to the particles. A second "splat" texture would be applied to each particle to show the base texture at that particle's position. As particles moved they would reveal different parts of the base texture. The splat texture could have sharp or fuzzy edges depending on the desired effect. This could be used for various simple animation techniques, or different stylized rendering.

Labels: ,