From fc5b50a9755c08ba17c3c2921d08f63ebbf1a10d Mon Sep 17 00:00:00 2001 From: Sollace Date: Fri, 19 Feb 2021 16:56:01 +0200 Subject: [PATCH] Add a particle effect for dropping a cloud in a bottle --- .../unicopia/client/URenderers.java | 2 + .../particle/CloudsEscapingParticle.java | 58 ++++++++ .../client/particle/GroundPoundParticle.java | 35 ++--- .../minelittlepony/unicopia/item/JarItem.java | 10 ++ .../unicopia/particle/ParticleUtils.java | 6 +- .../unicopia/particle/UParticles.java | 1 + .../unicopia/util/shape/Cylinder.java | 130 ++++++++++++++++++ .../unicopia/util/shape/PointGenerator.java | 104 ++++++++++++++ .../unicopia/util/shape/Shape.java | 41 +----- .../unicopia/particles/clouds_escaping.json | 2 + 10 files changed, 323 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java create mode 100644 src/main/java/com/minelittlepony/unicopia/util/shape/Cylinder.java create mode 100644 src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java create mode 100644 src/main/resources/assets/unicopia/particles/clouds_escaping.json diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 299cdcf0..990e1925 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.client; import com.minelittlepony.unicopia.UEntities; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; +import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle; import com.minelittlepony.unicopia.client.particle.DiskParticle; import com.minelittlepony.unicopia.client.particle.GroundPoundParticle; import com.minelittlepony.unicopia.client.particle.HealthDrainParticle; @@ -43,6 +44,7 @@ public interface URenderers { ParticleFactoryRegistry.getInstance().register(UParticles.SPHERE, SphereParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.DISK, DiskParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new); + ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new); EntityRendererRegistry.INSTANCE.register(UEntities.THROWN_ITEM, (manager, context) -> new FlyingItemEntityRenderer<>(manager, context.getItemRenderer())); EntityRendererRegistry.INSTANCE.register(UEntities.FLOATING_ARTEFACT, FloatingArtefactEntityRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java new file mode 100644 index 00000000..ceb7ea5c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java @@ -0,0 +1,58 @@ +package com.minelittlepony.unicopia.client.particle; + +import com.minelittlepony.unicopia.particle.ParticleUtils; +import com.minelittlepony.unicopia.util.shape.Sphere; + +import net.minecraft.client.world.ClientWorld; +import net.minecraft.particle.DefaultParticleType; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.util.math.Vec3d; + +public class CloudsEscapingParticle extends GroundPoundParticle { + + public CloudsEscapingParticle(DefaultParticleType effect, ClientWorld world, double x, double y, double z, double dX, double dY, double dZ) { + super(effect, world, x, y, z, dX, dY, dZ); + maxAge = 200; + collidesWithWorld = false; + } + + @Override + public void tick() { + this.prevPosX = this.x; + this.prevPosY = this.y; + this.prevPosZ = this.z; + if (this.age++ >= this.maxAge) { + this.markDead(); + } else { + this.velocityY -= 0.04D * this.gravityStrength; + this.move(this.velocityX, this.velocityY, this.velocityZ); + this.velocityX *= 0.9800000190734863D; + this.velocityY *= 0.9800000190734863D; + this.velocityZ *= 0.9800000190734863D; + if (this.onGround) { + this.velocityX *= 0.699999988079071D; + this.velocityZ *= 0.699999988079071D; + } + + } + + this.velocityY += 0.125; + + Vec3d center = getPos(); + + double variance = age / maxAge; + Vec3d vel = new Vec3d( + (world.random.nextFloat() - 0.5) * (0.125 + variance), + velocityY * 0.2, + (world.random.nextFloat() - 0.5) * (0.125 + variance) + ); + + double columnHeight = 1 + age / 30; + new Sphere(true, columnHeight, 1, 1, 1) + .offset(center) + .randomPoints(random) + .forEach(point -> { + ParticleUtils.spawnParticle(ParticleTypes.CLOUD, world, point, vel); + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/GroundPoundParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/GroundPoundParticle.java index 2b804314..1d51493b 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/GroundPoundParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/GroundPoundParticle.java @@ -1,6 +1,6 @@ package com.minelittlepony.unicopia.client.particle; -import com.minelittlepony.unicopia.util.shape.Shape; +import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.block.BlockState; @@ -32,27 +32,18 @@ public class GroundPoundParticle extends Particle { public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float f) { } + protected Vec3d getPos() { + return new Vec3d(x, y, z); + } + @Override public void tick() { super.tick(); - spawnParticleRing(age, 1); - } + Vec3d vel = new Vec3d(0, (0.5 + (Math.sin(age) * 2.5)) * 5, 0); - - private void spawnParticleRing(int timeDiff, double yVel) { - - Shape shape = new Sphere(true, timeDiff, 1, 0, 1); - - double y = 0.5 + (Math.sin(timeDiff) * 2.5); - - yVel *= y * 5; - - Vec3d center = new Vec3d(x, this.y, z); - for (int i = 0; i < shape.getVolumeOfSpawnableSpace(); i++) { - Vec3d point = shape.computePoint(random).add(center); - - BlockPos pos = new BlockPos(point.x, center.y - 1, point.z); + new Sphere(true, age, 1, 0, 1).offset(getPos()).randomPoints(random).forEach(point -> { + BlockPos pos = new BlockPos(point).down(); BlockState state = world.getBlockState(pos); if (state.isAir()) { @@ -62,13 +53,7 @@ public class GroundPoundParticle extends Particle { } } - world.addParticle(new BlockStateParticleEffect(ParticleTypes.BLOCK, state), - point.x, - point.y, - point.z, - 0, yVel, 0 - ); - } + ParticleUtils.spawnParticle(new BlockStateParticleEffect(ParticleTypes.BLOCK, state), world, point, vel); + }); } - } diff --git a/src/main/java/com/minelittlepony/unicopia/item/JarItem.java b/src/main/java/com/minelittlepony/unicopia/item/JarItem.java index 3b017e68..62071788 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/JarItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/JarItem.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.item; import com.minelittlepony.unicopia.entity.IItemEntity; import com.minelittlepony.unicopia.entity.ItemImpl; +import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.util.WorldEvent; @@ -138,6 +139,15 @@ public class JarItem extends Item implements ProjectileDelegate, ItemImpl.Tickab if (rain || thunder) { projectile.world.syncWorldEvent(WorldEvent.PROJECTILE_HIT, projectile.getBlockPos(), thunder ? 0x888888 : 0xF8F8F8); + + for (int i = projectile.world.random.nextInt(3) + 1; i >= 0; i--) { + projectile.world.addParticle(UParticles.CLOUDS_ESCAPING, true, + projectile.getX(), projectile.getY(), projectile.getZ(), + projectile.world.random.nextFloat() - 0.5, + 0, + projectile.world.random.nextFloat() - 0.5 + ); + } } WorldEvent.play(WorldEvent.DESTROY_BLOCK, projectile.world, projectile.getBlockPos(), Blocks.GLASS.getDefaultState()); diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java index a31f9cf9..1f300ddd 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java @@ -6,6 +6,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; import net.minecraft.particle.ParticleEffect; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; /** @@ -28,12 +29,15 @@ public final class ParticleUtils { }); } + public static void spawnParticle(ParticleEffect particleId, World world, Vec3d pos, Vec3d vel) { + spawnParticle(world, particleId, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z); + } + public static void spawnParticle(World world, ParticleEffect effect, double x, double y, double z, double vX, double vY, double vZ) { if (world instanceof ServerWorld) { ((ServerWorld)world).spawnParticles(effect, x, y, z, 1, vX, vY, vZ, 0); } else { world.addParticle(effect, x, y, z, vX, vY, vZ); } - } } diff --git a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java index d3ec7d0a..36ceb97d 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java @@ -22,6 +22,7 @@ public interface UParticles { ParticleType HEALTH_DRAIN = register("health_drain", FabricParticleTypes.complex(true, FollowingParticleEffect.FACTORY)); DefaultParticleType GROUND_POUND = register("ground_pound", FabricParticleTypes.simple()); + DefaultParticleType CLOUDS_ESCAPING = register("clouds_escaping", FabricParticleTypes.simple(true)); static > T register(String name, T type) { return Registry.register(Registry.PARTICLE_TYPE, new Identifier("unicopia", name), type); diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/Cylinder.java b/src/main/java/com/minelittlepony/unicopia/util/shape/Cylinder.java new file mode 100644 index 00000000..74d703ff --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/Cylinder.java @@ -0,0 +1,130 @@ +package com.minelittlepony.unicopia.util.shape; + +import java.util.Random; + +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class Cylinder implements Shape { + + private final boolean hollow; + + private final double stretchX; + private final double stretchZ; + + private final double height; + private final double rad; + + private float yaw = 0; + private float pitch = 0; + + private final double volume; + + /** + * Creates a uniform cylinder. + * + * @param hollow True if this shape must be hollow. + * @param height Cylinder height + * @param radius Cylinder radius + * + */ + public Cylinder(boolean hollow, double height, double radius) { + this(hollow, height, radius, 1, 1); + } + + /** + * Creates a cylinder of arbitrary dimensions. + *

+ * Can be used to create a flat circle by setting the height to 0. + * + * @param hollow True if this shape must be hollow. + * @param height Cylinder height + * @param radius Cylinder radius + * @param stretchX Warp this shape's X-axis + * @param stretchZ Warp this shape's Z-axis + */ + public Cylinder(boolean hollow, double height, double radius, double stretchX, double stretchZ) { + this.hollow = hollow; + this.height = height; + rad = radius; + this.stretchX = stretchX; + this.stretchZ = stretchZ; + volume = computeSpawnableSpace(); + } + + @Override + public double getVolumeOfSpawnableSpace() { + return volume; + } + + private double computeSpawnableSpace() { + if (hollow) { + if (stretchX != stretchZ) { + double result = 3 * (stretchX + stretchZ); + result -= Math.sqrt((10 * stretchX * stretchZ) + 3 * ((stretchX * stretchX) + (stretchZ * stretchZ))); + return Math.PI * result; + } + return 2 * Math.PI * rad * stretchX * height; + } + return Math.PI * (stretchX * rad * stretchZ * rad) * height; + } + + @Override + public double getXOffset() { + return 0; + } + + @Override + public double getYOffset() { + return 0; + } + + @Override + public double getZOffset() { + return 0; + } + + @Override + public Vec3d computePoint(Random rand) { + double y = MathHelper.nextDouble(rand, 0, height); + double pheta = MathHelper.nextDouble(rand, 0, Math.PI * 2); + double rho = hollow && Math.abs(y) != height/2 ? rad : MathHelper.nextDouble(rand, 0, rad); + + return new Vec3d(rho * Math.cos(pheta) * stretchX, y, rho * Math.sin(pheta) * stretchZ) + .rotateY(yaw) + .rotateX(pitch); + } + + @Override + public Cylinder setRotation(float u, float v) { + yaw = u; + pitch = v; + return this; + } + + @Override + public boolean isPointInside(Vec3d point) { + point = point.rotateY(-yaw).rotateX(-pitch); + point = new Vec3d(point.x / stretchX, point.y, point.z / stretchZ); + double y = Math.abs(point.y); + if (y < height/2) { + double r = Math.sqrt((point.x * point.x) + (point.z * point.z)); + return hollow ? r == rad : r <= rad; + } + return y == height/2; + } + + @Override + public Vec3d getLowerBound() { + return new Vec3d(-rad * stretchX, 0, -rad * stretchZ) + .rotateY(yaw) + .rotateX(pitch); + } + + @Override + public Vec3d getUpperBound() { + return new Vec3d(-rad * stretchX, height, -rad * stretchZ) + .rotateY(yaw) + .rotateX(pitch); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java b/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java new file mode 100644 index 00000000..6e1c1d7b --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java @@ -0,0 +1,104 @@ +package com.minelittlepony.unicopia.util.shape; + +import java.util.Random; +import java.util.Spliterator; +import java.util.Spliterators.AbstractSpliterator; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import com.google.common.collect.Streams; + +import net.minecraft.util.math.Vec3d; + +/** + * + *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). + */ +public interface PointGenerator { + + /** + * Get the volume of space filled by this shape, or the surface area if hollow. + * + * @return double volume + */ + double getVolumeOfSpawnableSpace(); + + /** + * Computes a random coordinate that falls within this shape's designated area. + */ + Vec3d computePoint(Random rand); + + /** + * Returns a sequence of random points dealed out to uniformly fill this shape's area. + */ + default Stream randomPoints(Random rand) { + return randomPoints((int)getVolumeOfSpawnableSpace(), rand); + } + + /** + * Returns a sequence of N random points. + */ + default Stream randomPoints(int n, Random rand) { + AtomicInteger atom = new AtomicInteger(n); + return StreamSupport.stream(new AbstractSpliterator(n, Spliterator.SIZED) { + @Override + public boolean tryAdvance(Consumer consumer) { + + if (atom.decrementAndGet() >= 0) { + consumer.accept(computePoint(rand)); + return true; + } + + return false; + } + }, false); + } + + default PointGenerator union(PointGenerator other) { + final PointGenerator source = this; + return new PointGenerator() { + + @Override + public double getVolumeOfSpawnableSpace() { + return source.getVolumeOfSpawnableSpace() + other.getVolumeOfSpawnableSpace(); + } + + @Override + public Vec3d computePoint(Random rand) { + return source.computePoint(rand); + } + + @Override + public Stream randomPoints(int n, Random rand) { + double total = this.getVolumeOfSpawnableSpace(); + + return Streams.concat( + source.randomPoints((int)(n * (source.getVolumeOfSpawnableSpace() / total)), rand), + other.randomPoints((int)(n * (other.getVolumeOfSpawnableSpace() / total)), rand) + ); + } + }; + } + + /** + * Returns a new point generator where all of its points are offset by the specified amount. + */ + default PointGenerator offset(Vec3d offset) { + final PointGenerator source = this; + return new PointGenerator() { + + @Override + public double getVolumeOfSpawnableSpace() { + return source.getVolumeOfSpawnableSpace(); + } + + @Override + public Vec3d computePoint(Random rand) { + return source.computePoint(rand).add(offset); + } + + }; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java b/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java index 4b56e5f9..e4336326 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java @@ -1,20 +1,12 @@ package com.minelittlepony.unicopia.util.shape; -import java.util.Random; -import java.util.Spliterator; -import java.util.Spliterators.AbstractSpliterator; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - import net.minecraft.util.math.Vec3d; /** * *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). */ -public interface Shape { +public interface Shape extends PointGenerator { /** * Rotates this shape around it's center. @@ -26,13 +18,6 @@ public interface Shape { */ Shape setRotation(float u, float v); - /** - * Get the volume of space filled by this shape, or the surface area if hollow. - * - * @return double volume - */ - double getVolumeOfSpawnableSpace(); - /** * X offset from the shape's origin. * @@ -64,33 +49,9 @@ public interface Shape { */ Vec3d getUpperBound(); - /** - * Computes a random coordinate that falls within this shape's designated area. - */ - Vec3d computePoint(Random rand); - /** * Checks if the given point is on the edge, or if not hollow the inside, of this shape. * @return */ boolean isPointInside(Vec3d point); - - /** - * Returns a sequence of N random points. - */ - default Stream randomPoints(int n, Random rand) { - AtomicInteger atom = new AtomicInteger(n); - return StreamSupport.stream(new AbstractSpliterator(n, Spliterator.SIZED) { - @Override - public boolean tryAdvance(Consumer consumer) { - - if (atom.decrementAndGet() >= 0) { - consumer.accept(computePoint(rand)); - return true; - } - - return false; - } - }, false); - } } diff --git a/src/main/resources/assets/unicopia/particles/clouds_escaping.json b/src/main/resources/assets/unicopia/particles/clouds_escaping.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/src/main/resources/assets/unicopia/particles/clouds_escaping.json @@ -0,0 +1,2 @@ +{ +}