[WIP] initial Fabric port

major package refactoring
This commit is contained in:
octarine-noise
2020-02-05 13:42:48 +01:00
parent 2252fb3b42
commit df50f61b0d
151 changed files with 5181 additions and 5663 deletions

View File

@@ -0,0 +1,114 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.findFirst
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormatElement
import net.minecraft.client.render.VertexFormatElement.Format.FLOAT
import net.minecraft.client.render.VertexFormatElement.Format.UBYTE
import net.minecraft.client.render.VertexFormatElement.Type.*
import net.minecraft.client.render.VertexFormatElement.Type.UV
import net.minecraft.client.render.VertexFormats
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BakedQuad
import net.minecraft.util.math.Direction
import java.lang.Float
import java.util.*
interface BakedModelConverter {
/**
* Convert baked model. Returns null if conversion unsuccessful (wrong input type).
* @param model Input model
* @param converter Converter to use for converting nested models.
*/
fun convert(model: BakedModel, converter: BakedModelConverter): BakedModel?
companion object {
fun of(func: (BakedModel, BakedModelConverter)->BakedModel?) = object : BakedModelConverter {
override fun convert(model: BakedModel, converter: BakedModelConverter) = func(model, converter)
}
val identity = of { model, _ -> model }
}
}
/**
* Convert [BakedModel] using the provided list of [BakedModelConverter]s (in order).
* If all converters fail, gives the original model back.
*/
fun List<BakedModelConverter>.convert(model: BakedModel) = object : BakedModelConverter {
val converters = this@convert + BakedModelConverter.identity
override fun convert(model: BakedModel, converter: BakedModelConverter) = converters.findFirst { it.convert(model, converter) }
}.let { converterStack ->
// we are guaranteed a result here because of the identity converter
converterStack.convert(model, converterStack)!!
}
/** List of converters without meaningful configuration that should always be used */
val COMMON_MESH_CONVERTERS = listOf(WrappedWeightedModel.converter)
/**
* Convert [BakedModel] into one using fabric-rendering-api [Mesh] instead of the vanilla pipeline.
* @param renderLayerOverride Use the given [BlockRenderLayer] for the [Mesh]
* instead of the one declared by the corresponding [Block]
*/
fun meshifyStandard(model: BakedModel, state: BlockState, renderLayerOverride: BlockRenderLayer? = null) =
(COMMON_MESH_CONVERTERS + WrappedMeshModel.converter(state, renderLayerOverride = renderLayerOverride)).convert(model)
/**
* Convert a vanilla [BakedModel] into intermediate [Quad]s
* Vertex normals not supported (yet)
* Vertex data elements not aligned to 32 bit boundaries not supported
*/
fun unbakeQuads(model: BakedModel, state: BlockState, random: Random, unshade: Boolean): List<Quad> {
return (allDirections.toList() + null as Direction?).flatMap { face ->
model.getQuads(state, face, random).mapIndexed { qIdx, bakedQuad ->
var quad = Quad(Vertex(), Vertex(), Vertex(), Vertex(), face = face, colorIndex = bakedQuad.colorIndex, sprite = bakedQuad.sprite)
val format = quadVertexFormat(bakedQuad)
val stride = format.vertexSizeInteger
format.getIntOffset(POSITION, FLOAT, 3)?.let { posOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(xyz = Double3(
x = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 0]).toDouble(),
y = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 1]).toDouble(),
z = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + posOffset + 2]).toDouble()
)) }
}
format.getIntOffset(COLOR, UBYTE, 4)?.let { colorOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(
color = Color(bakedQuad.vertexData[vIdx * stride + colorOffset])
) }
}
format.getIntOffset(UV, FLOAT, 2, 0)?.let { uvOffset ->
quad = quad.transformVI { vertex, vIdx -> vertex.copy(uv = UV(
u = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 0]).toDouble(),
v = Float.intBitsToFloat(bakedQuad.vertexData[vIdx * stride + uvOffset + 1]).toDouble()
)) }
}
quad = quad.transformV { it.copy(uv = it.uv.unbake(quad.sprite!!)) }.move(Double3(-0.5, -0.5, -0.5))
if (unshade) quad = quad.transformV { it.copy(color = it.color * (1.0f / Color.bakeShade(quad.face))) }
quad
}
}
}
/** Get the byte offset of the [VertexFormatElement] matching the given criteria */
fun VertexFormat.getByteOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0): Int? {
elements.forEachIndexed { idx, element ->
if (element.type == type && element.format == format && element.count == count && element.index == index)
return getElementOffset(idx)
}
return null
}
/**
* Get the int (32 bit) offset of the [VertexFormatElement] matching the given criteria
* Returns null if the element is not properly aligned
*/
fun VertexFormat.getIntOffset(type: VertexFormatElement.Type, format: VertexFormatElement.Format, count: Int, index: Int = 0) =
getByteOffset(type, format, count, index)?.let { if (it % 4 == 0) it / 4 else null }
/** Function to determine [VertexFormat] used by [BakedQuad] */
var quadVertexFormat: (BakedQuad)->VertexFormat = { VertexFormats.POSITION_COLOR_UV_LMAP }

