Apel is a library that brings particle animations to the table with flexible behaviour and a clean developer interface. It promises also lots of predefined shapes & paths to help the developer on their particle scene
Other
0
stars
0
forks
source link
Allow ParticleCombiners to have their own rotation/offsets #31
This would allow for sophisticated composites of ParticleObjects by allowing the individual objects within a combiner to rotate and offset on their own. It would require some rework of methods currently in ParticleCombiner (and these probably need rework anyway):
setRotation(Vector3f) and setRotation(float, float, float)currently set rotation on child objects; this would need to change.
setRotation(Vector3f, Vector3f) currently sets an incremental rotation per child object. This is probably useful, but should get a new name.
setRotationRecursively (both variations) may still be useful, but the version with progressive offsets has the progression reset with every recursive call. That may not be intended.
The family of getOffsets and setOffsetPosition calls should stop using the combiner as its data location. Individual objects have an offset property that should be used.
setParticleEffect should be nulled out, as combiners don't render particles at all.
setAmount should be nulled out, if a method is needed to set all child objects to an amount, it should be named appropriately.
setAmountRecursively shouldn't care if the increment is 0. It should be determined whether a negative increment is allowed or not. Also, the behavior when setAmount receives a value below 0 should be clarified and documented.
setObjects should allow a list of length 1, maybe allow length 0.
appendObjects should not mess with the combiner's particle effect, amount, or offset, as these should never be used.
draw should get before/after draw interceptors
Once these are done, and combiner is a true non-rendering branch node, then the following can happen:
A particle combiner and multiple particle objects (e.g., three Ellipsoids) could compose like so:
That should create a Mickey Mouse with ears that move away/toward the center of the body in a sinusoidal motion while the entire thing rotates around the y-axis. One ear (e3) is twitching back and forth, too. Doing this without applying composite rotations and offsets would be complicated mathematically, I believe.
Implementing this will require correct order of operations when composing the objects, rotations, and offsets. Looking at the body, the following would need to be applied ($M_{body}$ is the resultant matrix transformation):
$M{body} = T{body}\ x\ R{body}\ x\ S{body}$
Multiplying that matrix by each point on the body's ellipsoid would place those particles in the correct locations.
Manipulating the ears is a bit more difficult because they're offset relative to the body. I believe the ears need to be positioned first, then they would join the body in its rotation. That means doing the following:
Since matrix transformations effectively read right to left, what that's doing is placing the ear relative to the body, then applying the same transformation the body has. Matrix multiplication is associative, but not commutative, so the order is important.
Determining the order of decomposing ParticleCombiner instances into Instruction instances would need one of three approaches:
Individual objects need the ability to read up the combiner hierarchy and combine their translation/rotation/scaling properties. The resultant translations, rotations, and scalings would be encoded into the existing Ellipsoid instruction (in the example). This is equivalent to a pre-order traversal (since there's no foreknowledge of the combiner nodes).
Encoding into Instruction instances would compute the matrix as the combiner hierarchy is traversed. The matrix would be decomposed into translation and rotation when outputting individual instructions. This is equivalent to a post-order traversal, since the combiner node is computed prior to all child objects.
Combiner nodes are able to emit "simple" Instruction instances that apply and remove translation and rotation operations. At this point, scaling is not a separate property of a ParticleObject, but it could be in the future. This is worth describing in a bit more detail to understand what happens.
Option 3 (my preference) would emit instructions like this for Mickey (with comments inline after the #):
F x y z # Indicates new frame, x/y/z is the origin for the frame, provided from the Animator
T effect # ParticleEffect
M x y z # Encodes a Move from combiner, this would be a translation ('T' is particle type, though that could change)
R x y z # Encodes a Rotation from combiner, using Euler angles (Q would be for a quaternion, if chosen)
S 0 0 0 sx sy sz 0 0 0 a # Encodes an Ellipsoid with no translation, scale, no rotation, and particle amount for the body
S tx ty tz sx sy sz 0 0 0 a # Encodes an Ellipsoid with translation, scale, no rotation, and particle amount for the left ear
S -tx ty tz sx sy sz rx ry rz a # Encodes an Ellipsoid with translation, scale, rotation, and particle amount for the right ear
R -x -y -z # Undoes the combiner rotation
M -x -y -z # Undoes the combiner move
Notes on the above:
The example is somewhat simple, but it's worth noting that the body itself neither translates nor rotates on its own, only as part of the combined rotation/translation.
Trying to compose the rotation of the right ear would be non-trivial.
One could imagine more complex combiner hierarchies. The stack-like nature of translation and rotation would extend to N-deep hierarchies. The renderer would simply multiply these into the current transformation matrix.
The final "undoing" would not be necessary, as the renderer would reset for each frame.
Encoding a composite scaling vector would be possible, if there's a need.
The individual shapes could break out their translation, rotation, and scaling, since they all apply to shapes using unit lengths (for axes, radii, height, etc.). The shapes would then become a byte and a short to convey the shape and particle count, respectively. Shapes could be combined into a single instruction that's (byte, byte, short) for (shape, which shape, particle count).
This would allow for sophisticated composites of ParticleObjects by allowing the individual objects within a combiner to rotate and offset on their own. It would require some rework of methods currently in ParticleCombiner (and these probably need rework anyway):
setRotation(Vector3f)
andsetRotation(float, float, float)
currently set rotation on child objects; this would need to change.setRotation(Vector3f, Vector3f)
currently sets an incremental rotation per child object. This is probably useful, but should get a new name.setRotationRecursively
(both variations) may still be useful, but the version with progressive offsets has the progression reset with every recursive call. That may not be intended.getOffsets
andsetOffsetPosition
calls should stop using the combiner as its data location. Individual objects have anoffset
property that should be used.setParticleEffect
should be nulled out, as combiners don't render particles at all.setAmount
should be nulled out, if a method is needed to set all child objects to an amount, it should be named appropriately.setAmountRecursively
shouldn't care if the increment is 0. It should be determined whether a negative increment is allowed or not. Also, the behavior whensetAmount
receives a value below 0 should be clarified and documented.setObjects
should allow a list of length 1, maybe allow length 0.appendObjects
should not mess with the combiner's particle effect, amount, or offset, as these should never be used.draw
should get before/after draw interceptorsOnce these are done, and combiner is a true non-rendering branch node, then the following can happen:
A particle combiner and multiple particle objects (e.g., three Ellipsoids) could compose like so:
That should create a Mickey Mouse with ears that move away/toward the center of the body in a sinusoidal motion while the entire thing rotates around the y-axis. One ear (
e3
) is twitching back and forth, too. Doing this without applying composite rotations and offsets would be complicated mathematically, I believe.Implementing this will require correct order of operations when composing the objects, rotations, and offsets. Looking at the body, the following would need to be applied ($M_{body}$ is the resultant matrix transformation):
$M{body} = T{body}\ x\ R{body}\ x\ S{body}$
Multiplying that matrix by each point on the body's ellipsoid would place those particles in the correct locations.
Manipulating the ears is a bit more difficult because they're offset relative to the body. I believe the ears need to be positioned first, then they would join the body in its rotation. That means doing the following:
$M{ear} = T{body}\ x\ R{body}\ x\ S{body}\ x\ T{ear}\ x\ R{ear}\ x\ S_{ear}$
Since matrix transformations effectively read right to left, what that's doing is placing the ear relative to the body, then applying the same transformation the body has. Matrix multiplication is associative, but not commutative, so the order is important.
Determining the order of decomposing
ParticleCombiner
instances intoInstruction
instances would need one of three approaches:Ellipsoid
instruction (in the example). This is equivalent to a pre-order traversal (since there's no foreknowledge of the combiner nodes).Instruction
instances would compute the matrix as the combiner hierarchy is traversed. The matrix would be decomposed into translation and rotation when outputting individual instructions. This is equivalent to a post-order traversal, since the combiner node is computed prior to all child objects.Instruction
instances that apply and remove translation and rotation operations. At this point, scaling is not a separate property of aParticleObject
, but it could be in the future. This is worth describing in a bit more detail to understand what happens.Option 3 (my preference) would emit instructions like this for Mickey (with comments inline after the
#
):Notes on the above:
body
itself neither translates nor rotates on its own, only as part of the combined rotation/translation.(byte, byte, short)
for(shape, which shape, particle count)
.Doing this type of enhancement would require moving to matrices for transformation, so https://github.com/DarthSharkie/Apel-Mod/issues/28 is a prerequisite to this.