added ticking logic to particles
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -145,4 +141,9 @@ 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
|
||||
)
|
||||
Reference in New Issue
Block a user