View File

@@ -0,0 +1,230 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.*
import mods.betterfoliage.util.minmax
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Direction.*
import java.lang.Math.max
import java.lang.Math.min
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
/**
* Vertex UV coordinates
*
* Zero-centered: sprite coordinates fall between (-0.5, 0.5)
*/
data class UV(val u: Double, val v: Double) {
companion object {
val topLeft = UV(-0.5, -0.5)
val topRight = UV(0.5, -0.5)
val bottomLeft = UV(-0.5, 0.5)
val bottomRight = UV(0.5, 0.5)
}
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
else -> UV(-v, u)
}
fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
UV(u.minmax(minU, maxU), v.minmax(minV, maxV))
fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v)
fun unbake(sprite: Sprite) = UV(
(u - sprite.minU.toDouble()) / (sprite.maxU - sprite.minU).toDouble() - 0.5,
(v - sprite.minV.toDouble()) / (sprite.maxV - sprite.minV).toDouble() - 0.5
)
}
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(combined shr 24 and 255, combined shr 16 and 255, combined shr 8 and 255, combined and 255)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
/** Amount of vanilla diffuse lighting applied to face quads */
fun bakeShade(dir: Direction?) = when(dir) {
DOWN -> 0.5f
NORTH, SOUTH -> 0.8f
EAST, WEST -> 0.6f
else -> 1.0f
}
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
}
/**
* Model vertex
*
* @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates
* @param[color] vertex color RGB components
* @param[alpha] vertex color alpha component
*/
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
val alpha: Int = 255,
val normal: Double3? = null
)
/**
* Intermediate (fabric-renderer-api independent) representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: Sprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad = copy(
v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3)
)
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun rotate(rot: Rotation) = transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot))
fun rotateZ(angle: Double) = transformV { it.copy(
xyz = Double3(it.xyz.x * cos(angle) + it.xyz.z * sin(angle), it.xyz.y, it.xyz.z * cos(angle) - it.xyz.x * sin(angle)),
normal = it.normal?.let { normal-> Double3(normal.x * cos(angle) + normal.z * sin(angle), normal.y, normal.z * cos(angle) - normal.x * sin(angle)) }
) }
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) }
fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun sprite(sprite: Sprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Int?) = color(color ?: Color.white.asInt).colorIndex(if (color == null) 0 else -1)
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when(n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex)-> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
}
}
fun List<Quad>.transform(trans: Quad.()-> Quad) = map { it.trans() }
fun Array<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }.toTypedArray()
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun Array<List<Quad>>.withOpposites() = map { it.withOpposites() }.toTypedArray()
/**
* Pour quad data into a fabric-renderer-api Mesh
*/
fun List<Quad>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false): Mesh {
val renderer = RendererAccess.INSTANCE.renderer
val material = renderer.materialFinder().blendMode(0, layer).disableAo(0, flatLighting).disableDiffuse(0, noDiffuse).find()
val builder = renderer.meshBuilder()
builder.emitter.apply {
forEach { quad ->
val sprite = quad.sprite ?: Atlas.BLOCKS.atlas[MissingSprite.getMissingSpriteId()]!!
quad.verts.forEachIndexed { idx, vertex ->
pos(idx, (vertex.xyz + Double3(0.5, 0.5, 0.5)).asVec3f)
sprite(idx, 0,
(sprite.maxU - sprite.minU) * (vertex.uv.u.toFloat() + 0.5f) + sprite.minU,
(sprite.maxV - sprite.minV) * (vertex.uv.v.toFloat() + 0.5f) + sprite.minV
)
spriteColor(idx, 0, vertex.color.asInt)
}
cullFace(quad.face)
colorIndex(quad.colorIndex)
material(material)
emit()
}
}
return builder.build()
}
fun Array<List<Quad>>.build(layer: BlockRenderLayer, noDiffuse: Boolean = false, flatLighting: Boolean = false) = map { it.build(layer, noDiffuse, flatLighting) }.toTypedArray()
/**
* The model should be positioned so that (0,0,0) is the block center.
* The block extends to (-0.5, 0.5) in all directions (inclusive).
*/
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2); val xMax = max(x1, x2)
val zMin = min(z1, z2); val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
Vertex(base + top + left, UV.topLeft),
Vertex(base - top + left, UV.bottomLeft),
Vertex(base - top - left, UV.bottomRight),
Vertex(base + top - left, UV.topRight),
face = face
)
}
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }

