diff --git a/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt b/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt index 3276ce8..5429fe6 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt @@ -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 = 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) } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/DisplayEntity.kt b/src/main/kotlin/com/bytedice/bde_particles/DisplayEntity.kt index 15b5c63..0cd54a8 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/DisplayEntity.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/DisplayEntity.kt @@ -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() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/DisplayEntityProperties.kt b/src/main/kotlin/com/bytedice/bde_particles/DisplayEntityProperties.kt index f0d0187..857dd20 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/DisplayEntityProperties.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/DisplayEntityProperties.kt @@ -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 = emptyArray(), val translation: Vector3f = Vector3f(-0.5f, -0.5f, -0.5f), val leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f), diff --git a/src/main/kotlin/com/bytedice/bde_particles/math/main.kt b/src/main/kotlin/com/bytedice/bde_particles/math/main.kt index 59990ac..f063da2 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/math/main.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/math/main.kt @@ -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 } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particle/ForceFields.kt b/src/main/kotlin/com/bytedice/bde_particles/particle/ForceFields.kt new file mode 100644 index 0000000..c5c4842 --- /dev/null +++ b/src/main/kotlin/com/bytedice/bde_particles/particle/ForceFields.kt @@ -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 +) \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particle/Particle.kt b/src/main/kotlin/com/bytedice/bde_particles/particle/Particle.kt index 6f45f99..f9f5e26 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particle/Particle.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particle/Particle.kt @@ -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 } } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleEmitter.kt b/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleEmitter.kt index fc79465..156a6c0 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleEmitter.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleEmitter.kt @@ -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 = emptyArray() - val timeAlive = 0 + private val world = emitterWorld + private val pos = emitterPos + private val params = emitterParams + + private var allParticles: Array = 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 + } } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleParams.kt b/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleParams.kt index 12f7d1d..4862590 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleParams.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particle/ParticleParams.kt @@ -5,24 +5,16 @@ import org.joml.Vector3f data class ParticleParams ( //val shape: Shape, // add later, start with single point - val blockCurve: Array = arrayOf("minecraft:purple_concrete"), + val blockCurve: Array = arrayOf("minecraft:purple_concrete", "minecraft:purple_stained_glass"), val rotRandom: Pair = Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(360.0f, 360.0f, 360.0f)), val rotVelRandom: Pair = Pair(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(5.0f, 5.0f, 5.0f)), val rotVelCurve: Array = arrayOf(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f)), val sizeRandom: Pair = Pair(Vector3f(0.5f, 0.5f, 0.5f), Vector3f(1.0f, 1.0f, 1.0f)), val sizeCurve: Array = arrayOf(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f)), + val velRandom: Pair = 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 ) \ No newline at end of file