Adds several particle improvements

Adds a new sphere particle mode
Stores particle calculations for improved efficiency
Makes particles take the height of the launchpad block into account
Sets a limit for circular density as too many particles create visual glitches.
Makes the particle respawn time configurable
This commit is contained in:
Kristian Knarvik 2023-07-04 15:12:26 +02:00
parent b558129944
commit e528ce96b5
4 changed files with 125 additions and 28 deletions

View File

@ -161,6 +161,7 @@ public class LaunchpadConfiguration {
double heightOffset = particlesSection.getDouble("heightOffset", 0.5);
double particleDensity = particlesSection.getDouble("particleDensity", 0.1);
double extra = particlesSection.getDouble("extra", 0);
int spawnDelay = particlesSection.getInt("spawnDelay", 20);
ParticleMode particleMode;
try {
particleMode = ParticleMode.valueOf(particlesSection.getString("mode"));
@ -184,7 +185,7 @@ public class LaunchpadConfiguration {
// Start particle spawning
particleTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(),
new ParticleSpawner(particleMode, particleType, particleAmount, particleDensity, heightOffset,
offsetX, offsetY, offsetZ, extra), 20, 20);
offsetX, offsetY, offsetZ, extra), 20, spawnDelay);
}
}

View File

@ -21,8 +21,13 @@ public enum ParticleMode {
CIRCLE,
/**
* Spawns the set amount of particles in a triangle centered on the block
* Spawns the set amount of particles in a pyramid centered on the block
*/
PYRAMID,
/**
* Spawns the set amount of particles in a sphere centered on the block
*/
SPHERE,
}

View File