View File

@@ -0,0 +1,70 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
import net.minecraft.client.MinecraftClient
import net.minecraft.client.texture.MissingSprite
import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.util.Identifier
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface SpriteSet {
val num: Int
operator fun get(idx: Int): Sprite
}
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
constructor(atlas: Atlas, ids: List<Identifier>) : this(
ids.mapNotNull { atlas.atlas[it] }.let { sprites ->
if (sprites.isNotEmpty()) sprites else listOf(atlas.atlas[MissingSprite.getMissingSpriteId()]!!)
}
)
}
class SpriteDelegate(val atlas: Atlas, val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, ClientSpriteRegistryCallback {
private var id: Identifier? = null
private var value: Sprite? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
id = idFunc(); value = null
registry.register(id)
}
override fun getValue(thisRef: Any, property: KProperty<*>): Sprite {
value?.let { return it }
synchronized(this) {
value?.let { return it }
atlas.atlas[id!!]!!.let { value = it; return it }
}
}
}
class SpriteSetDelegate(val atlas: Atlas, val idRegister: (Identifier)->Identifier = { it }, val idFunc: (Int)->Identifier) : ReadOnlyProperty<Any, SpriteSet>, ClientSpriteRegistryCallback {
private var idList: List<Identifier> = emptyList()
private var spriteSet: SpriteSet? = null
init { ClientSpriteRegistryCallback.event(atlas.resourceId).register(this) }
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
spriteSet = null
val manager = MinecraftClient.getInstance().resourceManager
idList = (0 until 16).map(idFunc).filter { manager.containsResource(atlas.wrap(it)) }.map(idRegister)
idList.forEach { registry.register(it) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it }
synchronized(this) {
spriteSet?.let { return it }
spriteSet = FixedSpriteSet(atlas, idList)
return spriteSet!!
}
}
}

View File

@@ -0,0 +1,74 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.*
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.client.texture.Sprite
import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction.UP
data class TuftShapeKey(
val size: Double,
val height: Double,
val offset: Double3,
val flipU1: Boolean,
val flipU2: Boolean
)
fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array<TuftShapeKey> {
return Array(64) { idx ->
TuftShapeKey(
size,
randomD(heightMin, heightMax),
xzDisk(idx) * randomD(hOffset / 2.0, hOffset),
randomB(),
randomB()
)
}
}
fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
verticalRectangle(x1 = -0.5 * size, z1 = 0.5 * size, x2 = 0.5 * size, z2 = -0.5 * size, yBottom = 0.5, yTop = 0.5 + height)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, overrideColor: Int?, spriteGetter: (Int)->Sprite) = shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorAndIndex(overrideColor) }
.map { it.sprite(spriteGetter(idx)) }
}.toTypedArray()
fun fullCubeTextured(spriteId: Identifier, overrideColor: Int?, scrambleUV: Boolean = true): Mesh {
val sprite = Atlas.BLOCKS.atlas[spriteId]!!
return allDirections.map { faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) }
.map { it.colorAndIndex(overrideColor) }
.build(BlockRenderLayer.SOLID)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
return Array(num) { idx ->
listOf(
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41),
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.rotate(rot(UP))
).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
}
}
fun crossModelsTextured(leafBase: Array<List<Quad>>, overrideColor: Int?, scrambleUV: Boolean, spriteGetter: (Int)->Sprite) = leafBase.map { leaf ->
leaf.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorAndIndex(overrideColor) }
.mapIndexed { idx, quad -> quad.sprite(spriteGetter(idx)) }
.withOpposites().build(CUTOUT_MIPPED)
}.toTypedArray()
fun Array<List<Quad>>.buildTufts() = withOpposites().build(CUTOUT_MIPPED)

