Model loading rework (1.14 prep)
remove unnecessary complexity access sprites only at PostStitch
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import com.google.common.base.Joiner
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.client.render.BlockContext
|
||||
import mods.octarinecore.common.Int3
|
||||
import mods.octarinecore.common.config.IBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.filterValuesNotNull
|
||||
import mods.octarinecore.findFirst
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
||||
@@ -13,9 +17,12 @@ import net.minecraft.client.renderer.block.statemap.IStateMapper
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockAccess
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.client.model.IModel
|
||||
import net.minecraftforge.client.model.ModelLoader
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.fml.common.eventhandler.Event
|
||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
||||
@@ -24,41 +31,43 @@ import org.apache.logging.log4j.Logger
|
||||
|
||||
class LoadModelDataEvent(val loader: ModelLoader) : Event()
|
||||
|
||||
data class ModelVariant(
|
||||
val state: IBlockState,
|
||||
val modelLocation: ResourceLocation?,
|
||||
val weight: Int
|
||||
)
|
||||
interface ModelRenderRegistry<T> {
|
||||
operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos)
|
||||
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): T?
|
||||
}
|
||||
|
||||
interface ModelProcessor<T1, T2> {
|
||||
interface ModelRenderDataExtractor<T> {
|
||||
fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>?
|
||||
}
|
||||
|
||||
interface ModelRenderKey<T> {
|
||||
val logger: Logger?
|
||||
var variants: MutableMap<IBlockState, MutableList<ModelVariant>>
|
||||
var variantToKey: MutableMap<ModelVariant, T1>
|
||||
var variantToValue: Map<ModelVariant, T2>
|
||||
fun onPreStitch(atlas: TextureMap) {}
|
||||
fun resolveSprites(atlas: TextureMap): T
|
||||
}
|
||||
|
||||
fun addVariant(state: IBlockState, variant: ModelVariant) { variants.getOrPut(state) { mutableListOf() }.add(variant) }
|
||||
fun getVariant(state: IBlockState, rand: Int) = variants[state]?.let { it[rand % it.size] }
|
||||
fun putKeySingle(state: IBlockState, key: T1) {
|
||||
val variant = ModelVariant(state, null, 1)
|
||||
variants[state] = mutableListOf(variant)
|
||||
variantToKey[variant] = key
|
||||
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
|
||||
val subRegistries = mutableListOf<ModelRenderRegistry<T>>()
|
||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
|
||||
fun addRegistry(registry: ModelRenderRegistry<T>) {
|
||||
subRegistries.add(registry)
|
||||
MinecraftForge.EVENT_BUS.register(registry)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPostLoad() { }
|
||||
fun onPreStitch() { }
|
||||
abstract class ModelRenderRegistryBase<T> : ModelRenderRegistry<T>, ModelRenderDataExtractor<T> {
|
||||
open val logger: Logger? = null
|
||||
open val logName: String get() = this::class.java.name
|
||||
|
||||
fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel)
|
||||
fun processStitch(variant: ModelVariant, key: T1, atlas: TextureMap): T2?
|
||||
val stateToKey = mutableMapOf<IBlockState, ModelRenderKey<T>>()
|
||||
var stateToValue = mapOf<IBlockState, T>()
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.HIGHEST)
|
||||
fun clearBeforeLoadModelData(event: LoadModelDataEvent) {
|
||||
variants.clear()
|
||||
variantToKey.clear()
|
||||
}
|
||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = stateToValue[state]
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@SubscribeEvent
|
||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
||||
onPostLoad()
|
||||
stateToValue = emptyMap()
|
||||
|
||||
val stateMappings = Block.REGISTRY.flatMap { block ->
|
||||
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
|
||||
@@ -68,33 +77,41 @@ interface ModelProcessor<T1, T2> {
|
||||
|
||||
stateMappings.forEach { mapping ->
|
||||
if (mapping.key.block != null) stateModels[mapping.value]?.let { model ->
|
||||
processModelLoad(mapping.key, mapping.value, model)
|
||||
try {
|
||||
processModel(mapping.key, mapping.value, model)?.let { stateToKey[mapping.key] = it }
|
||||
} catch (e: Exception) {
|
||||
logger?.warn("Exception while trying to process model ${mapping.value}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@SubscribeEvent(priority = EventPriority.LOW)
|
||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
||||
onPreStitch()
|
||||
variantToValue = variantToKey.mapValues { processStitch(it.key, it.value, event.map) }.filterValuesNotNull()
|
||||
stateToKey.forEach { (_, key) -> key.onPreStitch(event.map) }
|
||||
}
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.LOW)
|
||||
fun handlePostStitch(event: TextureStitchEvent.Post) {
|
||||
stateToValue = stateToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
|
||||
stateToKey.clear()
|
||||
}
|
||||
}
|
||||
|
||||
interface TextureListModelProcessor<T2> : ModelProcessor<List<String>, T2> {
|
||||
val logName: String
|
||||
val matchClasses: IBlockMatcher
|
||||
val modelTextures: List<ModelTextureList>
|
||||
abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>() {
|
||||
|
||||
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) {
|
||||
val matchClass = matchClasses.matchingClass(state.block) ?: return
|
||||
abstract val matchClasses: IBlockMatcher
|
||||
abstract val modelTextures: List<ModelTextureList>
|
||||
|
||||
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>? {
|
||||
val matchClass = matchClasses.matchingClass(state.block) ?: return null
|
||||
logger?.log(Level.DEBUG, "$logName: block state ${state.toString()}")
|
||||
logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
|
||||
|
||||
val allModels = model.modelBlockAndLoc.distinctBy { it.second }
|
||||
if (allModels.isEmpty()) {
|
||||
logger?.log(Level.DEBUG, "$logName: no models found")
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
allModels.forEach { blockLoc ->
|
||||
@@ -107,53 +124,13 @@ interface TextureListModelProcessor<T2> : ModelProcessor<List<String>, T2> {
|
||||
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
|
||||
|
||||
if (textures.all { it.second != "missingno" }) {
|
||||
// found a valid variant (all required textures exist)
|
||||
val variant = ModelVariant(state, blockLoc.second, 1)
|
||||
addVariant(state, variant)
|
||||
variantToKey[variant] = textures.map { it.second }
|
||||
// found a valid model (all required textures exist)
|
||||
return processModel(state, textures.map { it.second} )
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): List<String>? {
|
||||
// val matchClass = matchClasses.matchingClass(state.block) ?: return null
|
||||
// logger?.log(Level.DEBUG, "$logName: block state ${state.toString()}")
|
||||
// logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
|
||||
//
|
||||
// val allModels = model.modelBlockAndLoc
|
||||
// if (allModels.isEmpty()) {
|
||||
// logger?.log(Level.DEBUG, "$logName: no models found")
|
||||
// return null
|
||||
// }
|
||||
// allModels.forEach { blockLoc ->
|
||||
// modelTextures.firstOrNull { blockLoc.derivesFrom(it.modelLocation) }?.let{ modelMatch ->
|
||||
// logger?.log(Level.DEBUG, "$logName: model ${blockLoc.second} matches ${modelMatch.modelLocation.toString()}")
|
||||
//
|
||||
// val textures = modelMatch.textureNames.map { it to blockLoc.first.resolveTextureName(it) }
|
||||
// val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
||||
// logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
|
||||
//
|
||||
// return if (textures.all { it.second != "missingno" }) textures.map { it.second } else null
|
||||
// }
|
||||
// }
|
||||
// logger?.log(Level.DEBUG, "$logName: no matching models found")
|
||||
// return null
|
||||
// }
|
||||
}
|
||||
|
||||
interface TextureMediatedRegistry<T1, T3> : ModelProcessor<T1, TextureAtlasSprite> {
|
||||
|
||||
var textureToValue: MutableMap<TextureAtlasSprite, T3>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
||||
textureToValue.clear()
|
||||
super.handlePreStitch(event)
|
||||
|
||||
val textureToVariants = variantToValue.entries.groupBy(keySelector = { it.value }, valueTransform = { it.key })
|
||||
variantToValue.values.toSet().forEach { processTexture(textureToVariants[it]!!, it, event.map) }
|
||||
}
|
||||
|
||||
fun processTexture(states: List<ModelVariant>, texture: TextureAtlasSprite, atlas: TextureMap)
|
||||
abstract fun processModel(state: IBlockState, textures: List<String>) : ModelRenderKey<T>?
|
||||
}
|
||||
@@ -20,7 +20,10 @@ import java.util.*
|
||||
// ============================
|
||||
// Resource types
|
||||
// ============================
|
||||
interface IStitchListener { fun onStitch(atlas: TextureMap) }
|
||||
interface IStitchListener {
|
||||
fun onPreStitch(atlas: TextureMap)
|
||||
fun onPostStitch(atlas: TextureMap)
|
||||
}
|
||||
interface IConfigChangeListener { fun onConfigChange() }
|
||||
interface IWorldLoadListener { fun onWorldLoad(world: World) }
|
||||
|
||||
@@ -34,7 +37,8 @@ interface IWorldLoadListener { fun onWorldLoad(world: World) }
|
||||
open class ResourceHandler(val modId: String) {
|
||||
|
||||
val resources = mutableListOf<Any>()
|
||||
open fun afterStitch() {}
|
||||
open fun afterPreStitch() {}
|
||||
open fun afterPostStitch() {}
|
||||
|
||||
// ============================
|
||||
// Self-registration
|
||||
@@ -46,7 +50,7 @@ open class ResourceHandler(val modId: String) {
|
||||
// ============================
|
||||
fun iconStatic(domain: String, path: String) = IconHolder(domain, path).apply { resources.add(this) }
|
||||
fun iconStatic(location: ResourceLocation) = iconStatic(location.namespace, location.path)
|
||||
fun iconSet(domain: String, pathPattern: String) = IconSet(domain, pathPattern).apply { resources.add(this) }
|
||||
fun iconSet(domain: String, pathPattern: String) = IconSet(domain, pathPattern).apply { this@ResourceHandler.resources.add(this) }
|
||||
fun iconSet(location: ResourceLocation) = iconSet(location.namespace, location.path)
|
||||
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) }
|
||||
fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) }
|
||||
@@ -57,9 +61,15 @@ open class ResourceHandler(val modId: String) {
|
||||
// Event registration
|
||||
// ============================
|
||||
@SubscribeEvent
|
||||
fun onStitch(event: TextureStitchEvent.Pre) {
|
||||
resources.forEach { (it as? IStitchListener)?.onStitch(event.map) }
|
||||
afterStitch()
|
||||
fun onPreStitch(event: TextureStitchEvent.Pre) {
|
||||
resources.forEach { (it as? IStitchListener)?.onPreStitch(event.map) }
|
||||
afterPreStitch()
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun onPostStitch(event: TextureStitchEvent.Post) {
|
||||
resources.forEach { (it as? IStitchListener)?.onPostStitch(event.map) }
|
||||
afterPostStitch()
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -76,8 +86,10 @@ open class ResourceHandler(val modId: String) {
|
||||
// Resource container classes
|
||||
// ============================
|
||||
class IconHolder(val domain: String, val name: String) : IStitchListener {
|
||||
val iconRes = ResourceLocation(domain, name)
|
||||
var icon: TextureAtlasSprite? = null
|
||||
override fun onStitch(atlas: TextureMap) { icon = atlas.registerSprite(ResourceLocation(domain, name)) }
|
||||
override fun onPreStitch(atlas: TextureMap) { atlas.registerSprite(iconRes) }
|
||||
override fun onPostStitch(atlas: TextureMap) { icon = atlas[iconRes] }
|
||||
}
|
||||
|
||||
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
|
||||
@@ -86,18 +98,23 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
|
||||
}
|
||||
|
||||
class IconSet(val domain: String, val namePattern: String) : IStitchListener {
|
||||
val resources = arrayOfNulls<ResourceLocation>(16)
|
||||
val icons = arrayOfNulls<TextureAtlasSprite>(16)
|
||||
var num = 0
|
||||
|
||||
override fun onStitch(atlas: TextureMap) {
|
||||
override fun onPreStitch(atlas: TextureMap) {
|
||||
num = 0
|
||||
(0..15).forEach { idx ->
|
||||
icons[idx] = null
|
||||
val locReal = ResourceLocation(domain, "textures/${namePattern.format(idx)}.png")
|
||||
if (resourceManager[locReal] != null) icons[num++] = atlas.registerSprite(ResourceLocation(domain, namePattern.format(idx)))
|
||||
if (resourceManager[locReal] != null) resources[num++] = ResourceLocation(domain, namePattern.format(idx)).apply { atlas.registerSprite(this) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostStitch(atlas: TextureMap) {
|
||||
(0 until num).forEach { idx -> icons[idx] = atlas[resources[idx]!!] }
|
||||
}
|
||||
|
||||
operator fun get(idx: Int) = if (num == 0) null else icons[idx % num]
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryD
|
||||
|
||||
/** Index operator to get a texture sprite. */
|
||||
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
|
||||
|
||||
fun TextureMap.registerSprite(name: String): TextureAtlasSprite = registerSprite(ResourceLocation(name))!!
|
||||
operator fun TextureMap.get(res: ResourceLocation): TextureAtlasSprite? = getTextureExtry(res.toString())
|
||||
|
||||
/** Load an image resource. */
|
||||
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
|
||||
|
||||
@@ -37,9 +37,9 @@ class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, Bl
|
||||
}
|
||||
}
|
||||
|
||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>)
|
||||
|
||||
fun modelTextures(vararg args: String) = ModelTextureList(ResourceLocation(args[0]), listOf(*args).drop(1))
|
||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
|
||||
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
|
||||
}
|
||||
|
||||
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
|
||||
override fun convertValue(line: String): ModelTextureList? {
|
||||
|
||||
Reference in New Issue
Block a user