@ -6,6 +6,7 @@ import net.knarcraft.launchpad.launchpad.LaunchpadBlockHandler;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
@ -24,6 +25,12 @@ public class ParticleSpawner implements Runnable {
private final double offsetZ;
private final double extra;
private Vector[] pyramidVectors;
private double[][] circleCoordinates;
private double[][] sphereCoordinates;
private LaunchpadBlock processingLaunchpad = null;
/**
* Instantiates a new particle spawner
*
@ -49,11 +56,15 @@ public class ParticleSpawner implements Runnable {
this.offsetY = offsetY;
this.offsetZ = offsetZ;
this.extra = extra;
this.pyramidVectors = null;
this.circleCoordinates = null;
}
@Override
public void run() {
for (LaunchpadBlock launchpad : LaunchpadBlockHandler.getAll()) {
// Ignore launchpads in unloaded chunks
if (!launchpad.getBlock().getChunk().isLoaded()) {
continue;
}
@ -64,15 +75,53 @@ public class ParticleSpawner implements Runnable {
continue;
}
// Store the currently processed launchpad for height calculation
processingLaunchpad = launchpad;
switch (particleMode) {
case SINGLE -> spawnParticle(world, location.clone().add(0.5, heightOffset, 0.5));
case SQUARE -> drawSquare(world, location);
case CIRCLE -> drawCircle(world, location);
case PYRAMID -> drawPyramid(world, location);
case SPHERE -> drawSphere(world, location);
}
}
}
/**
* Spawns a sphere of particles at the given location
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
*/
private void drawSphere(@NotNull World world, @NotNull Location location) {
// For spheres, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point
// errors when calculating the length of circleCoordinates
double density = Math.max(1, particleDensity);
// Store calculations for improved efficiency
if (sphereCoordinates == null) {
int length = (int) Math.ceil((180 / density));
sphereCoordinates = new double[length * 3][];
int i = 0;
for (float x = 0; x < 180; x += density) {
if (i >= sphereCoordinates.length) {
continue;
}
sphereCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5,
heightOffset + 0.5, (0.5 * Math.cos(x)) + 0.5};
sphereCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5,
heightOffset + 0.5 + (0.5 * Math.cos(x)), 0.5};
sphereCoordinates[i++] = new double[]{0.5,
heightOffset + 0.5 + (0.5 * Math.sin(x)), (0.5 * Math.cos(x)) + 0.5};
}
}
// Spawn particles on the stored locations, relative to the launchpad
for (double[] sphereCoordinate : sphereCoordinates) {
spawnParticle(world, location.clone().add(sphereCoordinate[0], sphereCoordinate[1], sphereCoordinate[2]));
}
}
/**
* Spawns a pyramid of particles at the given location
*
@ -80,24 +129,31 @@ public class ParticleSpawner implements Runnable {
* @param location <p>The location of the block to spawn the particles at</p>
*/
private void drawPyramid(@NotNull World world, @NotNull Location location) {
// Draw the bottom of the
// Draw the bottom of the pyramid
drawSquare(world, location);
// Top: 0.5, 1, 0.5. Corner 1: 0, 0, 0. Corner 2: 1, 0, 0. Corner 3: 0, 0, 1, Corner 4: 1, 0, 1
double triangleHeight = 1;
Vector topVector = new Vector(0.5, triangleHeight + heightOffset, 0.5);
Location topLocation = location.clone().add(topVector);
double coordinateMin = -0.5 * heightOffset;
double coordinateMax = 1 + (0.5 * heightOffset);
Vector line1Direction = new Vector(coordinateMin, 0, coordinateMin).subtract(topVector).normalize();
Vector line2Direction = new Vector(coordinateMax, 0, coordinateMin).subtract(topVector).normalize();
Vector line3Direction = new Vector(coordinateMin, 0, coordinateMax).subtract(topVector).normalize();
Vector line4Direction = new Vector(coordinateMax, 0, coordinateMax).subtract(topVector).normalize();
for (double x = 0; x <= triangleHeight; x += particleDensity) {
spawnParticle(world, topLocation.clone().add(line1Direction.clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(line2Direction.clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(line3Direction.clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(line4Direction.clone().multiply(x)));
// Store calculations for improved efficiency
if (pyramidVectors == null) {
// The 0.5 offsets are required for the angle of the pyramid's 4 lines to be correct
double coordinateMin = -0.5 * heightOffset;
double coordinateMax = 1 + (0.5 * heightOffset);
pyramidVectors = new Vector[5];
// The vector from the origin to the top of the pyramid
pyramidVectors[0] = new Vector(0.5, 1 + heightOffset, 0.5);
// The vectors from the top of the pyramid towards each corner
pyramidVectors[1] = new Vector(coordinateMin, 0, coordinateMin).subtract(pyramidVectors[0]).normalize();
pyramidVectors[2] = new Vector(coordinateMax, 0, coordinateMin).subtract(pyramidVectors[0]).normalize();
pyramidVectors[3] = new Vector(coordinateMin, 0, coordinateMax).subtract(pyramidVectors[0]).normalize();
pyramidVectors[4] = new Vector(coordinateMax, 0, coordinateMax).subtract(pyramidVectors[0]).normalize();
}
Location topLocation = location.clone().add(pyramidVectors[0]);
for (double x = 0; x <= 1; x += particleDensity) {
spawnParticle(world, topLocation.clone().add(pyramidVectors[1].clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(pyramidVectors[2].clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(pyramidVectors[3].clone().multiply(x)));
spawnParticle(world, topLocation.clone().add(pyramidVectors[4].clone().multiply(x)));
}
}
@ -108,9 +164,24 @@ public class ParticleSpawner implements Runnable {
* @param location <p>The location of the block to spawn the particles at</p>
*/
private void drawCircle(@NotNull World world, @NotNull Location location) {
for (float x = 0; x < 180; x += particleDensity) {
spawnParticle(world, location.clone().add((0.5 * Math.sin(x)) + 0.5, heightOffset,
(0.5 * Math.cos(x)) + 0.5));
// For circles, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point
// errors when calculating the length of circleCoordinates
double density = Math.max(1, particleDensity);
// Store calculations for improved efficiency
if (circleCoordinates == null) {
circleCoordinates = new double[(int) Math.ceil((180 / density))][];
int i = 0;
for (float x = 0; x < 180; x += density) {
if (i >= circleCoordinates.length) {
continue;
}
circleCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5, (0.5 * Math.cos(x)) + 0.5};
}
}
// Spawn particles on the stored locations, relative to the launchpad
for (double[] circleCoordinate : circleCoordinates) {
spawnParticle(world, location.clone().add(circleCoordinate[0], heightOffset, circleCoordinate[1]));
}
}
@ -121,7 +192,7 @@ public class ParticleSpawner implements Runnable {
* @param location <p>The location of the block to spawn the particles at</p>
*/
private void drawSquare(@NotNull World world, @NotNull Location location) {
for (float x = 0; x < 1; x += particleDensity) {
for (float x = 0; x <= 1; x += particleDensity) {
spawnParticle(world, location.clone().add(x, heightOffset, 0));
spawnParticle(world, location.clone().add(x, heightOffset, 1));
spawnParticle(world, location.clone().add(0, heightOffset, x));
@ -136,7 +207,24 @@ public class ParticleSpawner implements Runnable {
* @param location <p>The location to spawn the particle at</p>
*/
private void spawnParticle(@NotNull World world, @NotNull Location location) {
world.spawnParticle(particleType, location, particleAmount, offsetX, offsetY, offsetZ, extra);
world.spawnParticle(particleType, location.add(0, getBlockHeight(processingLaunchpad), 0), particleAmount,
offsetX, offsetY, offsetZ, extra);
}
/**
* Gets the height of the launchpad block at the given location
*
* @param launchpad <p>The launchpad to check</p>
* @return <p>The height of the block</p>
*/
private double getBlockHeight(LaunchpadBlock launchpad) {
double maxY = 0;
for (BoundingBox boundingBox : launchpad.getBlock().getCollisionShape().getBoundingBoxes()) {
if (boundingBox.getMaxY() > maxY) {
maxY = boundingBox.getMaxY();
}
}
return maxY;
}
}

View File

@ -25,17 +25,17 @@ launchpad:
particles:
# Whether to enable particles above launchpads
enabled: false
# The mode used for spawning particles. Valid values are: SINGLE, SQUARE and CIRCLE
# The mode used for spawning particles. Valid values are: SINGLE, SQUARE, PYRAMID, SPHERE and CIRCLE
mode: SQUARE
# The type of particle to spawn. See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html
type: DRIP_LAVA
# The number of particles to spawn every second
amount: 1
# The offset of the particle in the X direction, relative to the launchpad
# The offset, or spread of the particles in the X direction, relative to the launchpad
offsetX: 0
# The offset of the particle in the Y direction, relative to the launchpad
# The offset, or spread of the particles in the Y direction, relative to the launchpad
offsetY: 0
# The offset of the particle in the Z direction, relative to the launchpad
# The offset, or spread of the particles in the Z direction, relative to the launchpad
offsetZ: 0
# The height above the launchpad the particle should spawn. 1 = one block above, 0.5 = half a block above
heightOffset: 0.5
@ -44,4 +44,7 @@ launchpad:
# particles on each side.
particleDensity: 0.5
# Extra data for the particle. Valid values depend on the particle type.
extra: 0
extra: 0
# The amount of ticks between each time the particle(s) should be set again. Depending on the particle, higher
# values will make the particle(s) completely disappear and reappear.
spawnDelay: 20