fix some animation bugs

This commit is contained in:
Moritz Zwerger 2023-12-12 23:35:24 +01:00
parent be2e266279
commit 44004d5919
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
11 changed files with 134 additions and 121 deletions

View File

@ -79,6 +79,14 @@ class TextureAnimationTest {
fun `draw 3,0 frames`() {
val animation = create()
animation.update(3.0f)
assertSame(animation.frame1, c)
assertSame(animation.frame2, a)
assertEquals(animation.progress, 0.75f)
}
fun `draw 3,5 frames`() {
val animation = create()
animation.update(3.5f)
assertSame(animation.frame1, a)
assertSame(animation.frame2, b)
assertEquals(animation.progress, 0.0f)
@ -104,13 +112,10 @@ class TextureAnimationTest {
val animation = create()
animation.update(1.0f)
animation.update(1.0f)
animation.update(0.2f)
animation.update(0.3f)
animation.update(0.5f)
assertSame(animation.frame1, c)
assertSame(animation.frame2, a)
assertEquals(animation.progress, 0.5f)
}
// TODO: update twice
}

View File

@ -26,7 +26,7 @@ class AnimationPropertiesTest {
val json = """{
"animation": {
"interpolate": true,
"frametime": 1,
"frametime": 4,
"frames": [
{
"index": 2,
@ -41,9 +41,9 @@ class AnimationPropertiesTest {
val data = properties.animation!!.create(Vec2i(16, 64))
assertEquals(data, AnimationProperties.FrameData(listOf(
AnimationProperties.Frame(0.1f, 2),
AnimationProperties.Frame(0.05f, 1),
AnimationProperties.Frame(0.05f, 2),
AnimationProperties.Frame(0.05f, 3),
AnimationProperties.Frame(0.2f, 1),
AnimationProperties.Frame(0.2f, 2),
AnimationProperties.Frame(0.2f, 3),
), 4, Vec2i(16, 16)))
}

View File

@ -36,7 +36,7 @@ abstract class SimpleTextureParticle(connection: PlayConnection, position: Vec3d
}
// calculate next texture
val nextTextureResourceLocation = data.type.textures[age / (maxAge / totalTextures + 1)]
if (texture?.nullCast<FileTexture>()?.resourceLocation == nextTextureResourceLocation) {
if (texture?.nullCast<FileTexture>()?.file == nextTextureResourceLocation) {
return
}
texture = connection.rendering?.context?.textures?.static?.get(nextTextureResourceLocation)

View File

@ -20,12 +20,13 @@ import de.bixilon.kutil.time.TimeUtil.nanos
import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.system.base.buffer.uniform.IntUniformBuffer
import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader
import de.bixilon.minosoft.gui.rendering.system.base.texture.data.buffer.TextureBuffer
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.Texture
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.memory.MemoryTexture
import de.bixilon.minosoft.gui.rendering.textures.TextureAnimation
import de.bixilon.minosoft.gui.rendering.textures.properties.AnimationFrame
import de.bixilon.minosoft.gui.rendering.textures.properties.AnimationProperties
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY_INSTANCE
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
@ -88,31 +89,29 @@ class SpriteAnimator(val context: RenderContext) {
buffer!!.use(shader, bufferName)
}
fun create(texture: Texture, properties: AnimationProperties): Pair<AnimationProperties.FrameData, TextureAnimation> {
val data = properties.create(texture.size)
fun create(texture: Texture, source: TextureBuffer, properties: AnimationProperties): Pair<AnimationProperties.FrameData, TextureAnimation> {
val data = properties.create(source.size)
val source = texture.data.buffer
val textures: Array<Texture> = arrayOfNulls<Texture?>(data.textures).cast()
val sprites: Array<Texture> = arrayOfNulls<Texture?>(data.textures).cast()
for (i in 0 until data.textures) {
val buffer = source.create(data.size)
buffer.put(source, Vec2i(0, i * data.size.y), Vec2i.EMPTY, data.size)
buffer.put(source, Vec2i(0, i * buffer.size.y), Vec2i.EMPTY_INSTANCE, data.size)
textures[i] = MemoryTexture(size = data.size, texture.properties, texture.mipmaps, buffer)
sprites[i] = MemoryTexture(size = data.size, texture.properties, texture.mipmaps, buffer)
}
val frames: Array<AnimationFrame> = arrayOfNulls<AnimationFrame?>(properties.frames.size).cast()
val frames: Array<AnimationFrame> = arrayOfNulls<AnimationFrame?>(data.frames.size).cast()
for ((index, frame) in data.frames.withIndex()) {
var sprite = textures.getOrNull(frame.texture)
var sprite = sprites.getOrNull(frame.texture)
if (sprite == null) {
Log.log(LogMessageType.LOADING, LogLevels.WARN) { "Animation is referencing invalid frame: $texture (frame=${frame.texture})" }
sprite = textures.first()
sprite = sprites.first()
}
frames[index] = AnimationFrame(index, frame.time, sprite)
}
val animation = TextureAnimation(animations.size, frames, properties.interpolate, textures)
val animation = TextureAnimation(animations.size, frames, properties.interpolate, sprites)
this.animations += animation
return Pair(data, animation)

View File

@ -58,7 +58,4 @@ interface Texture : ShaderTexture {
if (mipmaps <= 0) return TextureData(buffer)
return MipmapTextureData(buffer, mipmaps)
}
fun updateAnimation(size: Vec2i, animation: TextureAnimation) = Unit
fun updateProperties(properties: ImageProperties) = Unit
}

View File

@ -13,23 +13,112 @@
package de.bixilon.minosoft.gui.rendering.system.base.texture.texture.file
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.assets.util.InputStreamUtil.readJson
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.data.TextureData
import de.bixilon.minosoft.gui.rendering.system.base.texture.data.buffer.TextureBuffer
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.Texture
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.TextureRenderData
import de.bixilon.minosoft.gui.rendering.textures.TextureAnimation
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.readTexture
import de.bixilon.minosoft.gui.rendering.textures.properties.AnimationProperties
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import java.io.FileNotFoundException
interface FileTexture : Texture {
val resourceLocation: ResourceLocation
abstract class FileTexture(
val file: ResourceLocation,
override var mipmaps: Int,
) : Texture {
override lateinit var renderData: TextureRenderData
override lateinit var array: TextureArrayProperties
override lateinit var size: Vec2i
override lateinit var transparency: TextureTransparencies
override lateinit var data: TextureData
override var properties: ImageProperties = ImageProperties.DEFAULT
override var animation: TextureAnimation? = null
override var state: TextureStates = TextureStates.DECLARED
@Synchronized
override fun load(context: RenderContext) {
load(context.connection.assetsManager)
if (state == TextureStates.LOADED) return
updateImageProperties(context.connection.assetsManager)
val properties = this.properties
val buffer = tryRead(context.connection.assetsManager)
if (properties.animation != null) {
loadSprites(context, properties.animation, buffer)
} else {
load(buffer)
}
state = TextureStates.LOADED
}
fun load(assets: AssetsManager)
private fun loadSprites(context: RenderContext, properties: AnimationProperties, buffer: TextureBuffer) {
val (frames, animation) = context.textures.static.animator.create(this, buffer, properties)
this.animation = animation
this.size = frames.size
var transparency = TextureTransparencies.OPAQUE
for (sprite in animation.sprites) {
when (sprite.transparency) {
TextureTransparencies.OPAQUE -> continue
TextureTransparencies.TRANSPARENT -> transparency = TextureTransparencies.TRANSPARENT
TextureTransparencies.TRANSLUCENT -> {
transparency = TextureTransparencies.TRANSLUCENT; break
}
}
}
this.transparency = transparency
}
private fun load(buffer: TextureBuffer) {
val data = createData(mipmaps, buffer)
this.size = data.size
this.transparency = buffer.getTransparency()
this.data = data
}
private fun tryRead(assets: AssetsManager): TextureBuffer {
try {
return read(assets)
} catch (error: Throwable) {
state = TextureStates.ERRORED
Log.log(LogMessageType.RENDERING, LogLevels.WARN) { "Can not load texture $file: $error" }
if (error !is FileNotFoundException) {
Log.log(LogMessageType.RENDERING, LogLevels.VERBOSE) { error }
}
return assets[RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION].readTexture()
}
}
protected abstract fun read(assets: AssetsManager): TextureBuffer
override fun toString(): String {
return file.toString()
}
private fun updateImageProperties(assets: AssetsManager) {
properties = assets.readImageProperties(file) ?: return
}
companion object {

View File

@ -13,89 +13,18 @@
package de.bixilon.minosoft.gui.rendering.system.base.texture.texture.file
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
import de.bixilon.minosoft.gui.rendering.system.base.texture.array.TextureArrayProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.data.TextureData
import de.bixilon.minosoft.gui.rendering.system.base.texture.data.buffer.TextureBuffer
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.TextureRenderData
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.file.FileTexture.Companion.readImageProperties
import de.bixilon.minosoft.gui.rendering.textures.TextureAnimation
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.readTexture
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import java.io.FileNotFoundException
class PNGTexture(
override val resourceLocation: ResourceLocation,
override var mipmaps: Int,
) : FileTexture {
override lateinit var renderData: TextureRenderData
override var animation: TextureAnimation? = null
file: ResourceLocation,
mipmaps: Int,
) : FileTexture(file, mipmaps) {
override lateinit var array: TextureArrayProperties
override var state: TextureStates = TextureStates.DECLARED
private set
override lateinit var size: Vec2i
private set
override lateinit var transparency: TextureTransparencies
private set
override var properties: ImageProperties = ImageProperties.DEFAULT
override lateinit var data: TextureData
private fun updateImageProperties(assets: AssetsManager) {
properties = assets.readImageProperties(resourceLocation) ?: return
}
@Synchronized
override fun load(assets: AssetsManager) {
if (state == TextureStates.LOADED) return
updateImageProperties(assets)
val buffer = readTexture(assets)
val data = createData(mipmaps, buffer)
this.size = data.size
this.transparency = buffer.getTransparency()
this.data = data
state = TextureStates.LOADED
}
override fun toString(): String {
return resourceLocation.toString()
}
private fun readTexture(assets: AssetsManager): TextureBuffer {
try {
return assets[resourceLocation].readTexture()
} catch (error: Throwable) {
state = TextureStates.ERRORED
Log.log(LogMessageType.RENDERING, LogLevels.WARN) { "Can not load texture $resourceLocation: $error" }
if (error !is FileNotFoundException) {
Log.log(LogMessageType.RENDERING, LogLevels.VERBOSE) { error }
}
return assets[RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION].readTexture()
}
}
override fun updateAnimation(size: Vec2i, animation: TextureAnimation) {
this.animation = animation
this.size = size
}
override fun updateProperties(properties: ImageProperties) {
this.properties = properties
override fun read(assets: AssetsManager): TextureBuffer {
return assets[file].readTexture()
}
}

View File

@ -27,7 +27,6 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.Texture
import de.bixilon.minosoft.gui.rendering.system.opengl.OpenGLRenderSystem
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureUtil.glFormat
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureUtil.glType
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
@ -85,7 +84,6 @@ class OpenGLFontTextureArray(
private fun load(texture: Texture) {
if (texture.state != TextureStates.LOADED) texture.load(context)
texture.updateProperties(ImageProperties.DEFAULT)
val pixel = 1.0f / resolution
val size = texture.size

View File

@ -134,25 +134,21 @@ class OpenGLTextureArray(
val uvEnd = if (size.x == resolution && size.y == resolution) null else Vec2(size) / resolution
val array = TextureArrayProperties(uvEnd, resolution, pixel)
val animationProperties = texture.properties.animation
if (animationProperties == null) {
val animation = texture.animation
if (animation == null) {
this.resolution[arrayId] += texture
texture.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, -1)
texture.array = array
return
}
val (frames, animation) = animator.create(texture, animationProperties)
texture.renderData = OpenGLTextureData(-1, -1, uvEnd, animation.animationData)
for (split in animation.textures) {
split.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, animation.animationData)
split.array = array
this.resolution[arrayId] += split
for (sprite in animation.sprites) {
sprite.renderData = OpenGLTextureData(arrayId, lastTextureId[arrayId]++, uvEnd, animation.animationData)
sprite.array = array
this.resolution[arrayId] += sprite
}
texture.updateAnimation(frames.size, animation)
}
override fun load(textures: Collection<Texture>) {

View File

@ -21,7 +21,7 @@ class TextureAnimation(
val animationData: Int,
val frames: Array<AnimationFrame>,
val interpolate: Boolean,
val textures: Array<Texture>,
val sprites: Array<Texture>,
) {
private val totalTime = frames.getTotalTime()
private var frame = frames.first()
@ -56,6 +56,7 @@ class TextureAnimation(
left -= frame.time
frame = frame.next()
}
this.frame = frame
this.time = left
this.frame1 = frame.texture
this.frame2 = frame.next().texture

View File

@ -33,16 +33,17 @@ data class AnimationProperties(
val count = size.y / height
val frames: MutableList<Frame> = mutableListOf()
val frameTime = ticksToSeconds(this.frameTime)
if (this.frames.isEmpty()) {
// automatic
for (i in 0 until count) {
frames += Frame(DEFAULT_FRAME_TIME, i)
frames += Frame(frameTime, i)
}
} else {
for (frame in this.frames) {
when (frame) {
is Number -> frames += Frame(DEFAULT_FRAME_TIME, frame.toInt())
is Number -> frames += Frame(frameTime, frame.toInt())
is Map<*, *> -> {
frames += Frame(ticksToSeconds(frame["time"].toInt()), frame["index"].toInt())
}
@ -65,8 +66,6 @@ data class AnimationProperties(
)
companion object {
val DEFAULT_FRAME_TIME = ticksToSeconds(1)
private fun ticksToSeconds(ticks: Int): Float {
val millis = ticks * ProtocolDefinition.TICK_TIME