added ticking logic to particles

This commit is contained in:
2024-11-16 15:07:04 +01:00
parent 15200edda9
commit 112ed55b2a
8 changed files with 151 additions and 59 deletions
@@ -11,17 +11,15 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
import net.fabricmc.fabric.api.event.player.UseItemCallback
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
// const val ALL_PARTICLE_EMITTERS: Array<> // ParticleEmitter
var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray()
data class Config(
val cooldown: Int = 1
@@ -56,12 +54,17 @@ class Bde_particles : ModInitializer {
fun init() {
addToParticleRegister("DEBUG", ParticleEmitterParams())
addToParticleRegister("DEBUG", ParticleEmitterParams(loopDur = 20))
}
fun tick() {
for (emitter in ALL_PARTICLE_EMITTERS) {
if (emitter.isDead) {
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
}
else { emitter.tick() }
}
}
@@ -80,7 +83,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
}
val emitter = ParticleEmitter(hitResult.pos, world, emitterParams)
emitter.tick()
ALL_PARTICLE_EMITTERS += emitter
return TypedActionResult(ActionResult.PASS, handItem)
}
@@ -5,15 +5,17 @@ import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtFloat
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtString
import net.minecraft.server.world.ServerWorld
class DisplayEntity(val properties: DisplayEntityProperties?) {
private var hasSpawned = false
private var entity: BlockDisplayEntity? = null
fun spawn(world: ServerWorld) : BlockDisplayEntity? {
if (this.hasSpawned || properties == null) { return null }
if (hasSpawned || properties == null) { return null }
val e = updateProperties(world, this.properties)
val e = updateProperties(world, properties)
world.spawnEntity(e)
this.hasSpawned = true
@@ -23,8 +25,18 @@ class DisplayEntity(val properties: DisplayEntityProperties?) {
fun updateProperties(world: ServerWorld, properties: DisplayEntityProperties) : BlockDisplayEntity {
val e = BlockDisplayEntity(EntityType.BLOCK_DISPLAY, world)
// TODO: add tag nbt and kill all particles when server starts
val nbt = NbtCompound().apply {
val tagList = NbtList()
properties.tags.forEach { tag ->
tagList.add(NbtString.of(tag))
}
put("Tags", tagList)
put("block_state", NbtCompound().apply { putString("Name", properties.blockType) })
put("transformation", NbtCompound().apply {
val offsetList = NbtList().apply {
add(NbtFloat.of(properties.translation.x))
@@ -60,6 +72,11 @@ class DisplayEntity(val properties: DisplayEntityProperties?) {
e.refreshPositionAndAngles(properties.pos.x, properties.pos.y, properties.pos.z, properties.rot.x, properties.rot.y)
entity = e
return e
}
fun kill() {
entity?.kill()
}
}
@@ -9,7 +9,8 @@ data class DisplayEntityProperties (
val pos: Vec3d = Vec3d(0.0, 0.0, 0.0),
val rot: Vector2f = Vector2f(0.0f, 0.0f),
val blockType: String = "minecraft:purple_concrete",
val blockType: String = "minecraft:purple_concrete",
val tags: Array<String> = emptyArray(),
val translation: Vector3f = Vector3f(-0.5f, -0.5f, -0.5f),
val leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
@@ -67,24 +67,20 @@ fun quatToEuler(quat: Vector4f): Vector3f {
val z = quat.z
val w = quat.w
// Roll (X-axis rotation)
val sinr_cosp = 2 * (w * x + y * z)
val cosr_cosp = 1 - 2 * (x * x + y * y)
val roll = atan2(sinr_cosp, cosr_cosp)
val sinRCosP = 2 * (w * x + y * z)
val cosRCosP = 1 - 2 * (x * x + y * y)
val roll = atan2(sinRCosP, cosRCosP)
// Pitch (Y-axis rotation)
val sinp = 2 * (w * y - z * x)
val pitch = if (kotlin.math.abs(sinp) >= 1) {
// Use 90 degrees if out of range
kotlin.math.sign(sinp) * (Math.PI.toFloat() / 2)
val sinP = 2 * (w * y - z * x)
val pitch = if (abs(sinP) >= 1) {
sign(sinP) * (Math.PI.toFloat() / 2)
} else {
asin(sinp)
asin(sinP)
}
// Yaw (Z-axis rotation)
val siny_cosp = 2 * (w * z + x * y)
val cosy_cosp = 1 - 2 * (y * y + z * z)
val yaw = atan2(siny_cosp, cosy_cosp)
val sinYCosP = 2 * (w * z + x * y)
val cosYCosP = 1 - 2 * (y * y + z * z)
val yaw = atan2(sinYCosP, cosYCosP)
return Vector3f(roll, pitch, yaw)
}
@@ -146,3 +142,8 @@ fun transformOffsetByQuat(offset: Vector3f, rotation: Vector4f): Vector3f {
fun transformOffsetByScale(offset: Vector3f, scale: Vector3f): Vector3f {
return offset.mul(scale)
}
fun lerp(x: Float, y: Float, t: Float) : Float {
return x + (y - x) * t
}
@@ -0,0 +1,10 @@
package com.bytedice.bde_particles.particle
data class ForceField (
val x: Float = 0.0f,
val y: Float = 0.0f,
val z: Float = 0.0f,
val minForce: Float = 0.0f,
val maxForce: Float = 0.0f,
val radius: Float = -1.0f
)
@@ -6,57 +6,89 @@ import com.bytedice.bde_particles.math.*
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.Vec3d
import org.joml.Vector3f
import kotlin.math.round
class Particle(particleParams: ParticleParams) {
val rot = Vector3f(
class Particle(private val particleParams: ParticleParams) {
private val rot = Vector3f(
randomFloatBetween(particleParams.rotRandom.first.x, particleParams.rotRandom.second.x),
randomFloatBetween(particleParams.rotRandom.first.y, particleParams.rotRandom.second.x),
randomFloatBetween(particleParams.rotRandom.first.z, particleParams.rotRandom.second.z)
)
val rotVel = Vector3f(
private val rotVel = Vector3f(
randomFloatBetween(particleParams.rotVelRandom.first.x, particleParams.rotVelRandom.second.x),
randomFloatBetween(particleParams.rotVelRandom.first.y, particleParams.rotVelRandom.second.x),
randomFloatBetween(particleParams.rotVelRandom.first.z, particleParams.rotVelRandom.second.z)
)
val scale = Vector3f(
private val scale = Vector3f(
randomFloatBetween(particleParams.sizeRandom.first.x, particleParams.sizeRandom.second.x),
randomFloatBetween(particleParams.sizeRandom.first.y, particleParams.sizeRandom.second.x),
randomFloatBetween(particleParams.sizeRandom.first.z, particleParams.sizeRandom.second.z)
)
var blockDisplay: DisplayEntity? = null
val params = particleParams
private var blockDisplay: DisplayEntity? = null
var isInit = false
var isDead = false
private var timeAlive = 0
private var isInit = false
fun init(pos: Vec3d) {
val offset = Vector3f(-0.5f, -0.5f, -0.5f)
val quatRot = eulerToQuat(this.rot)
val quatRot = eulerToQuat(rot)
val scaleOffset = transformOffsetByScale(offset, this.scale)
val scaleOffset = transformOffsetByScale(offset, scale)
val rotOffset = transformOffsetByQuat(scaleOffset, quatRot)
val properties = DisplayEntityProperties(
pos = pos,
blockType = this.params.blockCurve[0],
blockType = particleParams.blockCurve[0],
translation = rotOffset,
leftRotation = quatRot,
scale = this.scale
scale = scale,
tags = arrayOf("BPS_Particle")
)
this.blockDisplay = DisplayEntity(properties)
blockDisplay = DisplayEntity(properties)
this.isInit = true
isInit = true
}
fun spawn(world: ServerWorld) {
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.") }
}
fun tick() {
fun tick(world: ServerWorld) {
if (!isInit) { error("Particle is not initialized before being ticked. Please use Particle.init() to initialize it.") }
val properties = blockDisplay?.properties ?: return
val newProperties = calculateNewProperties(properties, particleParams)
blockDisplay?.updateProperties(world, newProperties)
if (timeAlive > particleParams.lifeTime) { isDead = true; blockDisplay?.kill() }
timeAlive += 1
}
private fun calculateNewProperties(propetries: DisplayEntityProperties, params: ParticleParams) : DisplayEntityProperties {
val newBlockIdx = round(lerp(
0.0f,
params.blockCurve.lastIndex.toFloat(),
(timeAlive.toFloat() / params.lifeTime.toFloat()).coerceIn(0.0f, 1.0f)
)).toInt()
val newBlock = params.blockCurve[newBlockIdx]
val newProperties = DisplayEntityProperties(
blockType = newBlock
)
return newProperties
}
}
@@ -4,18 +4,54 @@ import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.Vec3d
class ParticleEmitter(emitterPos: Vec3d, emitterWorld: ServerWorld, emitterParams: ParticleEmitterParams) {
val world = emitterWorld
val pos = emitterPos
val params = emitterParams
val allParticles: Array<Particle> = emptyArray()
val timeAlive = 0
private val world = emitterWorld
private val pos = emitterPos
private val params = emitterParams
private var allParticles: Array<Particle> = emptyArray()
private var timeAlive = 0
private var loopCount = 0
private var loopDelay = 0
private var loopDur = 0
private var loopDelayActive = false
var isDead = false
fun tick() {
repeat(this.params.spawnsPerTick) {
val particle = Particle(ParticleParams())
particle.init(this.pos)
particle.spawn(world)
count()
if (!loopDelayActive) {
repeat(this.params.spawnsPerTick) {
val particle = Particle(ParticleParams())
particle.init(this.pos)
particle.spawn(world)
allParticles += particle
}
}
for (particle in allParticles) {
if (particle.isDead) { allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray() }
else { particle.tick(world) }
}
}
private fun count() {
if (loopDur >= params.loopDur) {
loopCount += 1
loopDur = 0
loopDelayActive = true
if (!params.loop) { isDead = true }
}
else if (loopDelay >= params.loopDelay) {
loopDelay = 0
loopDelayActive = false
}
if (!loopDelayActive) { loopDur += 1 }
else { loopDelay += 1 }
timeAlive += 1
}
}
@@ -5,24 +5,16 @@ import org.joml.Vector3f
data class ParticleParams (
//val shape: Shape, // add later, start with single point
val blockCurve: Array<String> = arrayOf("minecraft:purple_concrete"),
val blockCurve: Array<String> = arrayOf("minecraft:purple_concrete", "minecraft:purple_stained_glass"),
val rotRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(360.0f, 360.0f, 360.0f)),
val rotVelRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(5.0f, 5.0f, 5.0f)),
val rotVelCurve: Array<Vector3f> = arrayOf(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f)),
val sizeRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(0.5f, 0.5f, 0.5f), Vector3f(1.0f, 1.0f, 1.0f)),
val sizeCurve: Array<Vector3f> = arrayOf(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f)),
val velRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(5.0f, 5.0f, 5.0f)),
val forceFields: ForceField = ForceField(),
val gravity: Vec3d = Vec3d(0.0, -9.0, 0.0),
val drag: Float = 0.2f,
val minVel: Float = 0.003f
)
data class ForceField (
val x: Float = 0.0f,
val y: Float = 0.0f,
val z: Float = 0.0f,
val minForce: Float = 0.0f,
val maxForce: Float = 0.0f,
val radius: Float = -1.0f,
val minVel: Float = 0.003f,
val lifeTime: Int = 20
)