added some new params to Particle

This commit is contained in:
2024-11-30 00:05:31 +01:00
parent 939867934b
commit be00598efe
8 changed files with 192 additions and 175 deletions
@@ -5,12 +5,17 @@ import com.bytedice.bde_particles.commands.KillAllEmitters
import com.bytedice.bde_particles.commands.ManageEmitters import com.bytedice.bde_particles.commands.ManageEmitters
import com.bytedice.bde_particles.items.ParticleEmitterTool import com.bytedice.bde_particles.items.ParticleEmitterTool
import com.bytedice.bde_particles.particles.* import com.bytedice.bde_particles.particles.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import net.fabricmc.api.EnvType import net.fabricmc.api.EnvType
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
import net.fabricmc.fabric.api.event.player.UseItemCallback import net.fabricmc.fabric.api.event.player.UseItemCallback
import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry
import net.fabricmc.loader.api.FabricLoader import net.fabricmc.loader.api.FabricLoader
import net.minecraft.entity.EntityType import net.minecraft.entity.EntityType
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity
@@ -23,6 +28,7 @@ import net.minecraft.server.world.ServerWorld
import net.minecraft.util.ActionResult import net.minecraft.util.ActionResult
import net.minecraft.util.Hand import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult import net.minecraft.util.TypedActionResult
import net.minecraft.world.GameRules
import net.minecraft.world.World import net.minecraft.world.World
import org.joml.Vector2f import org.joml.Vector2f
import java.util.* import java.util.*
@@ -34,19 +40,9 @@ import java.util.*
// force field "config" option // force field "config" option
// blockCurve array not needing "minecraft:" // blockCurve array not needing "minecraft:"
// Adding a parameter for an initial velocity from an origin point (you currently have to use force fields)
// Parameters // Parameters
// originOffset (Vec3f) // rotWithVel / scaleWithVel (bool)
// velocity based, relative coordinate based, or rotation based // rotates/scales the particle to face the velocity
// originOffsetCurve (curve)
// spawnOffset (Vec3f)
// velRot (bool)
// rotates the particle to face the velocity
// spawnChance (Float)
// shape: spawnOnlyOnEdge (Boolean)
// Custom curve equation command args (parse from strings) // Custom curve equation command args (parse from strings)
// Cylinder and cone force field shape. // Cylinder and cone force field shape.
@@ -59,7 +55,12 @@ import java.util.*
var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray() var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray()
val sessionUuid: UUID = UUID.randomUUID() val SESSION_UUID: UUID = UUID.randomUUID()
val GLOBAL_MAX_PARTICLES = GameRuleRegistry.register(
"GlobalMaxParticles",
GameRules.Category.UPDATES,
GameRuleFactory.createIntRule(3000, 0)
)
class Bde_particles : ModInitializer { class Bde_particles : ModInitializer {
@@ -112,18 +113,23 @@ fun init() {
fun tick(server: MinecraftServer) { fun tick(server: MinecraftServer) {
for (world in server.worlds) { runBlocking {
for (entity in world.iterateEntities()) { server.worlds.map { world ->
if (entity.type == EntityType.BLOCK_DISPLAY) { async {
val blockDisplayEntities = world.iterateEntities()
.filter { it.type == EntityType.BLOCK_DISPLAY }
for (entity in blockDisplayEntities) {
if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID") if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID")
&& !displayEntityContainsTag(entity, sessionUuid.toString())) { && !displayEntityContainsTag(entity, SESSION_UUID.toString())) {
entity.kill() entity.kill()
} }
} }
} }
}.awaitAll()
} }
for (emitter in ALL_PARTICLE_EMITTERS) { for (emitter in ALL_PARTICLE_EMITTERS) {
if (emitter.isDead) { if (emitter.isDead) {
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray() ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
@@ -154,6 +160,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
} }
/*
fun emitterParamsToJson(params: EmitterParams) : Map<String, Any> { // TODO: map to new params fun emitterParamsToJson(params: EmitterParams) : Map<String, Any> { // TODO: map to new params
val allParamsJson: MutableList<Map<String, Any?>> = mutableListOf() val allParamsJson: MutableList<Map<String, Any?>> = mutableListOf()
@@ -169,6 +176,7 @@ fun emitterParamsToJson(params: EmitterParams) : Map<String, Any> { // TODO: map
return emitterParamsJSON return emitterParamsJSON
} }
*/
fun displayEntityContainsTag(entity: BlockDisplayEntity, tag: String) : Boolean { fun displayEntityContainsTag(entity: BlockDisplayEntity, tag: String) : Boolean {
@@ -170,18 +170,9 @@ fun lerp(x: Float, y: Float, t: Float) : Float {
} }
fun interpolateCurve(start: Vector3f, end: Vector3f, t: Float, curve: LerpCurves): Vector3f { fun lerpArray(array: Array<Any>, t: Float, curve: LerpCurves = LerpCurves.Linear) : Any {
val clampedT = t.coerceIn(0.0f, 1.0f) val idx = round(lerp(0.0f, array.lastIndex.toFloat(), t) * curve.function(t)).toInt()
return array[idx]
val curvedT = curve.function(clampedT)
val interpolatedVec = Vector3f(
lerp(start.x, end.x, curvedT),
lerp(start.y, end.y, curvedT),
lerp(start.z, end.z, curvedT)
)
return interpolatedVec
} }
@@ -25,6 +25,37 @@ import java.util.concurrent.CompletableFuture
// TODO: completely redesign // TODO: completely redesign
/*
// spawning
maxCount: Int,
spawnRate: Int,
spawnChance: Float,
spawnDuration: ParamClasses.Duration,
spawnPosOffset: Vector3f,
lifeTime: ParamClasses.PairInt,
shape: SpawningShape,
// init transforms
offset: ParamClasses.PairVec3f,
initRot: ParamClasses.PairVec3f,
rotVel: ParamClasses.PairVec3f,
rotWithVel: Boolean,
initVel: ParamClasses.PairVec3f,
initCenterVel: ParamClasses.PairFloat,
initScale: ParamClasses.PairVec3f,
scaleWithVel: Boolean,
// velocity
forceFields: Array<ForceField>,
constVel: Vector3f,
drag: Float,
minVel: Float,
// curves
offsetCurve: ParamClasses.LerpVal,
rotVelCurve: ParamClasses.LerpVal,
scaleCurve: ParamClasses.LerpVal,
blockCurve: Pair<Array<String>, LerpCurves>,
*/
// ManageEmitters // ManageEmitters
// <create / remove / config / list> // <create / remove / config / list>
// create // create
@@ -4,32 +4,6 @@ import com.bytedice.bde_particles.LerpCurves
import org.joml.Vector3f import org.joml.Vector3f
/**
* EmitterParams defines the parameters for controlling how particles are emitted,
* including settings for looping, spawn frequency, and maximum particle count.
*
* @param maxCount The maximum number of particles that can exist at any time. If set to 0 or below, no particles will spawn.
* @param spawnRate The number of particles to spawn each tick. If set to 0 or below, no particles will spawn.
* @param spawnChance The chance for a particle to spawn. 0.5 means a particle has a 50% chance of spawning. Values are between 0.0 and 1.0.
* @param loopDur The duration, in ticks, that each emission loop lasts. A value of 0 or below makes the loop infinite, ignoring `loopDelay` and `loopCount`.
* @param loopDelay The delay, in ticks, between consecutive loops. Negative values are treated as 0 (no delay).
* @param loopCount The number of times the loop repeats. A value of 0 results in a single burst of particles. Values below 0 make the loop repeat indefinitely.
* @param shape The spawning shape for the particles, such as `SpawningShape.Sphere()`.
* @param initRot Specifies the initial random rotation range for the particles, in degrees, for the X, Y, and Z axes.
* @param rotVel Specifies the random range for rotational velocity, in degrees per tick, for the X, Y, and Z axes.
* @param rotTowardVel If true, particles will face the direction of velocity. It uses `initRot` as an offset rotation and ignores `rotVel`.
* @param initVel Specifies the random range for the initial velocity, in units per tick, for the X, Y, and Z axes.
* @param initScale Defines the random range for the initial particle size.
* @param scaleTowardVel If true, particles will scale to the direction of velocity.
* @param forceFields An array of force fields applied to particles, affecting their motion during their lifetime.
* @param constVel A constant velocity vector applied to particles each tick, useful for effects like gravity.
* @param drag A drag coefficient that slows down particle velocity over time. Calculated as `velocity * (1.0 - drag)`.
* @param minVel The minimum speed a particle must maintain. Particles slower than this value are stopped. Values <= 0 disable this check.
* @param lifeTime Specifies the range for particle lifetime, in ticks. Values below 1 prevent particles from spawning.
* @param rotVelCurve A curve defining how rotational velocity evolves during the particle's lifetime. Multiplies the `rotVel` values.
* @param scaleCurve A curve defining how the particle's size evolves over its lifetime. Multiplies the `initScale` values.
* @param blockCurve Specifies an array of block names that the particle transitions through during its lifetime, combined with a curve to control transitions. Defaults to "minecraft:air" if empty.
*/
data class EmitterParams ( data class EmitterParams (
// spawning // spawning
var maxCount: Int, var maxCount: Int,
@@ -68,7 +42,7 @@ data class EmitterParams (
spawnDuration = ParamClasses.Duration.SingleBurst(25), spawnDuration = ParamClasses.Duration.SingleBurst(25),
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f), spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
lifeTime = ParamClasses.PairInt(25, 40), lifeTime = ParamClasses.PairInt(25, 40),
shape = SpawningShape.Circle(3.0f), shape = SpawningShape.Circle(3.0f, false),
offset = ParamClasses.PairVec3f.Uniform(-0.5f, -0.5f), offset = ParamClasses.PairVec3f.Uniform(-0.5f, -0.5f),
initRot = ParamClasses.PairVec3f.Null, initRot = ParamClasses.PairVec3f.Null,
rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f), rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f),
@@ -9,61 +9,77 @@ import org.joml.Vector4f
import kotlin.math.* import kotlin.math.*
// TODO: consider moving most params to emitter (such as shape, and forceFields) to store less data // TODO: add logic for new params (goes for ParticleEmitter too)
/* Changed or new params (haven't been implemented yet)
rotWithVel: Boolean (NEW)
scaleWithVel: Boolean (NEW)
*/
class Particle(private val emitterParams: EmitterParams) { class Particle(private val emitterParams: EmitterParams, private var entityPos: Vec3d, private val entityRot: Vector2f) {
private val initOffset = Vector3f(-0.5f, -0.5f, -0.5f) private var pos = Vector3f(0.0f, 0.0f, 0.0f)
private var offset = Vector3f(0.0f, 0.0f, 0.0f) private var blockDisplay: DisplayEntity = DisplayEntity(DisplayEntityProperties())
private var pos = Vec3d(0.0, 0.0, 0.0) private var timeAlive = 0
private var entityRot = Vector2f(0.0f, 0.0f) private var isInit = false
var isDead = false
private var rot = emitterParams.initRot.randomize() // params
private val rotVel = emitterParams.rotVel.randomize() private val originOffset = when (val offset = emitterParams.offset) {
private var vel = emitterParams.initVel.randomize() is ParamClasses.PairVec3f.Uniform -> offset.randomize()
is ParamClasses.PairVec3f.NonUniform -> offset.randomize()
private val scaleAny: Any = when (emitterParams.initScale) { ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
is ParamClasses.RandScale.Uniform -> { (emitterParams.initScale as ParamClasses.RandScale.Uniform).randomize() } }
is ParamClasses.RandScale.NonUniform -> { (emitterParams.initScale as ParamClasses.RandScale.NonUniform).randomize() }
private var rot = when (val initRot = emitterParams.initRot) {
is ParamClasses.PairVec3f.Uniform -> initRot.randomize()
is ParamClasses.PairVec3f.NonUniform -> initRot.randomize()
ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
}
private var rotVel = when (val initRotVel = emitterParams.rotVel) {
is ParamClasses.PairVec3f.Uniform -> initRotVel.randomize()
is ParamClasses.PairVec3f.NonUniform -> initRotVel.randomize()
ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
}
private var vel = when (val initVel = emitterParams.initVel) {
is ParamClasses.PairVec3f.Uniform -> initVel.randomize()
is ParamClasses.PairVec3f.NonUniform -> initVel.randomize()
ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
}
private var scale = when (val initScale = emitterParams.initScale) {
is ParamClasses.PairVec3f.Uniform -> initScale.randomize()
is ParamClasses.PairVec3f.NonUniform -> initScale.randomize()
ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
} }
private var scale = if (scaleAny is Vector3f) { scaleAny } else { Vector3f(scaleAny as Float, scaleAny, scaleAny) }
private var blockDisplay: DisplayEntity? = null
private var lifeTime = emitterParams.lifeTime.randomize() private var lifeTime = emitterParams.lifeTime.randomize()
var isDead = false
private var timeAlive = 0
private var isInit = false fun init() {
val so = emitterParams.spawnPosOffset
entityPos = entityPos.add(so.x.toDouble(), so.y.toDouble(), so.z.toDouble())
fun init(pos: Vec3d, rot: Vector2f) { // TODO: replace initOffset with originOffset param
this.pos = pos
this.entityRot = rot
val shape = emitterParams.shape val shape = emitterParams.shape
val newSpawnPos: Vector3f val newSpawnPos = shape.random(shape)
when (shape) { pos = newSpawnPos
is SpawningShape.Circle -> { val posInCircle = randomInCircle(shape.radius); newSpawnPos = Vector3f(posInCircle.x, 0.0f, posInCircle.y) }
is SpawningShape.Sphere -> { newSpawnPos = randomInSphere(shape.radius) }
is SpawningShape.Rect -> { val posInRect = randomInRect(shape.size); newSpawnPos = Vector3f(posInRect.x, 0.0f, posInRect.y) }
is SpawningShape.Cube -> { newSpawnPos = randomInCube(shape.size) }
is SpawningShape.Point -> { newSpawnPos = Vector3f(0.0f, 0.0f, 0.0f) }
}
offset = newSpawnPos val dirFromCenter = entityPos.subtract(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()).normalize()
val forceFromCenter = dirFromCenter.multiply(emitterParams.initCenterVel.randomize().toDouble())
vel.add(forceFromCenter.x.toFloat(), forceFromCenter.y.toFloat(), forceFromCenter.z.toFloat())
val (quatRot, newOffset) = calcTransformOffset(this.rot, initOffset, scale) val (quatRot, newPos) = calcTransformOffset(this.rot, originOffset, scale)
val properties = DisplayEntityProperties( val properties = DisplayEntityProperties(
pos = this.pos, pos = entityPos,
rot = rot, rot = entityRot,
blockType = emitterParams.blockCurve.first[0], // TODO: curve this blockType = lerpArray(emitterParams.blockCurve.first as Array<Any>, 0.0f, emitterParams.blockCurve.second) as String,
translation = newOffset.add(offset), translation = newPos.add(pos),
leftRotation = quatRot, leftRotation = quatRot,
scale = scale, scale = scale,
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString()) tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString())
) )
blockDisplay = DisplayEntity(properties) blockDisplay = DisplayEntity(properties)
@@ -73,7 +89,7 @@ class Particle(private val emitterParams: EmitterParams) {
fun spawn(world: ServerWorld) { fun spawn(world: ServerWorld) {
if (isInit) { blockDisplay?.spawn(world) } if (isInit) { blockDisplay.spawn(world) }
else { error("Particle is not initialized before being spawned. Please use Particle.init() to initialize it.") } else { error("Particle is not initialized before being spawned. Please use Particle.init() to initialize it.") }
} }
@@ -83,9 +99,9 @@ class Particle(private val emitterParams: EmitterParams) {
val newProperties = calcNewProperties() val newProperties = calcNewProperties()
blockDisplay?.updateProperties(newProperties) blockDisplay.updateProperties(newProperties)
if (timeAlive > lifeTime) { isDead = true; blockDisplay?.kill() } if (timeAlive > lifeTime) { isDead = true; blockDisplay.kill() }
timeAlive += 1 timeAlive += 1
} }
@@ -105,16 +121,16 @@ class Particle(private val emitterParams: EmitterParams) {
private fun calcNewProperties() : DisplayEntityProperties { private fun calcNewProperties() : DisplayEntityProperties {
val timeAliveClamped = (timeAlive.toFloat() / lifeTime.toFloat()).coerceIn(0.0f, 1.0f) val timeAliveClamped = (timeAlive.toFloat() / lifeTime.toFloat()).coerceIn(0.0f, 1.0f)
val newBlock = calcBlockCurve(timeAliveClamped) val newBlock = lerpArray(emitterParams.blockCurve.first as Array<Any>, timeAliveClamped, emitterParams.blockCurve.second) as String
val (newVel, newOffset) = calcOffset() val (newVel, newPos) = calcPos()
val newRot = calcRot(timeAliveClamped) val newRot = calcRot(timeAliveClamped)
val newScale = calcSize(timeAliveClamped) val newScale = calcScale(timeAliveClamped)
val newOriginOffset = Vector3f(originOffset.mul(emitterParams.offsetCurve.lerpToVector3f(timeAliveClamped)))
vel = newVel vel = newVel
offset = newOffset pos = newPos
rot = newRot rot = newRot
for (forceField in emitterParams.forceFields) { for (forceField in emitterParams.forceFields) {
@@ -122,35 +138,29 @@ class Particle(private val emitterParams: EmitterParams) {
else if (forceField.shape is ForceFieldShape.Cube) { vel.add(cubeSdfVel(forceField)) } else if (forceField.shape is ForceFieldShape.Cube) { vel.add(cubeSdfVel(forceField)) }
} }
val (quatRot, transformOffset) = calcTransformOffset(newRot, initOffset, newScale) val (quatRot, transformOffset) = calcTransformOffset(newRot, originOffset, newScale)
val combinedOffset = transformOffset.add(newOffset) val combinedOffset = transformOffset.add(newPos)
val newProperties = DisplayEntityProperties( val newProperties = DisplayEntityProperties(
pos = pos, pos = entityPos,
blockType = newBlock, blockType = newBlock,
translation = combinedOffset, translation = combinedOffset,
leftRotation = quatRot, leftRotation = quatRot,
scale = newScale, scale = newScale,
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString()) tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString())
) )
return newProperties return newProperties
} }
private fun calcBlockCurve(t: Float) : String {
val newBlockIdx = round(lerp(0.0f, emitterParams.blockCurve.first.lastIndex.toFloat(), t)).toInt()
return emitterParams.blockCurve.first[newBlockIdx]
}
private fun sphereSdfVel(forceField: ForceField) : Vector3f { private fun sphereSdfVel(forceField: ForceField) : Vector3f {
if (forceField.shape !is ForceFieldShape.Sphere) { return Vector3f(0.0f, 0.0f, 0.0f) } if (forceField.shape !is ForceFieldShape.Sphere) { return Vector3f(0.0f, 0.0f, 0.0f) }
val shape = forceField.shape val shape = forceField.shape
val sdfVal = sdfSphere(forceField.pos, shape.radius, offset) val sdfVal = sdfSphere(forceField.pos, shape.radius, pos)
val normalizedSdf = normalizeSdf(sdfVal, shape.radius) val normalizedSdf = normalizeSdf(sdfVal, shape.radius)
val velDir = Vector3f(offset).sub(forceField.pos).normalize() val velDir = Vector3f(pos).sub(forceField.pos).normalize()
val velMul = if (sdfVal > 0) { 0.0f } val velMul = if (sdfVal > 0) { 0.0f }
else { lerp(shape.force.second, shape.force.second, 1 - normalizedSdf) } else { lerp(shape.force.second, shape.force.second, 1 - normalizedSdf) }
@@ -162,7 +172,7 @@ class Particle(private val emitterParams: EmitterParams) {
if (forceField.shape !is ForceFieldShape.Cube) { return Vector3f(0.0f, 0.0f, 0.0f) } if (forceField.shape !is ForceFieldShape.Cube) { return Vector3f(0.0f, 0.0f, 0.0f) }
val shape = forceField.shape val shape = forceField.shape
val sdfVal = sdfCube(forceField.pos, shape.size, offset) val sdfVal = sdfCube(forceField.pos, shape.size, pos)
val velDir = if (sdfVal < 0) { shape.dir.mul(shape.force) } val velDir = if (sdfVal < 0) { shape.dir.mul(shape.force) }
else { Vector3f(0.0f, 0.0f, 0.0f) } else { Vector3f(0.0f, 0.0f, 0.0f) }
@@ -181,7 +191,7 @@ class Particle(private val emitterParams: EmitterParams) {
} }
private fun calcOffset() : Pair<Vector3f, Vector3f> { private fun calcPos() : Pair<Vector3f, Vector3f> {
var vel = Vector3f(vel) var vel = Vector3f(vel)
vel = vel.add(emitterParams.constVel) vel = vel.add(emitterParams.constVel)
vel = vel.mul(1.0f - emitterParams.drag) vel = vel.mul(1.0f - emitterParams.drag)
@@ -190,13 +200,13 @@ class Particle(private val emitterParams: EmitterParams) {
if (abs(vel.y) < emitterParams.minVel) { vel.y = 0.0f } if (abs(vel.y) < emitterParams.minVel) { vel.y = 0.0f }
if (abs(vel.z) < emitterParams.minVel) { vel.z = 0.0f } if (abs(vel.z) < emitterParams.minVel) { vel.z = 0.0f }
var newOffset = Vector3f(offset) var newPos = Vector3f(pos)
newOffset = newOffset.add(vel) newPos = newPos.add(vel)
return Pair(vel, newOffset) return Pair(vel, newPos)
} }
private fun calcSize(t: Float) : Vector3f { private fun calcScale(t: Float) : Vector3f {
val newScale = Vector3f(scale) val newScale = Vector3f(scale)
newScale.mul(emitterParams.scaleCurve.lerpToVector3f(t)) newScale.mul(emitterParams.scaleCurve.lerpToVector3f(t))
return scale return scale
@@ -204,6 +214,6 @@ class Particle(private val emitterParams: EmitterParams) {
fun kill() { fun kill() {
if (!isDead) { blockDisplay?.kill() } if (!isDead) { blockDisplay.kill() }
} }
} }
@@ -1,19 +1,18 @@
package com.bytedice.bde_particles.particles package com.bytedice.bde_particles.particles
import com.bytedice.bde_particles.randomFloatBetween
import net.minecraft.server.world.ServerWorld import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3d
import org.joml.Vector2f import org.joml.Vector2f
import org.joml.Vector3f
class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private val world: ServerWorld, private val params: EmitterParams) { class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private val world: ServerWorld, private val params: EmitterParams) {
private var allParticles: Array<Particle> = emptyArray() private var allParticles: Array<Particle> = emptyArray()
private var timeAlive = 0 private var timeAlive = 0
private var loopCount = 0 private var timePaused = 0
private var loopDelay = 0 private var timesLooped = 0
private var loopDur = 0 private var isPaused = false
private var loopDelayActive = false
private var isTicking = true private var isTicking = true
@@ -33,13 +32,16 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private
private fun addParticle() { private fun addParticle() {
if (loopDelayActive) { return } if (isPaused) { return }
repeat(params.spawnRate) { repeat(params.spawnRate) {
if (allParticles.size >= params.maxCount) { return } if (allParticles.size >= params.maxCount) { return }
val particle = Particle(params) val randomSpawnChance = randomFloatBetween(0.0f, params.spawnChance.coerceIn(0.0f, 1.0f))
particle.init(pos, rot) if (randomSpawnChance > params.spawnChance) { return }
val particle = Particle(params, pos, rot)
particle.init()
particle.spawn(world) particle.spawn(world)
allParticles += particle allParticles += particle
@@ -48,46 +50,32 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private
private fun count() { private fun count() {
// this is the only section I have comments if (params.spawnDuration is ParamClasses.Duration.SingleBurst) {
// because im so damn good at cooking spaghetti val loopDur = (params.spawnDuration as ParamClasses.Duration.SingleBurst).loopDur
if (timeAlive >= loopDur) { isTicking = false; return }
// if max duration is 0 or less, skip counting, its infinite
if (params.loopDur <= 0) { return }
// if the duration is greater than max duration, add 1 loopCount
// return if max loopCount is 0
// if the delay is greater than 0 then enable it
if (loopDur >= params.loopDur) {
if (loopCount == 0) {
isTicking = false
return
}
loopCount += 1
loopDur = 0
if (params.loopDelay > 0) { loopDelayActive = true }
}
// if the delay is greater than max delay, disable the delay
else if (loopDelay >= params.loopDelay) {
loopDelay = 0
loopDelayActive = false
}
// if loopCount is greater than max loopCount then stop immediately
// if max loopCount is below 0 then don't do anything, loop infinitely
if (loopCount > 0 && loopCount >= params.loopCount) {
isTicking = false
return
}
// if delay isn't active, add 1 to duration
// if delay is active, add 1 to delay
if (!loopDelayActive) { loopDur += 1 }
else { loopDelay += 1 }
timeAlive += 1 timeAlive += 1
} }
else if (params.spawnDuration is ParamClasses.Duration.MultiBurst) {
val loopDur = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDur
val loopDelay = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDelay
val loopCount = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopCount
if (timeAlive >= loopDur && timesLooped >= loopCount) { isTicking = false; return }
if (timePaused >= loopDelay) { isPaused = false; timePaused = 0 }
if (timeAlive >= loopDur && loopDelay > 0) { isPaused = true; timeAlive = 0 }
if (!isPaused) { timeAlive += 1 }
else { timePaused += 1 }
}
else if (params.spawnDuration is ParamClasses.Duration.InfiniteLoop) {
return
}
}
fun kill() { fun kill() {
for (particle in allParticles) { for (particle in allParticles) {
@@ -97,4 +85,7 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private
isTicking = false isTicking = false
isDead = true isDead = true
} }
fun stopTicking() {
isTicking = false
}
} }
@@ -1,5 +1,3 @@
@file:Suppress("UNCHECKED_CAST")
package com.bytedice.bde_particles.particles package com.bytedice.bde_particles.particles
@@ -10,9 +8,13 @@ val forbiddenIds = arrayOf("", "NULL")
fun addToRegister(id: String, params: EmitterParams) : Pair<String, Boolean> { fun addToRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
val newId = replaceForbiddenChars(id.replace(" ", "_")) val newId = replaceForbiddenChars(id.replace(" ", "_"))
if (idRegister.containsKey(newId)) { return Pair(newId, false) } if (idRegister.containsKey(newId)) {
println("BPS - Failed to register Emitter ID \"$newId\" as it already exists!")
return Pair(newId, false)
}
idRegister[id] = params idRegister[id] = params
println("BPS - Registered Emitter ID \"$newId\".")
return Pair(newId, true) return Pair(newId, true)
} }
@@ -7,8 +7,8 @@ import org.joml.Vector3f
import kotlin.math.* import kotlin.math.*
import kotlin.random.Random import kotlin.random.Random
sealed class SpawningShape { sealed class SpawningShape() {
class Circle(val radius: Float = 1.0f) : SpawningShape() { class Circle(private val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() {
fun randomWithin(): Vector3f { fun randomWithin(): Vector3f {
val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f)) val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f))
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2) val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
@@ -23,7 +23,7 @@ sealed class SpawningShape {
} }
} }
class Rect(val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape() { class Rect(private val size: Vector2f, val spawnOnEdge: Boolean) : SpawningShape() {
fun randomWithin(): Vector3f { fun randomWithin(): Vector3f {
return Vector3f(randomFloatBetween(0.0f, size.x), 0.0f, randomFloatBetween(0.0f, size.y)) return Vector3f(randomFloatBetween(0.0f, size.x), 0.0f, randomFloatBetween(0.0f, size.y))
} }
@@ -41,7 +41,7 @@ sealed class SpawningShape {
} }
} }
class Cube(val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape() { class Cube(private val size: Vector3f, val spawnOnEdge: Boolean) : SpawningShape() {
fun randomWithin(): Vector3f { fun randomWithin(): Vector3f {
return Vector3f( return Vector3f(
randomFloatBetween(0.0f, size.x), randomFloatBetween(0.0f, size.x),
@@ -73,7 +73,7 @@ sealed class SpawningShape {
} }
} }
class Sphere(val radius: Float = 1.0f) : SpawningShape() { class Sphere(private val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() {
fun randomWithin(): Vector3f { fun randomWithin(): Vector3f {
val randRadius = radius * Random.nextDouble().pow(1.0 / 3.0) val randRadius = radius * Random.nextDouble().pow(1.0 / 3.0)
@@ -103,6 +103,16 @@ sealed class SpawningShape {
} }
data object Point : SpawningShape() data object Point : SpawningShape()
fun random(shape: SpawningShape) : Vector3f {
return when (shape) {
is Circle -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() }
is Sphere -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() }
is Rect -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() }
is Cube -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() }
is Point -> Vector3f(0.0f, 0.0f, 0.0f)
}
}
} }