View File

@@ -0,0 +1,86 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.util.YarnHelper
import mods.betterfoliage.util.get
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext
import net.minecraft.block.BlockRenderLayer
import net.minecraft.block.BlockState
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BasicBakedModel
import net.minecraft.client.render.model.WeightedBakedModel
import net.minecraft.item.ItemStack
import net.minecraft.util.WeightedPicker
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ExtendedBlockView
import java.util.*
import java.util.function.Supplier
// net.minecraft.client.render.model.WeightedBakedModel.totalWeight
val WeightedBakedModel_totalWeight = YarnHelper.requiredField<Int>("net.minecraft.class_1097", "field_5433", "I")
// net.minecraft.client.render.model.WeightedBakedModel.models
val WeightedBakedModel_models = YarnHelper.requiredField<List<WeightedPicker.Entry>>("net.minecraft.class_1097", "field_5434", "Ljava/util/List;")
// net.minecraft.client.render.model.WeightedBakedModel.ModelEntry.model
val WeightedBakedModelEntry_model = YarnHelper.requiredField<BakedModel>("net.minecraft.class_1097\$class_1099", "field_5437", "Lnet/minecraft/class_1087;")
// net.minecraft.util.WeightedPicker.Entry.weight
val WeightedPickerEntry_weight = YarnHelper.requiredField<Int>("net.minecraft.class_3549\$class_3550", "field_15774", "I")
abstract class WrappedBakedModel(val wrapped: BakedModel) : BakedModel by wrapped, FabricBakedModel {
override fun isVanillaAdapter() = false
override fun emitItemQuads(stack: ItemStack, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitItemQuads(stack, randomSupplier, context)
}
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(wrapped as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
}
class WrappedMeshModel(wrapped: BasicBakedModel, val mesh: Mesh) : WrappedBakedModel(wrapped) {
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
context.meshConsumer().accept(mesh)
}
companion object {
/**
* Converter for [BasicBakedModel] instances.
* @param state [BlockState] to use when querying [BakedModel]
* @param unshade undo vanilla diffuse lighting when unbaking the [BakedModel]
* @param noDiffuse disable diffuse lighting when baking the [Mesh]
* @param renderLayerOverride [BlockRenderLayer] to use instead of the one declared by the corresponding [Block]
*/
fun converter(state: BlockState, unshade: Boolean = false, noDiffuse: Boolean = true, renderLayerOverride: BlockRenderLayer? = null) = BakedModelConverter.of { model, _ ->
if (model is BasicBakedModel) {
val mesh = unbakeQuads(model, state, Random(42L), unshade).build(
layer = renderLayerOverride ?: state.block.renderLayer,
noDiffuse = noDiffuse,
flatLighting = !model.useAmbientOcclusion()
)
WrappedMeshModel(model, mesh)
} else null
}
}
}
class WrappedWeightedModel(wrapped: WeightedBakedModel, transformer: BakedModelConverter) : WrappedBakedModel(wrapped) {
val totalWeight = wrapped[WeightedBakedModel_totalWeight] as Int
val models = wrapped[WeightedBakedModel_models]!!.map { entry ->
Entry(transformer.convert(entry[WeightedBakedModelEntry_model]!!, transformer)!!, entry[WeightedPickerEntry_weight]!!)
}
override fun emitBlockQuads(blockView: ExtendedBlockView, state: BlockState, pos: BlockPos, randomSupplier: Supplier<Random>, context: RenderContext) {
(WeightedPicker.getRandom(randomSupplier.get(), models, totalWeight).model as FabricBakedModel).emitBlockQuads(blockView, state, pos, randomSupplier, context)
}
class Entry(val model: BakedModel, weight: Int) : WeightedPicker.Entry(weight)
companion object {
val converter = object : BakedModelConverter {
override fun convert(model: BakedModel, converter: BakedModelConverter) =
(model as? WeightedBakedModel)?.let { WrappedWeightedModel(it, converter) }
}
}
}