question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

API/Utilities for Particle creation and registration

See original GitHub issue

As it currently stands, creating a custom Particle is… quite the ordeal. There are several steps involved in doing so:

  • Creating the Particle subclass (client)
  • Registering any required sprites (client)
  • Registering the appropriate ParticleType (client/server)
  • Registering a ParticleFactory (client)
  • Actually spawning the particle (client)

Of these 5, only registering the ParticleType is straightforward, and even that can’t be easily understood by looking at how Vanilla handles particles. Thus, I’d like to propose a Fabric wrapper around the particle system to simplify this process.

Simple Steps - Particle and ParticleType

Creating a custom Particle is almost as straightforward and easy as a Block or Item as long as you don’t need to do anything particularly fancy with it. The trouble only really comes when you try to apply a custom texture. It’s almost as simple as this:

@Environment(EnvType.CLIENT)
public class TestParticle extends SpriteBillboardParticle {
    static final Identifier sprite = new Identifier("particletest:particle/test_particle");

    public TestParticle(World world, double x, double y, double z) { this(world, x, y, z, 0, 0, 0); }
    public TestParticle(World world, double x, double y, double z, double vx, double vy, double vz) {
        super(world, x, y, z, vx, vy, vz);
        this.setSprite(MinecraftClient.getInstance().getSpriteAtlas().getSprite(sprite));
    }

    public ParticleTextureSheet getType() { return ParticleTextureSheet.PARTICLE_SHEET_OPAQUE; }
}

Registering the ParticleType is similarly straightforward, albeit extremely obtuse:

public class ParticleTestCommon implements ModInitializer {
    public static DefaultParticleType testParticleType = new DefaultParticleType(false){};
    public void onInitialize() {
        Registry.register(Registry.PARTICLE_TYPE, "particletest:test_particle", testParticleType);
    }
}

Problem 1 - Registering ParticleFactorys

ParticleFactory registry, on the other hand, is quite the ordeal. There is no Registry type for ParticleFactorys, and all the relevant methods on ParticleManager are private. The only solution I’ve found is a client-side mixin:

@Mixin(ParticleManager.class)
public abstract class MixinParticleManager {
    @Shadow
    private <T extends ParticleEffect> void registerFactory(ParticleType<T> pt, ParticleFactory<T> pf) {}

    @Inject(method = "net/minecraft/client/particle/ParticleManager.registerDefaultFactories()V", at = @At("RETURN"))
    private void registerCustomFactories(CallbackInfo cbi) {
        this.registerFactory(ParticleTestCommon.testParticleType, (pt, world, x, y, z, vx, vy, vz) -> {
            return new TestParticle(world, x, y, z, vx, vy, vz);
        });
    }
}

We can then spawn our particle as follows:

world.addParticle(testParticleType, x, y, z, 0, 0, 0);

Or, if we need to pass custom properties of some kind, we can bypass the ParticleFactory altogether (keep in mind that this will make your particle incompatible with or feature-limited when spawned via the /particle command - this should really be done through a custom ParticleType rather than bypassing ParticleFactory but I haven’t messed with that yet so I can’t give examples):

MinecraftClient.getInstance().particleManager.addParticle(new TestParticle(world, x, y, z, vx, vy, vz, ...));

This works passably, but as we’ll see in just a moment, it comes with an inherent and deadly problem.

Problem 2 - Registering Textures

Let’s get the biggest problem out of the way now - registering custom particle textures is nearly if not impossible to do “Vanilla-esque”. There are two separate levels involved; the client texture registry, and a modification to how your ParticleFactory is registered. Registering the texture with the client is (theoretically) simple, via ClientSpriteRegistryCallback:

@Environment(EnvType.CLIENT)
public class ParticleTestClient implements ClientModInitializer {
    public void onInitializeClient() {
        ClientSpriteRegistryCallback.EVENT.register((atlas, registry) -> {
            if(atlas == MinecraftClient.getInstance().getSpriteAtlas()) {
                registry.register(TestParticle.sprite);
            }
        });
    }
}

However, the problem comes when we try to update our ParticleFactory to properly use this texture. Registering a ParticleFactory that can use this texture requires a SpriteProvider, which uses a different version of ParticleManager#registerFactory. Unfortunately, ParticleManager uses two package-private inner classes in the process of this version of the registry method: SimpleSpriteProvider and class_4091 (henceforth called ParticleFactoryFactory for simplicity, because that’s literally what it is). It’s impossible to shadow the proper version of registerFactory, because it uses ParticleFactoryFactory in its signature. It’s also impossible to shadow the maps that registerFactory saves the registered information to and create our own registerFactory method, since one of those maps has SimpleSpriteProvider in its signature. As of the time of writing, I have been unable to find any alternative method that uses the built-in particle sprite registry.

The Workaround

I have, however, found one workaround - skipping the vanilla particle texture registry altogether. This reduces the process to only 4 steps:

  • Creating the Particle subclass (client)
  • Registering the appropriate ParticleType (client/server)
  • Registering a ParticleFactory (client)
  • Actually spawning the particle (client)

