GitBrincie212 / 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
2 stars 1 forks source link

Animator interceptors should run on tick, not up-front #44

Open DarthSharkie opened 1 month ago

DarthSharkie commented 1 month ago

The following code, when using ApelServerRenderer.client, does not behave the same as when using ApelServerRenderer.create (server-side rendering).

ParticleSphere particleSphere = new ParticleSphere(
    ParticleTypes.FLAME, 1.0f, 80, new Vector3f()
);
particleSphere.setBeforeDraw(((data, obj) -> {
    obj.setOffset(user.getPos().toVector3f().add(0, 0.3f, 0));
    obj.setRotation(obj.getRotation().add(0.001f, 0.001f, 0.001f));
}));

ParticleSphere particleSphere2 = new ParticleSphere(particleSphere);
particleSphere2.setParticleEffect(ParticleTypes.SOUL_FIRE_FLAME);
particleSphere2.setBeforeDraw(((data, obj) -> {
    obj.setOffset(user.getPos().toVector3f().add(0, 0.3f, 0));
    obj.setRotation(obj.getRotation().add(-0.001f, -0.001f, -0.001f));
}));

ParticleCuboid particleCuboid = new ParticleCuboid(ParticleTypes.END_ROD, 50, new Vector3f(1f));
particleCuboid.setAfterDraw(((data, obj) -> {
    obj.setOffset(user.getPos().toVector3f().add(0, 1f, 0));
    obj.setSize((0.8f * (float) Math.sin(0.01f * obj.getSize().x)) + 1.8f);
}));

PointAnimator pointAnimator = new PointAnimator(1, particleCuboid, new Vector3f(), 2400);

CircularAnimator circularAnimator = new CircularAnimator(
  1, 3.0f, new Vector3f(),
  new Vector3f((float) Math.PI / 2, 0, 0), particleSphere, 600
);
circularAnimator.setRevolutions(4);
circularAnimator.setDuringRenderingSteps(((data, obj) -> {
  obj.setRadius((3.0f * (float) Math.sin(0.05f * obj.getRadius())) + 3.2f);
}));

CircularAnimator circularAnimator2 = new CircularAnimator(circularAnimator);
circularAnimator2.setParticleObject(particleSphere2);
circularAnimator2.rotate(3 * (float) Math.PI / 2, (float) Math.PI, 0);

ParallelAnimator parallelAnimator = new ParallelAnimator(0, circularAnimator, circularAnimator2, pointAnimator);
parallelAnimator.beginAnimation(ApelServerRenderer.create(world));
GitBrincie212 commented 1 month ago

Yup, specifically the cricles render half-way

DarthSharkie commented 1 month ago

What position are you at when trying this? I think what's happening is the detection to determine whether to send the instructions to the client is using a radius of 32 blocks to determine whether to send the payload or not. Since this code is rendering at the origin, that's the value being checked for distance from the player. If you're around 32 blocks from the origin, then there's a good chance you'll see part of the circular animation. If the point (as computed by the CircularAnimator) moves more than 32 blocks from you, the instructions will no longer send, and therefore you won't see the spheres render.

That value can be modified, and it's probably a value that should become a configuration.

It would also be good to figure out if the pattern in this code (render at origin, position with offset) is a pattern that should be supported, or if the guidance and docs should describe a better way to compute an origin. For example, use the animator interceptor to get the player's position and update the center of the circular animator's path. That would be ideal, I think, since the path is really supposed to be relevant to the player, and the offset (0, 0.3, 0) is not really used to do more than raise the sphere a little bit.

DarthSharkie commented 1 month ago

I changed some of the code to the following to try to set the center of the circular path to the user's position (everything not seen here is still the same):

        ParticleSphere particleSphere = ParticleSphere.builder().particleEffect(ParticleTypes.FLAME).radius(1.0f)
                .amount(80).offset(new Vector3f(0, 0.3f, 0))
                .beforeDraw((data, obj) -> obj.setRotation(obj.getRotation().add(0.001f, 0.001f, 0.001f)))
                .build();

        circularAnimator.setDuringRenderingSteps(((data, obj) -> {
            obj.setCenter(user.getPos().toVector3f());
            obj.setRadius((3.0f * (float) Math.sin(0.05f * obj.getRadius())) + 3.2f);
        }));

