diff --git a/build.gradle.kts b/build.gradle.kts
index 6838e1d60..03144f2d4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -79,6 +79,7 @@ val architecture = properties["architecture"]?.let { Architectures[it] } ?: Plat
logger.info("Building for ${os.name.lowercase()}, ${architecture.name.lowercase()}")
repositories {
+ mavenLocal()
mavenCentral()
maven(url = "https://s01.oss.sonatype.org/content/repositories/releases/")
}
diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRenderTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRenderTest.kt
new file mode 100644
index 000000000..d3e7f7f45
--- /dev/null
+++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRenderTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Minosoft
+ * Copyright (C) 2020-2025 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.models.block.state.render
+
+import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
+import de.bixilon.kutil.unsafe.UnsafeUtil.setUnsafeAccessible
+import de.bixilon.minosoft.data.direction.Directions
+import de.bixilon.minosoft.data.world.positions.BlockPosition
+import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedModel
+import de.bixilon.minosoft.gui.rendering.models.block.state.render.WeightedBlockRender.WeightedEntry
+import de.bixilon.minosoft.test.ITUtil.allocate
+import org.testng.Assert.assertSame
+import org.testng.annotations.Test
+import java.util.*
+
+@Test(groups = ["rendering"])
+class WeightedBlockRenderTest {
+ private val get = WeightedBlockRender::class.java.getDeclaredMethod("getModel", Random::class.java, BlockPosition::class.java).apply { setUnsafeAccessible() }
+ private val modelA = BakedModel::class.java.allocate().apply { this::properties.forceSet(arrayOfNulls(Directions.SIZE)) }
+ private val modelB = BakedModel::class.java.allocate().apply { this::properties.forceSet(arrayOfNulls(Directions.SIZE)) }
+
+ private val position = BlockPosition(1, 2, 3)
+
+ private fun WeightedBlockRender.getModel(random: Random?, position: BlockPosition): BakedModel {
+ return get.invoke(this, random, position) as BakedModel
+ }
+
+ private fun create(models: Array): WeightedBlockRender {
+ return WeightedBlockRender(models, models.sumOf { it.weight })
+ }
+
+ fun `single model no random`() {
+ val render = create(arrayOf(WeightedEntry(3, modelA)))
+
+ assertSame(render.getModel(null, position), modelA)
+ }
+
+ fun `single model random`() {
+ val render = create(arrayOf(WeightedEntry(3, modelA)))
+
+ assertSame(render.getModel(Random(), position), modelA)
+ }
+
+ fun `two models no random`() {
+ val render = create(arrayOf(WeightedEntry(3, modelA), WeightedEntry(3, modelB)))
+
+ assertSame(render.getModel(null, position), modelA)
+ }
+
+ fun `two models random`() {
+ val render = create(arrayOf(WeightedEntry(3, modelA), WeightedEntry(3, modelB)))
+
+ assertSame(render.getModel(Random(0L), position), modelB)
+ }
+
+ fun `two models high weight random`() {
+ val render = create(arrayOf(WeightedEntry(100, modelA), WeightedEntry(100, modelB)))
+
+ assertSame(render.getModel(Random(0L), position), modelB)
+ }
+
+ fun `two models random 2`() {
+ val render = create(arrayOf(WeightedEntry(3, modelA), WeightedEntry(3, modelB)))
+
+ assertSame(render.getModel(Random(0L), BlockPosition(3, 1, 1)), modelA)
+ }
+
+ fun `two models high weight random 2`() {
+ val render = create(arrayOf(WeightedEntry(100, modelA), WeightedEntry(100, modelB)))
+
+ assertSame(render.getModel(Random(0L), BlockPosition(1, 1, 1)), modelA)
+ }
+
+ /* fun `benchmark models random`() {
+ val render = create(arrayOf(WeightedEntry(1, modelA), WeightedEntry(1, modelB), WeightedEntry(1, modelA), WeightedEntry(1, modelB)))
+
+ val random = Random(0L)
+ val time = measureTime {
+ for (i in 0 until 499999999) {
+ render.getModel(random, position)
+ }
+ }
+ println("Took: ${time.inWholeNanoseconds.formatNanos()}")
+ }
+ */
+}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRender.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRender.kt
index ae0cbf6c7..8adc2296f 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRender.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WeightedBlockRender.kt
@@ -1,6 +1,6 @@
/*
* Minosoft
- * Copyright (C) 2020-2023 Moritz Zwerger
+ * Copyright (C) 2020-2025 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.
*
@@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.render
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.kotlinglm.vec3.Vec3i
+import de.bixilon.kutil.array.ArrayUtil.cast
import de.bixilon.kutil.exception.Broken
import de.bixilon.minosoft.data.container.stack.ItemStack
import de.bixilon.minosoft.data.direction.Directions
@@ -39,6 +40,7 @@ class WeightedBlockRender(
val totalWeight: Int,
) : BlockRender {
private val properties = models.getProperties()
+ private val unpacked = unpack(totalWeight, models)
override fun getProperties(direction: Directions): SideProperties? {
return properties[direction.ordinal] // TODO: get random block model
@@ -50,6 +52,10 @@ class WeightedBlockRender(
var weightLeft = abs(random.nextLong().toInt() % totalWeight)
+ if (unpacked != null) {
+ return unpacked[weightLeft]
+ }
+
for ((weight, model) in models) {
weightLeft -= weight
if (weightLeft >= 0) continue
@@ -118,4 +124,24 @@ class WeightedBlockRender(
return sizes
}
+
+ companion object {
+ const val UNPACK_LIMIT = 20
+
+ private fun unpack(total: Int, models: Array): Array? {
+ if (total >= UNPACK_LIMIT) return null
+
+ val unpacked = arrayOfNulls(total)
+
+ var index = 0
+ for ((weight, model) in models) {
+ for (i in 0 until weight) {
+ unpacked[index] = model
+ index++
+ }
+ }
+
+ return unpacked.cast()
+ }
+ }
}