DarthSharkie / Apel-Mod

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

Open DarthSharkie opened 5 months ago

DarthSharkie commented 5 months ago

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):

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:

ParticleEllipsoid e1 = new ParticleEllipsoid(effect, 5f, 5f, 2f, 500);
ParticleEllipsoid e2 = new ParticleEllipsoid(effect, 2f, 2f, 1f, 200);
ParticleEllipsoid e3 = new ParticleEllipsoid(e2);
e2.setOffset(4, 4, 0);
e2.setBeforeDraw((data, obj) -> {
    float diag = 4f + Math.sin(data.getCurrentStep() * Math.TAU / 120);
    obj.setOffset(diag, diag, 0);
});
e3.setOffset(-4, 4, 0);
e3.setBeforeDraw((data, obj) -> {
    float diag = 4f + Math.sin(data.getCurrentStep() * Math.TAU / 120);
    obj.setOffset(-diag, diag, 0);
    float twitch = 0f + Math.sin(data.getCurrentStep() * Math.TAU / 240);
    obj.getRotation().set(twitch, 0f, 0f);
});

ParticleCombiner c = new ParticleCombiner(e1, e2, e3);
c.setOffset(0, 0, 5);
c.setBeforeDraw((data, obj) -> obj.setRotation(0, 0.005f, 0));

PointAnimator(1, c, user.getPos().toVector3f(), 10000);

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 into Instruction instances would need one of three approaches:

  1. 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).
  2. 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.
  3. 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:

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.

DarthSharkie commented 4 months ago

This is a key part of https://github.com/GitBrincie212/Apel-Mod/issues/33, I think, since the letters/text are likely to be ParticleCombiners that combine lines, Bezier curves, and maybe more.