What I found is that the center doesn't move. The reason for this is the animation frames are all computed near-instantly instead of being run on the tick they should be. I think the animator code should run on the correct tick, not be front-loaded like it is now. That will change the scheduler a bit, but it will also (I believe) solve the lag problem we've discussed. I'm going to explore that change and see how big it is.

DarthSharkie commented 1 month ago

Coming back to this one. Here's updated code that uses builders to construct objects and animators:

        Vector3f userPos = user.getPos().toVector3f();
        user.sendMessage(Text.of("User at " + userPos));

        ParticleSphere particleSphere = ParticleSphere.builder().particleEffect( ParticleTypes.FLAME).radius(1.0f)
                .amount(80).beforeDraw((data, obj) -> {
            obj.setOffset(user.getPos().toVector3f().add(0, 0.3f, 0));
            obj.setRotation(obj.getRotation().add(0.001f, 0.001f, 0.001f));
        }).build();

        ParticleSphere particleSphere2 = new ParticleSphere(particleSphere);
        particleSphere2.setParticleEffect(ParticleTypes.SOUL_FIRE_FLAME);
        particleSphere2.setBeforeDraw(((data, obj) -> {
            obj.setOffset(user.getPos().toVector3f().add(0, 0.3f, 0));
            obj.setRotation(obj.getRotation().add(-0.001f, -0.001f, -0.001f));
        }));

        ParticleCuboid particleCuboid = ParticleCuboid.builder().particleEffect(ParticleTypes.END_ROD).amount(50)
                .size(new Vector3f(1f)).afterDraw((data, obj) -> {
            obj.setOffset(user.getPos().toVector3f().add(0, 1f, 0));
            obj.setSize((0.8f * (float) Math.sin(0.01f * obj.getSize().x)) + 1.8f);
        }).build();

        PointAnimator pointAnimator = PointAnimator.builder().delay(1).particleObject(particleCuboid)
                .renderingSteps(2400).build();

        CircularAnimator circularAnimator = CircularAnimator.builder().delay(1).center(new Vector3f()).radius(3.0f)
                .rotation(new Vector3f((float) Math.PI / 2, 0, 0)).particleObject(particleSphere).renderingSteps(600)
                .revolutions(4).build();
        circularAnimator.setDuringRenderingSteps(((data, obj) -> {
            obj.setRadius((3.0f * (float) Math.sin(0.05f * obj.getRadius())) + 3.2f);
        }));

        CircularAnimator circularAnimator2 = new CircularAnimator(circularAnimator);
        circularAnimator2.setParticleObject(particleSphere2);
        circularAnimator2.rotate(3 * (float) Math.PI / 2, (float) Math.PI, 0);

        ParallelAnimator parallelAnimator = ParallelAnimator.builder().delay(0).animator(circularAnimator)
                .animator(circularAnimator2).animator(pointAnimator).build();
        parallelAnimator.beginAnimation(ApelServerRenderer.create(world));

Summarizing the issues:

  1. The animator computing all steps up front means that setting something like the center or point of the animator to the user's position will not work as expected. (Shown in https://github.com/GitBrincie212/Apel-Mod/issues/44#issuecomment-2227546461) The only way to address this right now is move the offset in a beforeDraw or afterDraw method, but this leads to (2).
  2. If objects have offsets larger than Minecraft's default range, client-side rendering will not send the instructions. Server-side rendering works because the location (at least in the code shown in this comment) is close enough to the player to render.

Fixing the issues:

  1. Animators should run their interceptors on the ticks when they render frames, not up front. This issue will track the fix.
  2. This issue is captured in #50 and will be addressed there.