From 1a6b8d04a7b7bb314b68d3d68f940376c388d376 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Fri, 16 Jun 2023 01:18:18 +0200 Subject: [PATCH] wip bitmap font loading + tests --- .../font/types/bitmap/BitmapFontTypeTest.kt | 75 +++++++++++++++ .../system/dummy/texture/DummyTexture.kt | 4 +- .../rendering/font/types/PostInitFontType.kt | 2 +- .../font/types/bitmap/BitmapCodeRenderer.kt | 1 + .../font/types/bitmap/BitmapFontType.kt | 92 ++++++++++++++++++- .../font/types/empty/EmptyCodeRenderer.kt | 2 +- 6 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontTypeTest.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontTypeTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontTypeTest.kt new file mode 100644 index 000000000..d130fde5a --- /dev/null +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontTypeTest.kt @@ -0,0 +1,75 @@ +package de.bixilon.minosoft.gui.rendering.font.types.bitmap + +import de.bixilon.kotlinglm.vec2.Vec2 +import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft +import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer +import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture +import de.bixilon.minosoft.gui.rendering.system.dummy.texture.DummyTexture +import org.testng.Assert.* +import org.testng.annotations.Test +import java.nio.ByteBuffer +import java.util.stream.IntStream +import kotlin.reflect.full.companionObject + + +@Test(groups = ["font"]) +class BitmapFontTypeTest { + private val LOAD = BitmapFontType::class.companionObject!!.java.getDeclaredMethod("load", AbstractTexture::class.java, Int::class.java, Int::class.java, Array::class.java).apply { isAccessible = true } + + private fun createTexture(start: IntArray, end: IntArray, width: Int, height: Int): AbstractTexture { + check(start.size == end.size) + + val buffer = ByteBuffer.allocate((start.size / 16 + 1) * 16 * width * height * 4) + + for (row in 0..start.size / 16) { + for (char in 0 until 16) { + val start = start.getOrNull((row * 16) + char) ?: 0 + val end = end.getOrNull((row * 16) + char) ?: width + + for (pixel in 0 until width) { + buffer.put(0xFF.toByte()) + buffer.put(0xFF.toByte()) + buffer.put(0xFF.toByte()) + + if (pixel in start..end) { + buffer.put(0xFF.toByte()) + } else { + buffer.put(0x00.toByte()) + } + } + } + } + + val texture = DummyTexture(minosoft("test")) + texture.data = buffer + + return texture + } + + private fun load(start: IntArray, end: IntArray, width: Int = 8, height: Int = 8, ascent: Int = 8, chars: Array): BitmapFontType { + val texture = createTexture(start, end, width, height) + + val fontType = LOAD(BitmapFontType, texture, height, ascent, chars.map { IntStream.of(*it) }.toTypedArray()) as BitmapFontType + + + return fontType + } + + fun `space size`() { + val font = load(intArrayOf(), intArrayOf(), chars = arrayOf(intArrayOf('a'.code))) + + val a = font['a'.code]!! as EmptyCodeRenderer + assertEquals(a.width, 4) + } + + fun `load basic with default options`() { + val font = load(intArrayOf(1, 2, 3), intArrayOf(7, 4, 6), chars = arrayOf(intArrayOf('a'.code, 'b'.code, 'c'.code))) + + val a = font['a'.code]!! as BitmapCodeRenderer + assertEquals(a.width, 7) + assertEquals(a.uvStart, Vec2(0.0078125, 0)) + assertEquals(a.uvEnd, Vec2(0.0625, 0)) + + // TODO + } +} diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/system/dummy/texture/DummyTexture.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/system/dummy/texture/DummyTexture.kt index 53cdffa8b..569cd853c 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/system/dummy/texture/DummyTexture.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/system/dummy/texture/DummyTexture.kt @@ -35,9 +35,7 @@ class DummyTexture( override val transparency: TextureTransparencies get() = TextureTransparencies.OPAQUE override var properties: ImageProperties = ImageProperties() override var renderData: TextureRenderData = DummyTextureRenderData - override var data: ByteBuffer? - get() = TODO("Not yet implemented") - set(value) {} + override var data: ByteBuffer? = null override var mipmapData: Array? get() = TODO("Not yet implemented") set(value) {} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/PostInitFontType.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/PostInitFontType.kt index d19781835..6d0cee084 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/PostInitFontType.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/PostInitFontType.kt @@ -17,5 +17,5 @@ import de.bixilon.kutil.latch.AbstractLatch interface PostInitFontType : FontType { - fun postInit(latch: AbstractLatch) = Unit + fun postInit(latch: AbstractLatch) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapCodeRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapCodeRenderer.kt index f1438e322..46f2c214d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapCodeRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapCodeRenderer.kt @@ -23,6 +23,7 @@ class BitmapCodeRenderer( override var uvStart: Vec2, override var uvEnd: Vec2, override val width: Float, + val ascent1: Int, ) : RasterizedCodePointRenderer, AscentedCodePointRenderer { override val ascent: Float get() = 1.0f diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontType.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontType.kt index a055f79be..941eb6a78 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontType.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/bitmap/BitmapFontType.kt @@ -13,31 +13,47 @@ package de.bixilon.minosoft.gui.rendering.font.types.bitmap +import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kutil.json.JsonObject +import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.kutil.primitive.IntUtil.toInt import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft import de.bixilon.minosoft.data.registries.identified.ResourceLocation import de.bixilon.minosoft.gui.rendering.RenderContext -import de.bixilon.minosoft.gui.rendering.font.types.FontType +import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer +import de.bixilon.minosoft.gui.rendering.font.types.PostInitFontType +import de.bixilon.minosoft.gui.rendering.font.types.empty.EmptyCodeRenderer import de.bixilon.minosoft.gui.rendering.font.types.factory.FontTypeFactory +import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.nbt.tag.NBTUtil.listCast import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import java.nio.ByteBuffer +import java.util.stream.IntStream class BitmapFontType( - val chars: Int2ObjectOpenHashMap, -) : FontType { + val chars: Int2ObjectOpenHashMap, +) : PostInitFontType { init { chars.trim() } - override fun get(codePoint: Int): BitmapCodeRenderer { + override fun get(codePoint: Int): CodePointRenderer? { return chars[codePoint] } + override fun postInit(latch: AbstractLatch) { + for (char in chars.values) { + if (char !is BitmapCodeRenderer) continue + char.updateArray() + } + } + + companion object : FontTypeFactory { + private const val ROW = 16 override val identifier = minecraft("bitmap") override fun build(context: RenderContext, data: JsonObject): BitmapFontType? { @@ -48,12 +64,78 @@ class BitmapFontType( return load(file, height, ascent, chars, context) } + private fun List.codePoints(): Array { + return this.map { it.codePoints() }.toTypedArray() + } + private fun load(file: ResourceLocation, height: Int, ascent: Int, chars: List, context: RenderContext): BitmapFontType? { + if (chars.isEmpty() || height <= 0) return null val texture = context.textureManager.staticTextures.createTexture(file) texture.load(context.connection.assetsManager) // force load it, we need to calculate the width of every char + return load(texture, height, ascent, chars.codePoints()) + } + + private fun ByteBuffer.scanLine(y: Int, width: Int, start: IntArray, end: IntArray) { + for (index in 0 until (width * ROW)) { + val alpha = this[(((ROW * width) * y) + index) * 4 + 3].toInt() + if (alpha == 0) { + // transparent + continue + } + + val char = index / width + val pixel = index % width + + start[char] = minOf(start[char], pixel) + end[char] = maxOf(end[char], pixel) + } + } + + private fun createRenderer(texture: AbstractTexture, row: Int, column: Int, start: Int, end: Int, height: Int, ascent: Int): CodePointRenderer { + if (end < start) return EmptyCodeRenderer() + + val uvStart = Vec2() + val uvEnd = Vec2(0.1f) + + val width = 1.0f + + // TODO - return null + + return BitmapCodeRenderer(texture, uvStart, uvEnd, width, ascent) + } + + private fun load(texture: AbstractTexture, height: Int, ascent: Int, chars: Array): BitmapFontType? { + val rows = chars.size + val width = texture.size.x / ROW + + val start = IntArray(ROW) { width } + val end = IntArray(ROW) + + val renderer = Int2ObjectOpenHashMap() + + for (row in 0 until rows) { + val iterator = chars[row].iterator() + + for (pixel in 0 until height) { + texture.data!!.scanLine(row + pixel, width, start, end) + } + + var column = 0 + while (iterator.hasNext()) { + val codePoint = iterator.nextInt() + renderer[codePoint] = createRenderer(texture, row, column, start[column], end[column], height, ascent) + column++ + } + + start.fill(width); end.fill(0) // fill with maximum values again + } + + texture.data!!.rewind() + + if (renderer.isEmpty()) return null + return BitmapFontType(renderer) } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/empty/EmptyCodeRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/empty/EmptyCodeRenderer.kt index 09737451c..0d4a5e25d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/empty/EmptyCodeRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/types/empty/EmptyCodeRenderer.kt @@ -22,7 +22,7 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions data class EmptyCodeRenderer( - val width: Int, + val width: Int = 4, ) : CodePointRenderer { init {