added some new params to Particle
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user