mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-12 08:58:02 -04:00
improve player arm and leg walking animations
This commit is contained in:
parent
999af16d17
commit
5ed8ea53cf
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user