Since we no longer need to use the vanilla sprite registry, there’s no need to use the more complex version of ParticleManager#registerFactory, and we bypass the issue of the package-private inner classes altogether. There are a couple of other changes that come along with this:

  • The client-side entrypoint is no longer needed
  • Our custom Particle needs to extend BillboardParticle instead of SpriteBillboardParticle (cutting one link out of the inheritance chain), which will require a couple of extra abstract method implementations
  • We need to manually acquire and bind our particle texture when we’re ready to draw our particle

This means the following changes to our Particle class:

@Environment(EnvType.CLIENT)
public class TestParticle extends /*Sprite*/BillboardParticle { // <-- HERE
    static final Identifier sprite = new Identifier("particletest:textures/particle/test_particle.png"); // <-- HERE

    public TestParticle(World world, double x, double y, double z) { this(world, x, y, z, 0, 0, 0); }
    public TestParticle(World world, double x, double y, double z, double vx, double vy, double vz) {
        super(world, x, y, z, vx, vy, vz);
//        this.setSprite(MinecraftClient.getInstance().getSpriteAtlas().getSprite(sprite)); // <-- HERE
    }

    public ParticleTextureSheet getType() { return ParticleTextureSheet.PARTICLE_SHEET_OPAQUE; }

    // v ADDED METHODS v

    public void buildGeometry(BufferBuilder var1, Camera var2, float var3, float var4, float var5, float var6, float var7, float var8) {
        MinecraftClient.getInstance().getTextureManager().bindTexture(texture);
        super.buildGeometry(var1, var2, var3, var4, var5, var6, var7, var8);
    }

    protected float getMinU() { return 0; }
    protected float getMaxU() { return 1; }
    protected float getMinV() { return 0; }
    protected float getMaxV() { return 1; }
}

This works perfectly fine. We can revert our MixinParticleManager to before we tried to shadow the more complex overload of registerFactory, and spawn our particles into the world the same way.

Proposed API

I would personally propose the following additions to Fabric’s API:

  • A FabricParticle class which can be extended rather than extending BillboardParticle directly, which implements the basic method changes like getMin/MaxU/V and buildGeometry and makes the texture location a superctor parameter (potentially simplified from the full path needed?)
    • Any particles which are more complicated than BillboardParticle is set up for will need to do this for themselves, but currently all Vanilla particles except the following extend BillboardParticle at least indirectly, so these cases should be rare.
      • ElderGuardianAppearanceParticle
      • EmitterParticle
      • ExplosionEmitterParticle
      • FireworksSparkParticle and FireworkParticle
      • ItemPickupParticle
      • Some of the base “extend me” particle classes that aren’t actual ingame particles on their own
  • A FabricParticleTypeRegistry and/or FabricParticleFactoryRegistry which can be publicly accessed
    • The former would be for registering ParticleTypes - DefaultParticleType is almost always sufficient for this, but it would be trivial to add an overload that accepts a manually-constructed ParticleType. The registry method would take an identifier and optional custom ParticleType, call Registry.register(PARTICLE_TYPE, ...), and return the registered ParticleType so it could be saved for later use.
      • Note that while this is not strictly necessary for the API to function, it would smooth the process of understanding how to register a particle significantly, as it cuts out the middle man of understanding where you’re supposed to get a ParticleType and how to construct a trivial one for simple particles.
    • The latter would be for registering ParticleFactorys. The registry method would take a ParticleType (usually from the above method) and a lambda for the factory body, and store them in a temporary map.
  • A mixin to ParticleManager#registerDefaultFactories, as done manually above, that accesses the temporary map from FabricParticleFactoryRegistry and registers all of the contained factories with ParticleManager#registerFactory.
  • Potentially a simplified way of creating custom ParticleTypes?

These additions would drastically simplify the process of creating a custom particle, allowing modders to bypass potential hours of digging through Vanilla’s code before realizing that they can’t actually register a particle the same way Vanilla does.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:15 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
Prospectorcommented, Jun 10, 2019

I’d say leave this open until it’s merged

0reactions
KatrinaKittencommented, Sep 8, 2021

@ShadowHunter22 This issue is extremely outdated and none of the code in it is relevant to current Fabric development. It was replaced by a PR in #264, which itself was superceded by #341, which has been merged for almost 2 years now. If you’re trying to use the code from this issue, don’t. If you’re not, you’re in the wrong place.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Transforming Energy into Mass: Particle Creation
Relativistic Collisions Can Produce New Particles Energy Necessary to Produce a Pion Antiproton Production A Machine Built to Produce One Particle
Read more >
Particle creation and particle number in an expanding universe
I explain how the method that I used to obtain the observable particle number operator involved adiabatic invariance of the particle number ( ......
Read more >
Phys. Rev. 183, 1057 (1969) - Quantized Fields and Particle ...
We obtain an expression for the average particle density as a function of the time, and show that particle creation occurs in pairs....
Read more >
Special relativity and particle creation - Physics Stack Exchange
Special Relativity is necessary in order to have the generation of particles from energy, the four vector algebra defines invariant masses for ...
Read more >
Appendix for nanoforms applicable to the Guidance ... - ECHA
registration purposes and provide advice on how to create “sets of ... Particle size distribution and number fraction of constituent ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found