added async particle ticking and made new params fully working.

This commit is contained in:
2024-12-01 15:13:41 +01:00
parent 2edb23e9ec
commit c5cc910be3
6 changed files with 172 additions and 93 deletions
@@ -36,25 +36,16 @@ import java.util.*
// TODO:
// Allowing the use of regular Minecraft particles instead of just BDEs.
// not implementing in v1.0. Maybe in v2.0.
// Better command auto-completion. (and change names of args)
// force field "config" option
// Parameters
// rotWithVel / scaleWithVel (bool)
// rotates/scales the particle to face the velocity
// Custom curve equation command args (parse from strings)
// Cylinder and cone force field shape.
// Better particle performance.
// Emitter groups (multiple emitters for EmitterTool)
// Private functions/classes
// Debug tools
// RenderParticleDebug gamerule
// render emitter origin
// render particle origin
// name-tag particle with its data
// render particle velocity direction
var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray()
@@ -119,6 +110,8 @@ class Bde_particles : ModInitializer {
fun init() {
addToRegister("DEFAULT", EmitterParams.DEFAULT)
addToRegister("DEBUG", EmitterParams.DEBUG)
addToRegister("STRESS_TEST", EmitterParams.STRESS_TEST)
}
@@ -139,12 +132,18 @@ fun tick(server: MinecraftServer) {
}.awaitAll()
}
for (emitter in ALL_PARTICLE_EMITTERS) {
if (emitter.isDead) {
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
}
else { emitter.tick() }
runBlocking {
ALL_PARTICLE_EMITTERS.map { emitter ->
async {
if (emitter.isDead) {
synchronized(ALL_PARTICLE_EMITTERS) {
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
}
} else {
emitter.tick()
}
}
}.awaitAll()
}
}
@@ -85,7 +85,7 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
put("right_rotation", rightRotList)
})
put("view_range", NbtCompound().apply { properties.viewRange })
putFloat("view_range", properties.viewRange)
if (properties.name != null) {
putString("CustomName", properties.name)
@@ -83,19 +83,10 @@ fun randomIntBetween(min: Int, max: Int) : Int {
}
fun randomBetweenVector3f(min: Vector3f, max: Vector3f) : Vector3f {
return Vector3f(
randomFloatBetween(min.x, max.x),
randomFloatBetween(min.y, max.y),
randomFloatBetween(min.z, max.z)
)
}
fun eulerToQuat(euler: Vector3f): Vector4f {
val roll = euler.x
val pitch = euler.y
val yaw = euler.z
val roll = euler.x * (Math.PI / 180).toFloat()
val pitch = euler.y * (Math.PI / 180).toFloat()
val yaw = euler.z * (Math.PI / 180).toFloat()
val cy = cos(yaw * 0.5)
val sy = sin(yaw * 0.5)
@@ -217,21 +208,3 @@ fun sdfCube(pos: Vector3f, size: Vector3f, objectPos: Vector3f): Float {
fun normalizeSdf(sdf: Float, radius: Float): Float {
return if (sdf < 0) (sdf + radius) / radius else 0f
}
fun velToRot(velocity: Vector3f): Vector3f {
val normalizedVelocity = Vector3f(velocity).normalize()
val yaw = Math.toDegrees(atan2(normalizedVelocity.z.toDouble(), normalizedVelocity.x.toDouble())).toFloat()
val pitch = Math.toDegrees(
atan2(
-normalizedVelocity.y.toDouble(),
sqrt(
normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2)
)
)
).toFloat()
return Vector3f(pitch, yaw, 0f)
}
@@ -57,9 +57,88 @@ data class EmitterParams (
rotVelCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 0.0f, LerpCurves.Sqrt),
scaleCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 2.5f, LerpCurves.Constant),
modelCurve = Pair(
arrayOf("shroomlight", "orange_concrete", "orange_stained_glass", "gray_wool", "gray_stained_glass", "light_gray_stained_glass", "light_gray_stained_glass_pane"),
arrayOf(
"shroomlight",
"orange_concrete",
"orange_stained_glass",
"gray_wool",
"gray_stained_glass",
"light_gray_stained_glass"
),
LerpCurves.Sqrt
),
)
)
val DEBUG = EmitterParams(
maxCount = 50,
spawnRate = 10,
spawnChance = 1.0f,
spawnDuration = ParamClasses.Duration.SingleBurst(5),
spawnPosOffset = Vector3f(0.0f, 2.0f, 0.0f),
lifeTime = ParamClasses.PairInt(25, 40),
shape = SpawningShape.Circle(1.0f, true),
offset = ParamClasses.PairVec3f.Uniform(0.0f, 0.0f),
initRot = ParamClasses.PairVec3f.Null,
rotVel = ParamClasses.PairVec3f.Null,
initVel = ParamClasses.PairVec3f.NonUniform(0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f),
initCenterVel = ParamClasses.PairFloat(1.5f, 1.5f),
initScale = ParamClasses.PairVec3f.Uniform(2.0f, 2.0f),
transformWithVel = ParamClasses.TransformWithVel.ScaleAndRot(0.75f),
forceFields = emptyArray(),
constVel = Vector3f(0.0f, -0.05f, 0.0f),
drag = 0.075f,
minVel = 0.0f,
offsetCurve = ParamClasses.LerpVal.NoLerpVec3f,
rotVelCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 0.0f, LerpCurves.Constant),
scaleCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 1.0f, LerpCurves.Constant),
modelCurve = Pair(
arrayOf(
"shroomlight",
"orange_concrete",
"orange_stained_glass",
"gray_wool",
"gray_stained_glass",
"light_gray_stained_glass"
),
LerpCurves.Constant
)
)
val STRESS_TEST = EmitterParams(
maxCount = 10000,
spawnRate = 50,
spawnChance = 1.0f,
spawnDuration = ParamClasses.Duration.SingleBurst(100),
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
lifeTime = ParamClasses.PairInt(75, 100),
shape = SpawningShape.Circle(10.0f, true),
offset = ParamClasses.PairVec3f.Uniform(0.0f, 0.0f),
initRot = ParamClasses.PairVec3f.NonUniform(0.0f, 0.0f, 0.0f, 360.0f, 360.0f, 360.0f),
rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f),
initVel = ParamClasses.PairVec3f.NonUniform(0.0f, 4.0f, 0.0f, 0.0f, 8.0f, 0.0f),
initCenterVel = ParamClasses.PairFloat(-1.5f, -1.5f),
initScale = ParamClasses.PairVec3f.Uniform(10.0f, 15.0f),
transformWithVel = ParamClasses.TransformWithVel.None,
forceFields = arrayOf(ForceField(
"MUSHROOM_HEAD",
Vector3f(0.0f, 75.0f, 0.0f),
ForceFieldShape.Sphere(15.0f, Pair(0.1f, 0.2f))
)),
constVel = Vector3f(0.0f, 0.001f, 0.0f),
drag = 0.075f,
minVel = 0.0f,
offsetCurve = ParamClasses.LerpVal.NoLerpVec3f,
rotVelCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 0.0f, LerpCurves.Constant),
scaleCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 1.5f, LerpCurves.Constant),
modelCurve = Pair(
arrayOf(
"shroomlight",
"orange_concrete",
"orange_stained_glass",
"gray_wool",
"gray_stained_glass",
"light_gray_stained_glass"
),
LerpCurves.Constant
)
)
}
}
@@ -4,6 +4,10 @@ import com.bytedice.bde_particles.LerpCurves
import com.bytedice.bde_particles.randomFloatBetween
import com.bytedice.bde_particles.randomIntBetween
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.pow
import kotlin.math.sqrt
class ParamClasses {
sealed class PairVec3f {
@@ -118,9 +122,38 @@ class ParamClasses {
val loopDelay: Int
) : Duration()
}
enum class TransformWithVel {
RotOnly,
ScaleAndRot,
None
sealed class TransformWithVel {
data object RotOnly : TransformWithVel() {
fun velToRot(velocity: Vector3f): Vector3f {
val normalizedVelocity = Vector3f(velocity).normalize()
val yaw = Math.toDegrees(atan2(normalizedVelocity.x.toDouble(), normalizedVelocity.z.toDouble())).toFloat()
val horizontalMag = sqrt(normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2))
val pitch = Math.toDegrees(atan2(-normalizedVelocity.y.toDouble(), horizontalMag)).toFloat()
return Vector3f(pitch, yaw, 0f)
}
}
class ScaleAndRot(private val scaleMul: Float) : TransformWithVel() {
fun velToRot(velocity: Vector3f) : Vector3f {
val normalizedVelocity = Vector3f(velocity).normalize()
val yaw = Math.toDegrees(atan2(normalizedVelocity.x.toDouble(), normalizedVelocity.z.toDouble())).toFloat()
val horizontalMag = sqrt(normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2))
val pitch = Math.toDegrees(atan2(-normalizedVelocity.y.toDouble(), horizontalMag)).toFloat()
return Vector3f(pitch, yaw, 0f)
}
fun velToScale(velocity: Vector3f): Vector3f {
val velocityMagnitude = velocity.length()
val stretchFactor = velocityMagnitude * scaleMul + 1.0f
val contractFactor = 1.0f / stretchFactor
return Vector3f(contractFactor, contractFactor, stretchFactor)
}
}
data object None : TransformWithVel()
}
}
@@ -10,13 +10,6 @@ import org.joml.Vector4f
import kotlin.math.*
// 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,
private var entityPos: Vec3d,
@@ -139,13 +132,30 @@ class Particle(
val newModel = lerpArray(emitterParams.modelCurve.first as Array<Any>, timeAliveClamped, emitterParams.modelCurve.second) as String
val (newVel, newPos) = calcPos()
val newRot = calcRot(timeAliveClamped)
val newScale = calcScale(timeAliveClamped)
val newOriginOffset = Vector3f(originOffset.mul(emitterParams.offsetCurve.lerpToVector3f(timeAliveClamped)))
val newScale: Vector3f
val newRot: Vector3f
when (emitterParams.transformWithVel) {
is ParamClasses.TransformWithVel.RotOnly -> {
newScale = calcScale(timeAliveClamped)
newRot = (emitterParams.transformWithVel as ParamClasses.TransformWithVel.RotOnly).velToRot(vel)
}
is ParamClasses.TransformWithVel.ScaleAndRot -> {
newScale = Vector3f(scale).mul((emitterParams.transformWithVel as ParamClasses.TransformWithVel.ScaleAndRot).velToScale(vel))
newRot = (emitterParams.transformWithVel as ParamClasses.TransformWithVel.ScaleAndRot).velToRot(vel)
}
else -> {
newScale = calcScale(timeAliveClamped)
newRot = calcRot(timeAliveClamped)
rot = newRot
}
}
vel = newVel
pos = newPos
rot = newRot
for (forceField in emitterParams.forceFields) {
if (forceField.shape is ForceFieldShape.Sphere) { vel.add(sphereSdfVel(forceField)) }
@@ -197,17 +207,10 @@ class Particle(
private fun calcRot(t: Float) : Vector3f {
val newRot = Vector3f(rot)
val newVel = Vector3f(rotVel)
if (emitterParams.transformWithVel == ParamClasses.TransformWithVel.None) {
val newVel = Vector3f(rotVel)
newVel.mul(emitterParams.rotVelCurve.lerpToVector3f(t))
return newRot.add(newVel)
}
else {
val rotWithVel = velToRot(vel)
return newRot.add(rotWithVel)
}
newVel.mul(emitterParams.rotVelCurve.lerpToVector3f(t))
return newRot.add(newVel)
}
@@ -233,9 +236,6 @@ class Particle(
private fun tickDebug(world: ServerWorld) {
// spawn particle at particle origin
// show trail pointing toward particle direction
// origin debug
val greenDustEffect = DustParticleEffect(Vector3f(0.0f, 1.0f, 0.0f), 2.0f)
world.spawnParticles(
@@ -251,23 +251,18 @@ class Particle(
)
// vel debug
var timesLooped = 0
repeat(3) {
val redDustEffect = DustParticleEffect(Vector3f(1.0f, 0.0f, 0.0f), 1.0f)
world.spawnParticles(
redDustEffect,
entityPos.x + pos.x + originOffset.x + vel.x * timesLooped,
entityPos.y + pos.y + originOffset.y + vel.y * timesLooped,
entityPos.z + pos.z + originOffset.z + vel.z * timesLooped,
1,
0.0,
0.0,
0.0,
0.0
)
timesLooped += 1
}
val redDustEffect = DustParticleEffect(Vector3f(1.0f, 0.0f, 0.0f), 1.0f)
world.spawnParticles(
redDustEffect,
entityPos.x + pos.x + originOffset.x + vel.x * 10,
entityPos.y + pos.y + originOffset.y + vel.y * 10,
entityPos.z + pos.z + originOffset.z + vel.z * 10,
1,
0.0,
0.0,
0.0,
0.0
)
}