From 5ed8ea53cf4cd6ae331d186ecbf06dd5a3ebd229 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Mon, 13 Nov 2023 19:57:54 +0100 Subject: [PATCH] improve player arm and leg walking animations --- .../entities/entities/player/PlayerEntity.kt | 3 + .../block/flashing/FlashingBlockFeature.kt | 1 + .../entities/model/human/HumanModel.kt | 3 + .../model/human/animator/ArmAnimator.kt | 55 +++++++++--------- .../model/human/animator/LegAnimator.kt | 24 +------- .../renderer/living/player/PlayerRenderer.kt | 2 +- .../rendering/entities/util/EntitySpeed.kt | 51 +++++++++++++---- .../entities/util/EntitySpeedAnimator.kt | 56 +++++++++++++++++++ 8 files changed, 134 insertions(+), 61 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeedAnimator.kt diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/PlayerEntity.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/PlayerEntity.kt index c8a49e77b..b1c141fc4 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/PlayerEntity.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/PlayerEntity.kt @@ -15,6 +15,7 @@ package de.bixilon.minosoft.data.entities.entities.player import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kutil.bit.BitByte.isBitMask +import de.bixilon.kutil.cast.CastUtil.nullCast import de.bixilon.kutil.json.JsonObject import de.bixilon.kutil.observer.set.SetObserver.Companion.observedSet import de.bixilon.kutil.primitive.IntUtil.toInt @@ -38,6 +39,7 @@ import de.bixilon.minosoft.data.registries.shapes.aabb.AABB import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.RGBColor +import de.bixilon.minosoft.gui.rendering.entities.renderer.living.player.PlayerRenderer import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY import de.bixilon.minosoft.physics.entities.living.player.PlayerPhysics import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection @@ -141,6 +143,7 @@ abstract class PlayerEntity( fun swingHand(hand: Hands) { val arm = hand.getArm(mainArm) + renderer?.nullCast>()?.model?.arm?.swing(arm) } override fun handleAnimation(animation: EntityAnimations) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/feature/block/flashing/FlashingBlockFeature.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/feature/block/flashing/FlashingBlockFeature.kt index 8bbd9cf75..ee9119fde 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/feature/block/flashing/FlashingBlockFeature.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/feature/block/flashing/FlashingBlockFeature.kt @@ -42,6 +42,7 @@ open class FlashingBlockFeature( private fun updateFlashing(delta: Float) { if (delta > flashInterval) return + // TODO: exponential? progress += (delta / flashInterval) if (progress > maxFlash) { progress -= maxFlash * 2.0f diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/HumanModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/HumanModel.kt index 7c55c7e00..6897b8a86 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/HumanModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/HumanModel.kt @@ -19,6 +19,7 @@ import de.bixilon.minosoft.gui.rendering.entities.model.human.animator.ArmAnimat import de.bixilon.minosoft.gui.rendering.entities.model.human.animator.LegAnimator import de.bixilon.minosoft.gui.rendering.entities.renderer.EntityRenderer import de.bixilon.minosoft.gui.rendering.entities.util.EntitySpeed +import de.bixilon.minosoft.gui.rendering.entities.util.EntitySpeedAnimator import de.bixilon.minosoft.gui.rendering.skeletal.baked.BakedSkeletalModel abstract class HumanModel>(renderer: R, model: BakedSkeletalModel) : SkeletalFeature(renderer, model) { @@ -27,6 +28,7 @@ abstract class HumanModel>(renderer: R, model: BakedSkelet val arm = ArmAnimator(this, instance.transform.children["left_arm"]!!, instance.transform.children["right_arm"]!!) val speed = EntitySpeed(renderer.entity) + val speedAnimator = EntitySpeedAnimator(speed) override fun updatePosition() { super.updatePosition() @@ -35,6 +37,7 @@ abstract class HumanModel>(renderer: R, model: BakedSkelet override fun update(millis: Long, delta: Float) { speed.update(delta) + speedAnimator.update(delta) super.update(millis, delta) leg.update(delta) arm.update(delta) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/ArmAnimator.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/ArmAnimator.kt index 2ee919faf..e32398a35 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/ArmAnimator.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/ArmAnimator.kt @@ -14,53 +14,52 @@ package de.bixilon.minosoft.gui.rendering.entities.model.human.animator import de.bixilon.kotlinglm.func.rad -import de.bixilon.kotlinglm.vec3.Vec3 -import de.bixilon.kutil.math.MathConstants.PIf -import de.bixilon.kutil.math.interpolation.FloatInterpolation.interpolateLinear +import de.bixilon.minosoft.data.entities.entities.player.Arms import de.bixilon.minosoft.gui.rendering.entities.model.human.HumanModel import de.bixilon.minosoft.gui.rendering.skeletal.instance.TransformInstance -import de.bixilon.minosoft.gui.rendering.util.mat.mat4.Mat4Util.rotateDegreesAssign import de.bixilon.minosoft.gui.rendering.util.mat.mat4.Mat4Util.rotateXAssign -import kotlin.math.sin class ArmAnimator( val model: HumanModel<*>, val left: TransformInstance, val right: TransformInstance, ) { - private var maxAngle = 0.0f - private var progress = 0.0f - private var strength = 1.0f - - private fun updateAngle(delta: Float) { - this.maxAngle = interpolateLinear(model.speed.value * VELOCITY_ANGLE, 0.0f, MAX_ANGLE) - this.progress += strength * delta * 5.0f * model.speed.value - this.progress %= 2.0f - } + private var swinging = FloatArray(Arms.VALUES.size) { Float.NaN } fun update(delta: Float) { - updateAngle(delta) apply() } private fun apply() { - if (this.maxAngle == 0.0f) return - val progress = sin((progress - 1.0f) * PIf) * this.maxAngle + val angle = model.speedAnimator.getAngle(MAX_ANGLE).rad - val rad = progress.rad + apply(Arms.LEFT, angle) + apply(Arms.RIGHT, -angle) + } - left.value - .translateAssign(left.pivot) - .rotateXAssign(rad) - .translateAssign(left.nPivot) - right.value - .translateAssign(left.pivot) - .rotateXAssign(-rad) - .translateAssign(left.nPivot) + private fun apply(arm: Arms, walking: Float) { + val transform = this[arm] + val swinging = swinging[arm.ordinal] + if (swinging.isNaN()) { + transform.value + .translateAssign(right.pivot) + .rotateXAssign(walking) + .translateAssign(right.nPivot) + } + + } + + private operator fun get(arm: Arms) = when (arm) { + Arms.LEFT -> left + Arms.RIGHT -> right + } + + + fun swing(arm: Arms) { + swinging[arm.ordinal] = 0.0f } private companion object { - const val MAX_ANGLE = 45.0f - const val VELOCITY_ANGLE = 5.0f + const val MAX_ANGLE = 40.0f } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/LegAnimator.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/LegAnimator.kt index aee8eceeb..47637db25 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/LegAnimator.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/model/human/animator/LegAnimator.kt @@ -14,52 +14,34 @@ package de.bixilon.minosoft.gui.rendering.entities.model.human.animator import de.bixilon.kotlinglm.func.rad -import de.bixilon.kotlinglm.vec3.Vec3 -import de.bixilon.kutil.math.MathConstants.PIf -import de.bixilon.kutil.math.interpolation.FloatInterpolation.interpolateLinear import de.bixilon.minosoft.gui.rendering.entities.model.human.HumanModel import de.bixilon.minosoft.gui.rendering.skeletal.instance.TransformInstance -import de.bixilon.minosoft.gui.rendering.util.mat.mat4.Mat4Util.rotateDegreesAssign import de.bixilon.minosoft.gui.rendering.util.mat.mat4.Mat4Util.rotateXAssign -import kotlin.math.sin class LegAnimator( val model: HumanModel<*>, val left: TransformInstance, val right: TransformInstance, ) { - private var maxAngle = 0.0f - private var progress = 0.0f - private var strength = 1.0f - - private fun updateAngle(delta: Float) { - this.maxAngle = interpolateLinear(model.speed.value * VELOCITY_ANGLE, 0.0f, MAX_ANGLE) - this.progress += strength * delta * 5.0f * model.speed.value - this.progress %= 2.0f - } fun update(delta: Float) { - updateAngle(delta) apply() } private fun apply() { - if (this.maxAngle == 0.0f) return - val progress = sin((progress - 1.0f) * PIf) * this.maxAngle + val angle = model.speedAnimator.getAngle(MAX_ANGLE).rad - val rad = progress.rad left.value .translateAssign(left.pivot) - .rotateXAssign(-rad) + .rotateXAssign(-angle) .translateAssign(left.nPivot) right.value .translateAssign(left.pivot) - .rotateXAssign(rad) + .rotateXAssign(angle) .translateAssign(left.nPivot) } private companion object { const val MAX_ANGLE = 45.0f - const val VELOCITY_ANGLE = 5.0f } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/renderer/living/player/PlayerRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/renderer/living/player/PlayerRenderer.kt index d29605ca5..e6f5a5072 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/renderer/living/player/PlayerRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/renderer/living/player/PlayerRenderer.kt @@ -36,7 +36,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.skin.PlayerSkin import de.bixilon.minosoft.gui.rendering.util.mat.mat4.Mat4Util.translateYAssign open class PlayerRenderer(renderer: EntitiesRenderer, entity: E) : LivingEntityRenderer(renderer, entity), DynamicTextureListener { - protected var model: PlayerModel? = null + var model: PlayerModel? = null var skin: DynamicTexture? = null private var refresh = true diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeed.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeed.kt index 826394477..3f40525cf 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeed.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeed.kt @@ -19,41 +19,70 @@ import de.bixilon.kutil.math.interpolation.Interpolator import de.bixilon.minosoft.data.entities.entities.Entity import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.EMPTY import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import kotlin.math.sqrt class EntitySpeed(val entity: Entity) { private val interpolator = Interpolator(0.0f, FloatInterpolation::interpolateLinear) private var position0 = Vec3d.EMPTY + private var length2 = 0.0f + private var age = 0 val value: Float get() = interpolator.value - private fun update() { - val position = entity.renderInfo.position + private fun updateLength2() { + val position = entity.physics.position val deltaX = position.x - position0.x val deltaZ = position.z - position0.z this.position0 = position - val length2 = (deltaX * deltaX + deltaZ * deltaZ).toFloat() - var value = length2 * TIME_RATIO / 5.0f + var length2 = (deltaX * deltaX + deltaZ * deltaZ).toFloat() - // TODO: scale value that it never reaches 1.0 + if (length2 < 0.00003f) { + length2 = 0.0f + } + + this.length2 = length2 + } + + + private fun push(step: Float) { + if (length2 < 0.003f) { + return this.interpolator.push(0.0f) + } + val speed = sqrt(length2) * (step / (ProtocolDefinition.TICK_TIMEf / 1000.0f)) + + var value = (1.0f - (1.0f / (speed + 1.0f))) * 1.1f if (value > 1.0f) value = 1.0f - if (value < 0.1f && value > 0.001f) value = 0.1f this.interpolator.push(value) } fun update(delta: Float) { - if (this.interpolator.delta >= 1.0f) { - update() + val age = entity.age + if (age == this.age) return + this.age = age + + val previous = this.length2 + updateLength2() + + val rapid = when { + previous < SPEED_THRESHOLD && length2 > SPEED_THRESHOLD -> true // start + previous > SPEED_THRESHOLD && length2 < SPEED_THRESHOLD -> true // stop + else -> false } - this.interpolator.add(delta, TIME) + val step = if (rapid) RAPID_STEP else NORMAL_STEP + if (rapid || this.interpolator.delta >= 1.0f) { + push(step) + } + this.interpolator.add(delta, step) } private companion object { - const val TIME = 0.15f - const val TIME_RATIO = TIME / (ProtocolDefinition.TICK_TIMEf / 1000.0f) + const val SPEED_THRESHOLD = 0.0005f + const val RAPID_STEP = 0.05f + const val NORMAL_STEP = 0.1f } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeedAnimator.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeedAnimator.kt new file mode 100644 index 000000000..9b388093e --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entities/util/EntitySpeedAnimator.kt @@ -0,0 +1,56 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.entities.util + +import de.bixilon.kutil.math.MathConstants +import de.bixilon.kutil.math.interpolation.FloatInterpolation.interpolateLinear +import kotlin.math.sin + +class EntitySpeedAnimator( + val speed: EntitySpeed, +) { + var angle = 0.0f + private set + var progress = 0.0f + private set + + + fun update(delta: Float) { + val speed = speed.value + + var amplifier = speed * SPEED_AMPLIFIER + if (amplifier < MIN_SPEED) { + amplifier = -MIN_SPEED + } + + angle += (amplifier - amplifier / 2.0f) + if (angle > 1.0f) angle = 1.0f + if (angle < 0.0f) angle = 0.0f + + this.progress += delta * SPEED_AMPLIFIER * maxOf(speed, MIN_SPEED) + this.progress %= 2.0f + } + + fun getAngle(max: Float): Float { + val angle = interpolateLinear(this.angle, 0.0f, max) + if (angle <= 0.0f) return 0.0f + + return sin((progress - 1.0f) * MathConstants.PIf) * angle + } + + private companion object { + const val MIN_SPEED = 0.2f + const val SPEED_AMPLIFIER = 10.0f + } +}