Compare commits

..
Author SHA1 Message Date
Landon Wills dac4eb3501 Experimental fix for unintentional local emitter rotation 2026-11-12 16:51:42 -05:00
7 changed files with 11 additions and 37 deletions

No files matched your search

+1 -2
View File
@@ -27,10 +27,9 @@
- Pokémon can now have their Minecraft footstep sounds silenced in their poser. This doesn't affect emitting game events.
- Fixed the Molang coalescing operator not working the way you'd expect a coalesce operator to.
- Added `v.spawnable_position` to the weight spawn rule component's "weight" expression
- Added `is_alive` function to entities. Can be useful for terminating particles bound to them.
### Particles
- Fixed local space settings for particles that are attached to Pokemon locators. Should fix a lot of jitters and rotation issues.
- Fixes emitters being rotated by a model's locators when local rotation was off. This should fix some niche unexpected rotation issues from occurring.
### Localization
- Updated translations for:
@@ -266,9 +266,6 @@ object EntityMoLangFunctions : AbstractMoLangFunctionHolder<Entity>() {
entity.noPhysics = intangible
DoubleValue.ONE
}
functions["is_alive"] = { params ->
if (entity.isAlive) DoubleValue.ONE else DoubleValue.ZERO
}
when (entity) {
is PosableEntity -> {
@@ -31,8 +31,8 @@ class EmitterSpace(
}
}
fun initializeEmitterMatrix(rootMatrix: MatrixWrapper, locatorMatrix: MatrixWrapper): MatrixWrapper {
var rootRotation = rootMatrix.matrix.getRotation(AxisAngle4f())
fun initializeEmitterMatrix(rootMatrix: MatrixWrapper, locatorMatrix: MatrixWrapper, localSpaceRotation: Boolean = true): MatrixWrapper {
var rootRotation = if (localSpaceRotation) rootMatrix.matrix.getRotation(AxisAngle4f()) else AxisAngle4f()
//When the locator hasn't yet been initialized, we start getting NaNs, so default to no rotation instead
if (rootRotation.x.isNaN() || rootRotation.y.isNaN() || rootRotation.z.isNaN() || rootRotation.angle.isNaN()) {
rootRotation = AxisAngle4f()
@@ -36,7 +36,7 @@ object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSno
sourceLocators.forEach { locator ->
val locatorMatrix = sourceState.locatorStates[locator]!!
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix)
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix, effect.space.localRotation)
val particleRuntime = MoLangRuntime().setup().setupClient()
particleRuntime.environment.query.addFunction("entity") { sourceState.runtime.environment.query }
@@ -15,9 +15,7 @@ import com.cobblemon.mod.common.ModAPI
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterial
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterials
import com.cobblemon.mod.common.api.snowstorm.UVDetails
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
@@ -142,19 +140,6 @@ class SnowstormParticle(
}
}
/* Without this, locators will lag behind the animation because particles render before Pokemon.
* This is important for particles attached to locators using local position/rotation.
*
* In the future, we may want to make `MountedPokemonAnimationRenderController` a more
* generic utility for bedrock models if we see a case where other non-pokemon models (ie. NPCs)
* should emit particles in their animations that utilise local space.
*/
storm.entity?.let {
if (storm.effect.space.localPosition && it is PokemonEntity) {
MountedPokemonAnimationRenderController.setup(it, tickDelta)
}
}
applyRandoms()
setParticleAgeInRuntime()
storm.effect.curves.forEach { it.apply(runtime) }
@@ -162,16 +147,11 @@ class SnowstormParticle(
val vec3d = camera.position
// Interpolate because we are often between the ticks where these positions update.
val interpLocalX = Mth.lerp(tickDelta.toDouble(), prevLocalX, localX)
val interpLocalY = Mth.lerp(tickDelta.toDouble(), prevLocalY, localY)
val interpLocalZ = Mth.lerp(tickDelta.toDouble(), prevLocalZ, localZ)
val pos = if (storm.effect.space.localPosition && storm.effect.space.localRotation) {
val vec = Vec3(interpLocalX, interpLocalY, interpLocalZ)
val rotated = storm.attachedMatrix.matrix.transformDirection(vec)
Vector3d(rotated.x, rotated.y, rotated.z)
} else if (storm.effect.space.localRotation) {
val pos = if (storm.effect.space.localRotation) {
val interpRotation = Mth.lerp(tickDelta.toDouble(), 0.0, currentRotation.angle)
val vec = Vector3d(interpLocalX, interpLocalY, interpLocalZ)
oldAxisRotation.transform(vec)
@@ -180,11 +160,9 @@ class SnowstormParticle(
Vector3d(interpLocalX, interpLocalY, interpLocalZ)
}
val origin = if (storm.effect.space.localPosition) storm.attachedMatrix.getOrigin() else originPos
val f = (pos.x + origin.x - vec3d.x()).toFloat()
val g = (pos.y + origin.y - vec3d.y()).toFloat()
val h = (pos.z + origin.z - vec3d.z()).toFloat()
val f = (pos.x + originPos.x - vec3d.x()).toFloat()
val g = (pos.y + originPos.y - vec3d.y()).toFloat()
val h = (pos.z + originPos.z - vec3d.z()).toFloat()
val quaternion = storm.effect.particle.cameraMode.getRotation(
matrixWrapper = matrixWrapper,
prevAngle = oRoll,
@@ -253,7 +231,7 @@ class SnowstormParticle(
override fun tick() {
if (storm.effect.space.localPosition) {
originPos = storm.attachedMatrix.getOrigin()
originPos = matrixWrapper.getOrigin()
}
applyRandoms()
@@ -355,7 +355,7 @@ abstract class PosableState : Schedulable {
val rootMatrix = locatorStates["root"] ?: return@addFunction DoubleValue.ZERO // Played before it's on screen for the first time
val locatorMatrix = locatorStates[locator] ?: rootMatrix
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix)
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix, effect.space.localRotation)
val particleRuntime = MoLangRuntime().setup().setupClient()
particleRuntime.environment.query.addFunction("entity") { runtime.environment.query }
@@ -73,7 +73,7 @@ class BedrockParticleKeyframe(
val rootMatrix = state.locatorStates["root"]!!
val locatorMatrix = state.locatorStates[locator] ?: state.locatorStates["root"]!!
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix)
val particleMatrix = effect.emitter.space.initializeEmitterMatrix(rootMatrix, locatorMatrix, effect.space.localRotation)
if (this in state.poseParticles) {
return