significantly changed emitter params. STILL A LOT OF ERRORS.

This commit is contained in:
2024-11-29 19:38:43 +01:00
parent 6728445280
commit 939867934b
9 changed files with 260 additions and 270 deletions
@@ -24,6 +24,7 @@ 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.World import net.minecraft.world.World
import org.joml.Vector2f
import java.util.* import java.util.*
@@ -34,22 +35,19 @@ import java.util.*
// 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) // Adding a parameter for an initial velocity from an origin point (you currently have to use force fields)
// Pair<Pair<Vec3f, Vec3f>, Pair<Vec3f, Vec3f>>
// "min center vel", "max center vel", "min edge vel", "max edge vel"
// Parameters // Parameters
// localOffset (Vec3f)
// velocity based, relative coordinate based, or rotation based
// localOffsetCurve (curve)
// spawnOffset (Vec3f)
// originOffset (Vec3f) // originOffset (Vec3f)
// velocity based, relative coordinate based, or rotation based
// originOffsetCurve (curve)
// spawnOffset (Vec3f)
// velRot (bool) // velRot (bool)
// rotates the particle to face the velocity // rotates the particle to face the velocity
// spawnChance (Float) // spawnChance (Float)
// shape: spawnOnlyOnEdge (Boolean) // shape: spawnOnlyOnEdge (Boolean)
// Move most (if not all) particle params to emitters.
// 3D curves (curves for all X Y Z)
// 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.
// Better particle performance. // Better particle performance.
@@ -109,7 +107,7 @@ class Bde_particles : ModInitializer {
fun init() { fun init() {
addToEmitterRegister("DEFAULT", EmitterParams.DEFAULT) addToRegister("DEFAULT", EmitterParams.DEFAULT)
} }
@@ -139,7 +137,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
val handItem = player.getStackInHand(hand) val handItem = player.getStackInHand(hand)
val (isEmitterTool, emitterId) = ParticleEmitterTool.getToolDetails(handItem) val (isEmitterTool, emitterId) = ParticleEmitterTool.getToolDetails(handItem)
val hitResult = raycastFromPlayer(player as ServerPlayerEntity, 200.0) val hitResult = raycastFromPlayer(player as ServerPlayerEntity, 200.0)
val emitterParams = getEmitterParams(emitterId) val emitterParams = getParamsById(emitterId)
if ( if (
!isEmitterTool !isEmitterTool
@@ -149,7 +147,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
return TypedActionResult(ActionResult.PASS, handItem) return TypedActionResult(ActionResult.PASS, handItem)
} }
val emitter = ParticleEmitter(hitResult.pos, world, emitterParams) val emitter = ParticleEmitter(hitResult.pos, Vector2f(0.0f, 0.0f), world, emitterParams)
ALL_PARTICLE_EMITTERS += emitter ALL_PARTICLE_EMITTERS += emitter
return TypedActionResult(ActionResult.PASS, handItem) return TypedActionResult(ActionResult.PASS, handItem)
@@ -157,35 +155,16 @@ 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 allParticleParamsJSON: MutableList<Map<String, Any?>> = mutableListOf() val allParamsJson: MutableList<Map<String, Any?>> = mutableListOf()
val particle = params.particle
val particleParamsJSON = mapOf( val paramsJson = mapOf(
"shape" to particle.shape, "shape" to params.shape
"blockCurve" to particle.blockCurve,
"rotRandom" to particle.rotRandom,
"rotVelRandom" to particle.rotVelRandom,
"rotVelCurve" to particle.rotVelCurve,
"sizeRandom" to particle.sizeRandom,
"uniformSize" to particle.uniformSize,
"sizeCurve" to particle.sizeCurve,
"velRandom" to particle.velRandom,
"forceFields" to particle.forceFields,
"gravity" to particle.gravity,
"drag" to particle.drag,
"minVel" to particle.minVel,
"lifeTime" to particle.lifeTime
) )
allParticleParamsJSON.add(particleParamsJSON) allParamsJson.add(paramsJson)
val emitterParamsJSON = mapOf( val emitterParamsJSON = mapOf(
"maxCount" to params.maxCount, "maxCount" to params.maxCount,
"spawnsPerTick" to params.spawnsPerTick,
"loopDur" to params.loopDur,
"loopDelay" to params.loopDelay,
"loopCount" to params.loopCount,
"particleTypes" to allParticleParamsJSON
) )
return emitterParamsJSON return emitterParamsJSON
@@ -137,45 +137,6 @@ fun quatToEuler(quat: Vector4f): Vector3f {
} }
fun randomInCircle(r: Float) : Vector2f {
val randRadius = r * sqrt(randomFloatBetween(0.0f, 1.0f))
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
val randomPos = Vector2f(randRadius * cos(randAngle), randRadius * sin(randAngle))
return randomPos
}
fun randomInSphere(r: Float) : Vector3f {
val randRadius = r * Random.nextDouble().pow(1.0 / 3.0)
val phi = Random.nextDouble(0.0, 2 * Math.PI)
val cosTheta = Random.nextDouble(-1.0, 1.0)
val theta = acos(cosTheta)
val x = randRadius * sin(theta) * cos(phi)
val y = randRadius * sin(theta) * sin(phi)
val z = randRadius * cos(theta)
return Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
}
fun randomInRect(size: Vector2f) : Vector2f {
return Vector2f(randomFloatBetween(0.0f, size.x), randomFloatBetween(0.0f, size.y))
}
fun randomInCube(size: Vector3f) : Vector3f {
return Vector3f(
randomFloatBetween(0.0f, size.x),
randomFloatBetween(0.0f, size.y),
randomFloatBetween(0.0f, size.z)
)
}
fun transformOffsetByQuat(offset: Vector3f, rotation: Vector4f): Vector3f { fun transformOffsetByQuat(offset: Vector3f, rotation: Vector4f): Vector3f {
val x = offset.x val x = offset.x
val y = offset.y val y = offset.y
@@ -1,7 +1,7 @@
package com.bytedice.bde_particles.commands package com.bytedice.bde_particles.commands
import com.bytedice.bde_particles.items.ParticleEmitterTool import com.bytedice.bde_particles.items.ParticleEmitterTool
import com.bytedice.bde_particles.particles.emitterIdRegister import com.bytedice.bde_particles.particles.idRegister
import com.mojang.brigadier.Command import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.arguments.StringArgumentType
@@ -20,7 +20,7 @@ object GiveEmitterTool {
val command = CommandManager.literal("GiveEmitterTool") val command = CommandManager.literal("GiveEmitterTool")
.requires { source -> source.hasPermissionLevel(4) } .requires { source -> source.hasPermissionLevel(4) }
val allEmitterIds = emitterIdRegister.keys val allEmitterIds = idRegister.keys
val itemNameArg = CommandManager.argument("Item Name", StringArgumentType.string()) val itemNameArg = CommandManager.argument("Item Name", StringArgumentType.string())
val itemTypeArg = CommandManager.argument("Item Type", ItemStackArgumentType.itemStack(registryAccess)) val itemTypeArg = CommandManager.argument("Item Type", ItemStackArgumentType.itemStack(registryAccess))
@@ -4,7 +4,6 @@ import com.bytedice.bde_particles.emitterParamsToJson
import com.bytedice.bde_particles.particles.* import com.bytedice.bde_particles.particles.*
import com.mojang.brigadier.Command import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.builder.RequiredArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder
@@ -31,30 +31,63 @@ import org.joml.Vector3f
* @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. * @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 (
var maxCount: Int = 200, // spawning
var spawnRate: Int = 1, var maxCount: Int,
var spawnChance: Float = 1.0f, var spawnRate: Int,
var loopDur: Int = 25, var spawnChance: Float,
var loopDelay: Int = 0, var spawnDuration: ParamClasses.Duration,
var loopCount: Int = 0, var spawnPosOffset: Vector3f,
var shape: SpawningShape = SpawningShape.Circle(3.0f), var lifeTime: ParamClasses.PairInt,
var initRot: ParamClasses.PairVec3f = ParamClasses.PairVec3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), var shape: SpawningShape,
var rotVel: ParamClasses.PairVec3f = ParamClasses.PairVec3f(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f), // init transforms
var rotTowardVel: Boolean = true, var offset: ParamClasses.PairVec3f,
var initVel: ParamClasses.PairVec3f = ParamClasses.PairVec3f(-0.1f, 0.3f, -0.1f, 0.1f, 0.6f, 0.1f), var initRot: ParamClasses.PairVec3f,
var initScale: ParamClasses.RandScale = ParamClasses.RandScale.Uniform(), var rotVel: ParamClasses.PairVec3f,
var scaleTowardVel: Boolean = true, var rotWithVel: Boolean,
var forceFields: Array<ForceField> = emptyArray(), var initVel: ParamClasses.PairVec3f,
var constVel: Vector3f = Vector3f(0.0f, -0.01f, 0.0f), var initCenterVel: ParamClasses.PairFloat,
var drag: Float = 0.075f, var initScale: ParamClasses.PairVec3f,
var minVel: Float = 0.0f, var scaleWithVel: Boolean,
var lifeTime: ParamClasses.PairInt = ParamClasses.PairInt(25, 40), // velocity
var rotVelCurve: ParamClasses.LerpVal = ParamClasses.LerpVal.LerpUniform(1.0f, 0.0f, LerpCurves.Sqrt), var forceFields: Array<ForceField>,
var scaleCurve: ParamClasses.LerpVal = ParamClasses.LerpVal.LerpUniform(1.0f, 0.5f, LerpCurves.Linear), var constVel: Vector3f,
var blockCurve: Pair<Array<String>, LerpCurves> = Pair(arrayOf("minecraft:purple_concrete", "minecraft:purple_stained_glass"), LerpCurves.Sqrt), var drag: Float,
var minVel: Float,
// curves
var offsetCurve: ParamClasses.LerpVal,
var rotVelCurve: ParamClasses.LerpVal,
var scaleCurve: ParamClasses.LerpVal,
var blockCurve: Pair<Array<String>, LerpCurves>,
) )
{ {
companion object Presets { companion object Presets {
val DEFAULT = EmitterParams() val DEFAULT = EmitterParams(
maxCount = 200,
spawnRate = 1,
spawnChance = 1.0f,
spawnDuration = ParamClasses.Duration.SingleBurst(25),
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
lifeTime = ParamClasses.PairInt(25, 40),
shape = SpawningShape.Circle(3.0f),
offset = ParamClasses.PairVec3f.Uniform(-0.5f, -0.5f),
initRot = ParamClasses.PairVec3f.Null,
rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f),
rotWithVel = true,
initVel = ParamClasses.PairVec3f.Null,
initCenterVel = ParamClasses.PairFloat(0.1f, 0.3f),
initScale = ParamClasses.PairVec3f.Uniform(1.0f, 0.2f),
scaleWithVel = true,
forceFields = emptyArray(),
constVel = Vector3f(0.0f, -0.01f, 0.0f),
drag = 0.075f,
minVel = 0.0f,
offsetCurve = ParamClasses.LerpVal.NoLerpVec3f(Vector3f(-0.5f, -0.5f, -0.5f)),
rotVelCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 0.0f, LerpCurves.Sqrt),
scaleCurve = ParamClasses.LerpVal.LerpUniform(1.0f, 0.5f, LerpCurves.Linear),
blockCurve = Pair(
arrayOf("minecraft:purple_concrete", "minecraft:purple_stained_glass"),
LerpCurves.Sqrt
),
)
} }
} }
@@ -6,40 +6,24 @@ import com.bytedice.bde_particles.randomIntBetween
import org.joml.Vector3f import org.joml.Vector3f
class ParamClasses { class ParamClasses {
class PairVec3f ( sealed class PairVec3f {
private val minX: Float = 0.0f,
private val minY: Float = 0.0f,
private val minZ: Float = 0.0f,
private val maxX: Float = 1.0f,
private val maxY: Float = 1.0f,
private val maxZ: Float = 1.0f,
) {
fun randomize() : Vector3f {
return Vector3f(
randomFloatBetween(minX, maxX),
randomFloatBetween(minY, maxY),
randomFloatBetween(minZ, maxZ),
)
}
}
sealed class RandScale {
class Uniform ( class Uniform (
private val min: Float = 0.0f, private val min: Float,
private val max: Float = 1.0f, private val max: Float,
) : RandScale() { ) : PairVec3f() {
fun randomize() : Vector3f { fun randomize() : Vector3f {
val x = randomFloatBetween(min, max) val x = randomFloatBetween(min, max)
return Vector3f(x, x, x) return Vector3f(x, x, x)
} }
} }
class NonUniform ( class NonUniform (
private val minX: Float = 0.0f, private val minX: Float,
private val minY: Float = 0.0f, private val minY: Float,
private val minZ: Float = 0.0f, private val minZ: Float,
private val maxX: Float = 1.0f, private val maxX: Float,
private val maxY: Float = 1.0f, private val maxY: Float,
private val maxZ: Float = 1.0f private val maxZ: Float
) : RandScale() { ) : PairVec3f() {
fun randomize(): Vector3f { fun randomize(): Vector3f {
return Vector3f( return Vector3f(
randomFloatBetween(minX, maxX), randomFloatBetween(minX, maxX),
@@ -48,35 +32,29 @@ class ParamClasses {
) )
} }
} }
} data object Null : PairVec3f()
class PairVec2i (
private val minX: Int = 0,
private val minY: Int = 0,
private val maxX: Int = 1,
private val maxY: Int = 1,
) {
fun randomize() : Pair<Int, Int> {
return Pair(
randomIntBetween(minX, maxX),
randomIntBetween(minY, maxY),
)
}
} }
class PairInt ( class PairInt (
private val min: Int = 0, private val min: Int,
private val max: Int = 1, private val max: Int,
) { ) {
fun randomize() : Int { return randomIntBetween(min, max) } fun randomize() : Int { return randomIntBetween(min, max) }
} }
class PairFloat(
private val min: Float,
private val max: Float,
) {
fun randomize() : Float { return randomFloatBetween(min, max) }
}
sealed class LerpVal { sealed class LerpVal {
class LerpVec3f( class LerpVec3f(
private val fromX: Float = 0.0f, private val fromX: Float,
private val fromY: Float = 0.0f, private val fromY: Float,
private val fromZ: Float = 0.0f, private val fromZ: Float,
private val toX: Float = 1.0f, private val toX: Float,
private val toY: Float = 1.0f, private val toY: Float,
private val toZ: Float = 1.0f, private val toZ: Float,
private val curve: LerpCurves = LerpCurves.Linear, private val curve: LerpCurves,
) : LerpVal() { ) : LerpVal() {
fun lerp(t: Float): Vector3f { fun lerp(t: Float): Vector3f {
val x = com.bytedice.bde_particles.lerp(fromX, toX, t) * curve.function(t) val x = com.bytedice.bde_particles.lerp(fromX, toX, t) * curve.function(t)
@@ -86,15 +64,15 @@ class ParamClasses {
} }
} }
class MultiLerpVec3f( class MultiLerpVec3f(
private val fromX: Float = 0.0f, private val fromX: Float,
private val fromY: Float = 0.0f, private val fromY: Float,
private val fromZ: Float = 0.0f, private val fromZ: Float,
private val toX: Float = 1.0f, private val toX: Float,
private val toY: Float = 1.0f, private val toY: Float,
private val toZ: Float = 1.0f, private val toZ: Float,
private val curveX: LerpCurves = LerpCurves.Linear, private val curveX: LerpCurves,
private val curveY: LerpCurves = LerpCurves.Linear, private val curveY: LerpCurves,
private val curveZ: LerpCurves = LerpCurves.Linear private val curveZ: LerpCurves
) : LerpVal() { ) : LerpVal() {
fun lerp(t: Float): Vector3f { fun lerp(t: Float): Vector3f {
val x = com.bytedice.bde_particles.lerp(fromX, toX, t) * curveX.function(t) val x = com.bytedice.bde_particles.lerp(fromX, toX, t) * curveX.function(t)
@@ -104,14 +82,15 @@ class ParamClasses {
} }
} }
class LerpUniform( class LerpUniform(
private val from: Float = 0.0f, private val from: Float,
private val to: Float = 1.0f, private val to: Float,
private val curve: LerpCurves = LerpCurves.Linear, private val curve: LerpCurves,
) : LerpVal() { ) : LerpVal() {
fun lerp(t: Float): Float { fun lerp(t: Float): Float {
return com.bytedice.bde_particles.lerp(from, to, t) * curve.function(t) return com.bytedice.bde_particles.lerp(from, to, t) * curve.function(t)
} }
} }
data class NoLerpVec3f(val value: Vector3f) : LerpVal()
fun lerpToVector3f(t: Float) : Vector3f { fun lerpToVector3f(t: Float) : Vector3f {
when (this) { when (this) {
@@ -121,7 +100,22 @@ class ParamClasses {
val x = this.lerp(t) val x = this.lerp(t)
return Vector3f(x, x, x) return Vector3f(x, x, x)
} }
is NoLerpVec3f -> return this.value
} }
} }
} }
sealed class Duration {
data class SingleBurst(
val loopDur: Int
) : Duration()
data class MultiBurst(
val loopDur: Int,
val loopDelay: Int,
val loopCount: Int,
) : Duration()
data class InfiniteLoop(
val loopDur: Int,
val loopDelay: Int
) : Duration()
}
} }
@@ -2,8 +2,10 @@ package com.bytedice.bde_particles.particles
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.Vector3f
class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: ServerWorld, private val emitterParams: 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()
@@ -33,12 +35,12 @@ class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: S
private fun addParticle() { private fun addParticle() {
if (loopDelayActive) { return } if (loopDelayActive) { return }
repeat(this.emitterParams.spawnsPerTick) { repeat(params.spawnRate) {
if (allParticles.size >= emitterParams.maxCount) { return } if (allParticles.size >= params.maxCount) { return }
val particle = Particle(emitterParams.particle) val particle = Particle(params)
particle.init(this.emitterPos) particle.init(pos, rot)
particle.spawn(emitterWorld) particle.spawn(world)
allParticles += particle allParticles += particle
} }
@@ -50,30 +52,30 @@ class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: S
// because im so damn good at cooking spaghetti // because im so damn good at cooking spaghetti
// if max duration is 0 or less, skip counting, its infinite // if max duration is 0 or less, skip counting, its infinite
if (emitterParams.loopDur <= 0) { return } if (params.loopDur <= 0) { return }
// if the duration is greater than max duration, add 1 loopCount // if the duration is greater than max duration, add 1 loopCount
// return if max loopCount is 0 // return if max loopCount is 0
// if the delay is greater than 0 then enable it // if the delay is greater than 0 then enable it
if (loopDur >= emitterParams.loopDur) { if (loopDur >= params.loopDur) {
if (loopCount == 0) { if (loopCount == 0) {
isTicking = false isTicking = false
return return
} }
loopCount += 1 loopCount += 1
loopDur = 0 loopDur = 0
if (emitterParams.loopDelay > 0) { loopDelayActive = true } if (params.loopDelay > 0) { loopDelayActive = true }
} }
// if the delay is greater than max delay, disable the delay // if the delay is greater than max delay, disable the delay
else if (loopDelay >= emitterParams.loopDelay) { else if (loopDelay >= params.loopDelay) {
loopDelay = 0 loopDelay = 0
loopDelayActive = false loopDelayActive = false
} }
// if loopCount is greater than max loopCount then stop immediately // if loopCount is greater than max loopCount then stop immediately
// if max loopCount is below 0 then don't do anything, loop infinitely // if max loopCount is below 0 then don't do anything, loop infinitely
if (loopCount > 0 && loopCount >= emitterParams.loopCount) { if (loopCount > 0 && loopCount >= params.loopCount) {
isTicking = false isTicking = false
return return
} }
@@ -2,133 +2,60 @@
package com.bytedice.bde_particles.particles package com.bytedice.bde_particles.particles
import com.bytedice.bde_particles.LerpCurves
import org.joml.Vector3f val idRegister: MutableMap<String, EmitterParams> = mutableMapOf()
val forbiddenIds = arrayOf("", "NULL")
val emitterIdRegister: MutableMap<String, EmitterParams> = mutableMapOf() fun addToRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
val forbiddenIds = arrayOf("", "DEFAULT", "NULL") val newId = replaceForbiddenChars(id.replace(" ", "_"))
if (idRegister.containsKey(newId)) { return Pair(newId, false) }
fun addToEmitterRegister(id: String, params: EmitterParams) : Pair<String, Boolean> { idRegister[id] = params
val newId = id.replace(" ", "_")
if (newId in forbiddenIds && newId != "DEFAULT") { return Pair(newId, false) }
if (emitterIdRegister.containsKey(newId)) { return Pair(newId, false) }
emitterIdRegister[id] = params
return Pair(newId, true) return Pair(newId, true)
} }
fun removeFromEmitterRegister(id: String) : Boolean { fun removeFromRegister(id: String) : Boolean {
if (id in forbiddenIds) { return false } if (id in forbiddenIds) { return false }
if (!emitterIdRegister.containsKey(id)) { return false } if (!idRegister.containsKey(id)) { return false }
emitterIdRegister.remove(id) idRegister.remove(id)
return true return true
} }
fun getEmitterParams(id: String) : EmitterParams? { fun getParamsById(id: String) : EmitterParams? {
return emitterIdRegister[id] return idRegister[id]
} }
fun updateEmitterParams(id: String, newParams: EmitterParams) { fun updateRegistered(id: String, newParams: EmitterParams) {
emitterIdRegister[id] = newParams idRegister[id] = newParams
} }
fun updateSingleEmitterParam(id: String, paramName: String, paramValue: Any) : Boolean { fun updateParam(id: String, paramName: String, paramValue: Any) : Boolean {
val emitter = getEmitterParams(id) ?: return false val emitter = getParamsById(id) ?: return false
val updatedValues = updateSingleEmitterParam(emitter, paramName, paramValue) val updatedValues = updateParams(emitter, paramName, paramValue)
updateEmitterParams(id, updatedValues.first)
return updatedValues.second return updatedValues.second
} }
fun updateSingleParticleParam(emitterId: String, paramName: String, paramValue: Any) : Boolean { // TODO: map to new params fun updateParams(params: EmitterParams, paramName: String, paramValue: Any) : Pair<EmitterParams, Boolean> { // TODO: map to new params
val emitter = getEmitterParams(emitterId) ?: return false
val particle = emitter.particle
val updatedValues = updateSingleParticleParam(particle, paramName, paramValue)
emitter.particle = updatedValues.first
updateEmitterParams(emitterId, emitter)
return updatedValues.second
}
fun updateSingleEmitterParam(params: EmitterParams, paramName: String, paramValue: Any) : Pair<EmitterParams, Boolean> { // TODO: map to new params
val value: Any? = when (paramName) {
"maxCount" -> paramValue as Int
"spawnsPerTick" -> paramValue as Int
"loopDur" -> paramValue as Int
"loopDelay" -> paramValue as Int
"loopCount" -> paramValue as Int
else -> null
}
if (value == null) { return Pair(params, false) }
val newParams = params.copy() val newParams = params.copy()
when (paramName) { when (paramName) {
"maxCount" -> newParams.maxCount = value as Int "maxCount" -> newParams.maxCount = paramValue as Int
"spawnsPerTick" -> newParams.spawnsPerTick = value as Int
"loopDur" -> newParams.loopDur = value as Int
"loopDelay" -> newParams.loopDelay = value as Int
"loopCount" -> newParams.loopCount = value as Int
else -> return Pair(params, false)
} }
return Pair(newParams, true) return Pair(newParams, true)
} }
fun updateSingleParticleParam(params: ParticleParams, paramName: String, paramValue: Any) : Pair<ParticleParams, Boolean> { // TODO: map to new params fun replaceForbiddenChars(input: String): String {
val value: Any? = when (paramName) { val regex = "[^a-zA-Z0-9_-]".toRegex()
"shape" -> paramValue as SpawningShape? return input.replace(regex, "-")
"blockCurve" -> paramValue as Array<String>
"rotRandom" -> paramValue as Pair<Vector3f, Vector3f>
"rotVelRandom" -> paramValue as Pair<Vector3f, Vector3f>
"sizeRandom" -> paramValue as Pair<Vector3f, Vector3f>
"uniformSize" -> paramValue as Boolean
"velRandom" -> paramValue as Pair<Vector3f, Vector3f>
"forceFields" -> paramValue as Array<ForceField>
"gravity" -> paramValue as Vector3f
"drag" -> paramValue as Float
"minVel" -> paramValue as Float
"lifeTime" -> paramValue as Pair<Int, Int>
"rotVelCurve" -> paramValue as Triple<Vector3f, Vector3f, LerpCurves>
"sizeCurve" -> paramValue as Triple<Vector3f, Vector3f, LerpCurves>
else -> null
}
if (value == null) { return Pair(params, false) }
val newParams = params.copy()
when (paramName) {
"shape" -> newParams.shape = value as SpawningShape
"blockCurve" -> newParams.blockCurve = value as Array<String>
"rotRandom" -> newParams.rotRandom = value as Pair<Vector3f, Vector3f>
"rotVelRandom" -> newParams.rotVelRandom = value as Pair<Vector3f, Vector3f>
"sizeRandom" -> newParams.sizeRandom = value as Pair<Vector3f, Vector3f>
"uniformSize" -> newParams.uniformSize = value as Boolean
"velRandom" -> newParams.velRandom = value as Pair<Vector3f, Vector3f>
"forceFields" -> newParams.forceFields = value as Array<ForceField>
"gravity" -> newParams.gravity = value as Vector3f
"drag" -> newParams.drag = value as Float
"minVel" -> newParams.minVel = value as Float
"lifeTime" -> newParams.lifeTime = value as Pair<Int, Int>
"rotVelCurve" -> newParams.rotVelCurve = value as Triple<Vector3f, Vector3f, LerpCurves>
"sizeCurve" -> newParams.sizeCurve = value as Triple<Vector3f, Vector3f, LerpCurves>
else -> return Pair(params, false)
}
return Pair(newParams, true)
} }
@@ -1,16 +1,111 @@
package com.bytedice.bde_particles.particles package com.bytedice.bde_particles.particles
import com.bytedice.bde_particles.randomFloatBetween
import com.bytedice.bde_particles.randomIntBetween
import org.joml.Vector2f import org.joml.Vector2f
import org.joml.Vector3f import org.joml.Vector3f
import kotlin.math.*
import kotlin.random.Random
sealed class SpawningShape { sealed class SpawningShape {
data class Circle(val radius: Float = 1.0f) : SpawningShape() class Circle(val radius: Float = 1.0f) : SpawningShape() {
data class Rect (val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape() fun randomWithin(): Vector3f {
data class Cube (val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape() val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f))
data class Sphere(val radius: Float = 1.0f) : SpawningShape() val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
val randomPos = Vector3f(randRadius * cos(randAngle), 0.0f, randRadius * sin(randAngle))
return randomPos
}
fun randomOnEdge(): Vector3f {
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
return Vector3f(radius * cos(randAngle), 0.0f, radius * sin(randAngle))
}
}
class Rect(val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape() {
fun randomWithin(): Vector3f {
return Vector3f(randomFloatBetween(0.0f, size.x), 0.0f, randomFloatBetween(0.0f, size.y))
}
fun randomOnEdge(): Vector3f {
val perimeter = 2 * (size.x + size.y)
val randPos = randomFloatBetween(0.0f, perimeter)
return when {
randPos <= size.x -> Vector3f(randPos, 0.0f, 0.0f)
randPos <= size.x + size.y -> Vector3f(size.x, 0.0f, randPos - size.x)
randPos <= 2 * size.x + size.y -> Vector3f(size.x - (randPos - size.x - size.y), 0.0f, size.y)
else -> Vector3f(0.0f, 0.0f, size.y - (randPos - 2 * size.x - size.y))
}
}
}
class Cube(val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape() {
fun randomWithin(): Vector3f {
return Vector3f(
randomFloatBetween(0.0f, size.x),
randomFloatBetween(0.0f, size.y),
randomFloatBetween(0.0f, size.z)
)
}
fun randomOnEdge(): Vector3f {
val edgeIndex = randomIntBetween(0, 11) // 12 edges
val randPos = randomFloatBetween(0.0f, 1.0f)
return when (edgeIndex) {
0 -> Vector3f(randPos * size.x, 0.0f, 0.0f)
1 -> Vector3f(size.x, 0.0f, randPos * size.z)
2 -> Vector3f(size.x - randPos * size.x, 0.0f, size.z)
3 -> Vector3f(0.0f, 0.0f, size.z - randPos * size.z)
4 -> Vector3f(randPos * size.x, size.y, 0.0f)
5 -> Vector3f(size.x, size.y, randPos * size.z)
6 -> Vector3f(size.x - randPos * size.x, size.y, size.z)
7 -> Vector3f(0.0f, size.y, size.z - randPos * size.z)
8 -> Vector3f(0.0f, randPos * size.y, 0.0f)
9 -> Vector3f(size.x, randPos * size.y, 0.0f)
10 -> Vector3f(size.x, randPos * size.y, size.z)
11 -> Vector3f(0.0f, randPos * size.y, size.z)
else -> Vector3f(0.0f, 0.0f, 0.0f)
}
}
}
class Sphere(val radius: Float = 1.0f) : SpawningShape() {
fun randomWithin(): Vector3f {
val randRadius = radius * Random.nextDouble().pow(1.0 / 3.0)
val phi = Random.nextDouble(0.0, 2 * Math.PI)
val cosTheta = Random.nextDouble(-1.0, 1.0)
val theta = acos(cosTheta)
val x = randRadius * sin(theta) * cos(phi)
val y = randRadius * sin(theta) * sin(phi)
val z = randRadius * cos(theta)
return Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
}
fun randomOnEdge(): Vector3f {
val phi = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
val cosTheta = randomFloatBetween(-1.0f, 1.0f)
val sinTheta = sqrt(1 - cosTheta * cosTheta)
val x = radius * sinTheta * cos(phi)
val y = radius * sinTheta * sin(phi)
val z = radius * cosTheta
return Vector3f(x, y, z)
}
}
data object Point : SpawningShape() data object Point : SpawningShape()
} }
sealed class ForceFieldShape { sealed class ForceFieldShape {
data class Sphere( data class Sphere(
val radius: Float = 3.0f, val radius: Float = 3.0f,