improve player arm and leg walking animations

This commit is contained in:
Moritz Zwerger 2023-11-13 19:57:54 +01:00
parent 999af16d17
commit 5ed8ea53cf
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 134 additions and 61 deletions

View File

@ -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<PlayerRenderer<*>>()?.model?.arm?.swing(arm)
}
override fun handleAnimation(animation: EntityAnimations) {

View File

@ -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

View File

@ -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<R : EntityRenderer<*>>(renderer: R, model: BakedSkeletalModel) : SkeletalFeature(renderer, model) {
@ -27,6 +28,7 @@ abstract class HumanModel<R : EntityRenderer<*>>(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<R : EntityRenderer<*>>(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)

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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<E : PlayerEntity>(renderer: EntitiesRenderer, entity: E) : LivingEntityRenderer<E>(renderer, entity), DynamicTextureListener {
protected var model: PlayerModel? = null
var model: PlayerModel? = null
var skin: DynamicTexture? = null
private var refresh = true

View File

@ -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
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}