diff --git a/src/main/kotlin/mods/betterfoliage/client/Client.kt b/src/main/kotlin/mods/betterfoliage/client/Client.kt index 2a9db5f..1cd18aa 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Client.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Client.kt @@ -15,7 +15,6 @@ import net.minecraft.block.Block import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.util.math.BlockPos -import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentTranslation import net.minecraft.util.text.TextFormatting import net.minecraftforge.fml.client.FMLClientHandler @@ -65,21 +64,29 @@ object Client { RenderConnectedGrassLog() ) - // init singletons + // init other singletons val singletons = listOf( + StandardCactusRegistry, + LeafParticleRegistry, ChunkOverlayManager, - LeafRegistry, - GrassRegistry, LeafWindTracker, - RisingSoulTextures, + RisingSoulTextures + ) + + // init mod integrations + val integrations = listOf( ShadersModIntegration, OptifineCustomColors, ForestryIntegration, - IC2Integration, - TechRebornIntegration, - StandardLogSupport // add _after_ all other log registries + IC2RubberIntegration, + TechRebornRubberIntegration ) + // add basic block support instances as last + GrassRegistry.addRegistry(StandardGrassRegistry) + LeafRegistry.addRegistry(StandardLeafRegistry) + LogRegistry.addRegistry(StandardLogRegistry) + // init config hotkey val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") { FMLClientHandler.instance().showGuiScreen( @@ -88,7 +95,6 @@ object Client { } } - fun log(level: Level, msg: String) { BetterFoliageMod.log.log(level, "[BetterFoliage] $msg") BetterFoliageMod.logDetail.log(level, msg) diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt index 03ae17f..4c9c565 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/ForestryIntegration.kt @@ -3,38 +3,47 @@ package mods.betterfoliage.client.integration import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.* -import mods.betterfoliage.client.texture.ILeafRegistry +import mods.betterfoliage.client.render.LogRegistry +import mods.betterfoliage.client.render.StandardLogRegistry +import mods.betterfoliage.client.render.column.ColumnTextureInfo +import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.betterfoliage.client.texture.LeafInfo import mods.betterfoliage.client.texture.LeafRegistry +import mods.betterfoliage.client.texture.StandardLeafKey import mods.betterfoliage.loader.Refs -import mods.octarinecore.client.resource.ModelProcessor -import mods.octarinecore.client.resource.ModelVariant -import mods.octarinecore.client.resource.registerSprite +import mods.octarinecore.client.resource.ModelRenderKey +import mods.octarinecore.client.resource.ModelRenderRegistry +import mods.octarinecore.client.resource.ModelRenderRegistryBase import mods.octarinecore.getTileEntitySafe -import mods.octarinecore.isBlockLoaded import mods.octarinecore.metaprog.ClassRef import mods.octarinecore.metaprog.FieldRef import mods.octarinecore.metaprog.MethodRef import mods.octarinecore.metaprog.allAvailable -import mods.octarinecore.tryDefault import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft import net.minecraft.client.renderer.block.model.ModelResourceLocation -import net.minecraft.client.renderer.texture.TextureMap -import net.minecraft.util.EnumFacing 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.common.MinecraftForge import net.minecraftforge.fml.common.Loader +import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly import org.apache.logging.log4j.Level - +import kotlin.collections.Map +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.emptyMap +import kotlin.collections.find +import kotlin.collections.forEach +import kotlin.collections.get +import kotlin.collections.listOf +import kotlin.collections.mapValues +import kotlin.collections.mutableMapOf +import kotlin.collections.set @SideOnly(Side.CLIENT) object ForestryIntegration { @@ -64,50 +73,18 @@ object ForestryIntegration { init { if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) { Client.log(Level.INFO, "Forestry support initialized") - LeafRegistry.subRegistries.add(ForestryLeavesSupport) - LogRegistry.subRegistries.add(ForestryLogSupport) + LeafRegistry.addRegistry(ForestryLeafRegistry) + LogRegistry.addRegistry(ForestryLogRegistry) } } } -@SideOnly(Side.CLIENT) -object ForestryLeavesSupport : ILeafRegistry { +object ForestryLeafRegistry : ModelRenderRegistry { + val logger = BetterFoliageMod.logDetail + val textureToKey = mutableMapOf>() + var textureToValue = emptyMap() - val textureToValue = mutableMapOf() - - init { MinecraftForge.EVENT_BUS.register(this) } - - @SubscribeEvent - fun handlePreStitch(event: TextureStitchEvent.Pre) { - textureToValue.clear() - val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *> - allLeaves.entries.forEach { - Client.logDetail("ForestryLeavesSupport: base leaf type ${it.key.toString()}") - listOf( - ForestryIntegration.TeLplain.get(it.value) as ResourceLocation, - ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation, - ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation, - ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation - ).forEach { leafLocation -> - registerLeaf(leafLocation, event.map) - } - } - } - - fun registerLeaf(textureLocation: ResourceLocation, atlas: TextureMap) { - val texture = atlas.registerSprite(textureLocation) - val leafType = LeafRegistry.typeMappings.getType(texture) ?: "default" - Client.logDetail("ForestryLeavesSupport: texture ${texture.iconName}") - Client.logDetail("ForestryLeavesSupport: particle $leafType") - val generated = atlas.registerSprite( - Client.genLeaves.generatedResource(texture.iconName, "type" to leafType) - ) - textureToValue[textureLocation] = LeafInfo(generated, LeafRegistry.getParticleType(texture, atlas)) - } - - override fun get(state: IBlockState, rand: Int) = null - - override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): LeafInfo? { + override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? { // check variant property (used in decorative leaves) state.properties.entries.find { ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value) @@ -124,44 +101,54 @@ object ForestryLeavesSupport : ILeafRegistry { val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null return textureToValue[textureLoc] } + + @SubscribeEvent + fun handlePreStitch(event: TextureStitchEvent.Pre) { + textureToValue = emptyMap() + + val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *> + allLeaves.entries.forEach { + logger.log(Level.DEBUG, "ForestryLeavesSupport: base leaf type ${it.key.toString()}") + listOf( + ForestryIntegration.TeLplain.get(it.value) as ResourceLocation, + ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation, + ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation, + ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation + ).forEach { textureLocation -> + val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) } + textureToKey[textureLocation] = key + } + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + fun handlePostStitch(event: TextureStitchEvent.Post) { + textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) } + textureToKey.clear() + } } -@SideOnly(Side.CLIENT) -object ForestryLogSupport : ModelProcessor, IColumnTextureInfo>, IColumnRegistry { - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf>() - override var variantToValue = mapOf() - +object ForestryLogRegistry : ModelRenderRegistryBase() { override val logger = BetterFoliageMod.logDetail - init { MinecraftForge.EVENT_BUS.register(this) } - - override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { + override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey? { // respect class list to avoid triggering on fences, stairs, etc. - if (!Config.blocks.logClasses.matchesClass(state.block)) return + if (!Config.blocks.logClasses.matchesClass(state.block)) return null // find wood type property val woodType = state.properties.entries.find { ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value) - } ?: return + } ?: return null - logger.log(Level.DEBUG, "ForestryLogSupport: block state ${state.toString()}") - logger.log(Level.DEBUG, "ForestryLogSupport: variant ${woodType.value.toString()}") + logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state") + logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}") // get texture names for wood type val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String? val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String? logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]") - if (bark != null && heart != null) putKeySingle(state, listOf(heart, bark)) + if (bark != null && heart != null) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark)) + return null } - - override fun processStitch(variant: ModelVariant, key: List, atlas: TextureMap): IColumnTextureInfo? { - val heart = atlas.registerSprite(key[0]) - val bark = atlas.registerSprite(key[1]) - return StaticColumnInfo(StandardLogSupport.getAxis(variant.state), heart, heart, listOf(bark)) - } - - override fun get(state: IBlockState, rand: Int) = variants[state]?.let { variantToValue[it[0]] } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt index a69a26d..f792731 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt @@ -2,13 +2,9 @@ package mods.betterfoliage.client.integration import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.render.IColumnRegistry -import mods.betterfoliage.client.render.IColumnTextureInfo import mods.betterfoliage.client.render.LogRegistry -import mods.betterfoliage.client.render.StaticColumnInfo -import mods.betterfoliage.client.texture.StandardLeafSupport -import mods.betterfoliage.loader.Refs +import mods.betterfoliage.client.render.column.ColumnTextureInfo +import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.octarinecore.client.render.Quad import mods.octarinecore.client.render.QuadIconResolver import mods.octarinecore.client.render.ShadingContext @@ -16,7 +12,6 @@ import mods.octarinecore.client.render.blockContext import mods.octarinecore.client.resource.* import mods.octarinecore.common.rotate import mods.octarinecore.metaprog.ClassRef -import mods.octarinecore.metaprog.MethodRef import mods.octarinecore.metaprog.allAvailable import net.minecraft.block.state.IBlockState import net.minecraft.client.renderer.block.model.ModelResourceLocation @@ -25,7 +20,6 @@ import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.util.EnumFacing import net.minecraft.util.ResourceLocation import net.minecraftforge.client.model.IModel -import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.common.Loader import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly @@ -33,91 +27,68 @@ import org.apache.logging.log4j.Level import org.apache.logging.log4j.Logger @SideOnly(Side.CLIENT) -object IC2Integration { +object IC2RubberIntegration { val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood") init { if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) { - Client.log(Level.INFO, "IC2 support initialized") - LogRegistry.subRegistries.add(IC2LogSupport) + Client.log(Level.INFO, "IC2 rubber support initialized") + LogRegistry.addRegistry(IC2LogSupport) } } } @SideOnly(Side.CLIENT) -object TechRebornIntegration { +object TechRebornRubberIntegration { val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog") init { if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) { - Client.log(Level.INFO, "TechReborn support initialized") - LogRegistry.subRegistries.add(TechRebornLogSupport) + Client.log(Level.INFO, "TechReborn rubber support initialized") + LogRegistry.addRegistry(TechRebornLogSupport) } } } -@SideOnly(Side.CLIENT) -data class RubberLogModelInfo( - val axis: EnumFacing.Axis?, - val spotDir: EnumFacing?, - val textures: List -) +class RubberLogInfo( + axis: EnumFacing.Axis?, + val spotDir: EnumFacing, + topTexture: TextureAtlasSprite, + bottomTexture: TextureAtlasSprite, + val spotTexture: TextureAtlasSprite, + sideTextures: List +) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) { -// TODO avoid copy-paste pattern with regards to StaticColumnInfo -@SideOnly(Side.CLIENT) -data class RubberLogColumnInfo(override val axis: EnumFacing.Axis?, - val spotDir: EnumFacing, - val topTexture: TextureAtlasSprite, - val bottomTexture: TextureAtlasSprite, - val sideTexture: TextureAtlasSprite, - val spotTexture: TextureAtlasSprite): IColumnTextureInfo { - override val top: QuadIconResolver = { _, _, _ -> topTexture } - override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad -> - val worldRelativeSide = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation) - if (worldRelativeSide == spotDir) spotTexture else sideTexture + val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation) + if (worldFace == spotDir) spotTexture else { + val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0 + this.sideTextures[sideIdx] + } + } + + class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val spotDir: EnumFacing, val textures: List): ModelRenderKey { + override fun resolveSprites(atlas: TextureMap) = RubberLogInfo( + axis, + spotDir, + atlas[textures[0]] ?: atlas.missingSprite, + atlas[textures[1]] ?: atlas.missingSprite, + atlas[textures[2]] ?: atlas.missingSprite, + textures.drop(3).map { atlas[it] ?: atlas.missingSprite } + ) } } -@SideOnly(Side.CLIENT) -abstract class RubberLogSupportBase : ModelProcessor, IColumnRegistry { - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf() - override var variantToValue = mapOf() - +object IC2LogSupport : ModelRenderRegistryBase() { override val logger = BetterFoliageMod.logDetail - init { MinecraftForge.EVENT_BUS.register(this) } - - override fun processStitch(variant: ModelVariant, key: RubberLogModelInfo, atlas: TextureMap): IColumnTextureInfo? { - val topTex = atlas.registerSprite(key.textures[0]) - val bottomTex = atlas.registerSprite(key.textures[1]) - val sideTex = atlas.registerSprite(key.textures[2]) - if (key.spotDir == null) - return StaticColumnInfo(key.axis, topTex, bottomTex, listOf(sideTex)) - else { - val spotTex = atlas.registerSprite(key.textures[3]) - return RubberLogColumnInfo(key.axis, key.spotDir, topTex, bottomTex, sideTex, spotTex) - } - } - - override fun get(state: IBlockState, rand: Int): IColumnTextureInfo? { - val variant = getVariant(state, rand) ?: return null - return variantToValue[variant] - } -} - -@SideOnly(Side.CLIENT) -object IC2LogSupport : RubberLogSupportBase() { - - override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { + override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey? { // check for proper block class, existence of ModelBlock, and "state" blockstate property - if (!IC2Integration.BlockRubWood.isInstance(state.block)) return - val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return - val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return + if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null + val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null + val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null // logs with no rubber spot if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) { @@ -128,12 +99,10 @@ object IC2LogSupport : RubberLogSupportBase() { else -> null } val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) } + if (textureNames.any { it == "missingno" }) return null logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}") logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}") - if (textureNames.all { it != "missingno" }) { - putKeySingle(state, RubberLogModelInfo(axis, null, textureNames)) - } - return + return SimpleColumnInfo.Key(logger, axis, textureNames) } // logs with rubber spot @@ -144,39 +113,35 @@ object IC2LogSupport : RubberLogSupportBase() { "dry_east", "wet_east" -> EnumFacing.EAST else -> null } - val textureNames = listOf("up", "down", "south", "north").map { blockLoc.first.resolveTextureName(it) } + val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) } + if (textureNames.any { it == "missingno" }) return null logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}") logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}") - if (textureNames.all { it != "missingno" }) { - putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, spotDir, textureNames)) - } + return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames) } } -@SideOnly(Side.CLIENT) -object TechRebornLogSupport : RubberLogSupportBase() { +object TechRebornLogSupport : ModelRenderRegistryBase() { + override val logger = BetterFoliageMod.logDetail - override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { + override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey? { // check for proper block class, existence of ModelBlock - if (!TechRebornIntegration.BlockRubberLog.isInstance(state.block)) return - val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return + if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null + val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null - val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return - val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return + val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null + val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null - val textureNames = listOf("end", "end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) } - - logger.log(Level.DEBUG, "TechRebornLogSupport: block state ${state.toString()}") + logger.log(Level.DEBUG, "$logName: block state $state") if (hasSap) { - logger.log(Level.DEBUG, "TechRebornLogSupport: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}") - if (textureNames.all { it != "missingno" }) { - putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, sapSide, textureNames)) - } + val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) } + logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}") + if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames) } else { - logger.log(Level.DEBUG, "TechRebornLogSupport: end=${textureNames[0]}, side=${textureNames[2]}") - if (textureNames.all { it != "missingno" }) { - putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, null, textureNames)) - } + val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) } + logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}") + if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames) } + return null } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt index 533b71b..37cebc6 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt @@ -1,6 +1,7 @@ package mods.betterfoliage.client.render import mods.betterfoliage.client.config.Config +import mods.betterfoliage.client.texture.LeafParticleRegistry import mods.betterfoliage.client.texture.LeafRegistry import mods.octarinecore.PI2 import mods.octarinecore.client.render.AbstractEntityFX @@ -8,10 +9,8 @@ import mods.octarinecore.client.render.HSB import mods.octarinecore.common.Double3 import mods.octarinecore.minmax import mods.octarinecore.random -import mods.octarinecore.semiRandom import net.minecraft.client.Minecraft import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.EnumFacing.DOWN import net.minecraft.util.math.BlockPos import net.minecraft.util.math.MathHelper import net.minecraft.world.World @@ -45,12 +44,12 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble val state = world.getBlockState(pos) val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0) - val leafInfo = LeafRegistry.get(state, world, pos, DOWN, semiRandom(pos.x, pos.y, pos.z, 0)) + val leafInfo = LeafRegistry[state, world, pos] if (leafInfo != null) { - particleTexture = leafInfo.particleTextures?.get(rand.nextInt(1024)) + particleTexture = leafInfo.particleTextures[rand.nextInt(1024)] calculateParticleColor(leafInfo.averageColor, blockColor) } else { - particleTexture = LeafRegistry.particles["default"]?.get(rand.nextInt(1024)) + particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)] setColor(blockColor) } } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt index 2e556f0..0950a9c 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt @@ -71,7 +71,7 @@ object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) { val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d") val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track") - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${headIcons.num} soul particle textures") } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt index 255601b..4d4d49d 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt @@ -23,7 +23,7 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d") val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)) - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${algaeIcons.num} algae textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt index f92776f..b0fd30a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt @@ -3,19 +3,18 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config +import mods.betterfoliage.client.render.column.ColumnTextureInfo +import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.octarinecore.client.render.* -import mods.octarinecore.client.resource.ModelVariant -import mods.octarinecore.client.resource.TextureListModelProcessor -import mods.octarinecore.client.resource.registerSprite +import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable import mods.octarinecore.common.Int3 import mods.octarinecore.common.Rotation +import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.SimpleBlockMatcher -import mods.octarinecore.common.config.modelTextures import net.minecraft.block.BlockCactus import net.minecraft.block.state.IBlockState import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.util.BlockRenderLayer import net.minecraft.util.EnumFacing.* import net.minecraftforge.common.MinecraftForge @@ -23,6 +22,14 @@ import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly import org.apache.logging.log4j.Level +object StandardCactusRegistry : ModelRenderRegistryConfigurable() { + override val logger = BetterFoliageMod.logDetail + override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java) + override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side")) + override fun processModel(state: IBlockState, textures: List) = SimpleColumnInfo.Key(logger, Axis.Y, textures) + init { MinecraftForge.EVENT_BUS.register(this) } +} + @SideOnly(Side.CLIENT) class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { @@ -32,34 +39,6 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus") val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d") - val cactusTextures: IColumnRegistry = object : TextureListModelProcessor, IColumnRegistry { - - init { MinecraftForge.EVENT_BUS.register(this) } - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf>() - override var variantToValue = mapOf() - - override val logger = BetterFoliageMod.logDetail - override val logName = "CactusTextures" - override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java) - override val modelTextures = listOf( - modelTextures("block/cactus", "top", "bottom", "side") - ) - - override fun processStitch(variant: ModelVariant, key: List, atlas: TextureMap): IColumnTextureInfo? { - val topTex = atlas.registerSprite(key[0]) - val bottomTex = atlas.registerSprite(key[1]) - val sideTex = atlas.registerSprite(key[2]) - return StaticColumnInfo(Axis.Y, topTex, bottomTex, listOf(sideTex)) - } - - override fun get(state: IBlockState, rand: Int): IColumnTextureInfo? { - val variant = getVariant(state, rand) ?: return null - return variantToValue[variant] - } - } - val modelStem = model { horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5) .scaleUV(cactusStemRadius * 2.0) @@ -88,7 +67,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { .toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll() } - override fun afterStitch() { + override fun afterPreStitch() { Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures") } @@ -102,7 +81,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { // get AO data modelRenderer.updateShading(Int3.zero, allFaces) - val icons = cactusTextures[ctx.blockState(Int3.zero), ctx.random(0)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null) + val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null) modelRenderer.render( renderer, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt index 69a07d0..73758e6 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt @@ -40,7 +40,7 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { } } - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${coralIcons.num} coral textures") Client.log(INFO, "Registered ${crustIcons.num} coral crust textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt index 594d7fd..218a206 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt @@ -41,7 +41,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val grassModels = modelSet(64, grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)) - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${normalIcons.num} grass textures") Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures") } @@ -49,7 +49,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { override fun isEligible(ctx: BlockContext) = Config.enabled && (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && - GrassRegistry[ctx, UP] != null + GrassRegistry[ctx] != null override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean { // render the whole block on the cutout layer @@ -62,8 +62,8 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val isSnowed = ctx.blockState(up1).isSnow val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled) - val grassInfo = GrassRegistry[ctx, UP] - if (grassInfo == null) { + val grass = GrassRegistry[ctx] + if (grass == null) { // shouldn't happen Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) return renderWorldBlockBase(ctx, dispatcher, renderer, null) @@ -83,12 +83,12 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { renderer, fullCube, quadFilter = { qi, _ -> !isHidden[qi] }, - icon = { _, _, _ -> grassInfo.grassTopTexture }, + icon = { _, _, _ -> grass.grassTopTexture }, postProcess = { ctx, _, _, _, _ -> rotateUV(2) if (isSnowed) { if (!ctx.aoEnabled) setGrey(1.4f) - } else if (ctx.aoEnabled && grassInfo.overrideColor == null) multiplyColor(blockColor) + } else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor) } ) } @@ -116,7 +116,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { Rotation.identity, ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! }, - postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grassInfo.overrideColor ?: blockColor) } + postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) } ) } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt index ed7bcfa..0fcbc53 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt @@ -45,14 +45,14 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { override fun isEligible(ctx: BlockContext) = Config.enabled && Config.leaves.enabled && - LeafRegistry[ctx, DOWN] != null && + LeafRegistry[ctx] != null && !(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } ) override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean { val isSnowed = ctx.blockState(up1).material.let { it == Material.SNOW || it == Material.CRAFTED_SNOW } - val leafInfo = LeafRegistry[ctx, DOWN] + val leafInfo = LeafRegistry[ctx] if (leafInfo == null) { // shouldn't happen Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt index 4beea88..75bd24f 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt @@ -34,7 +34,7 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d") val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset } - override fun afterStitch() { + override fun afterPreStitch() { Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures") Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt index 45bca91..8e1fdc3 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt @@ -3,23 +3,21 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.config.Config +import mods.betterfoliage.client.render.column.AbstractRenderColumn +import mods.betterfoliage.client.render.column.ColumnRenderLayer +import mods.betterfoliage.client.render.column.ColumnTextureInfo +import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.octarinecore.client.render.BlockContext -import mods.octarinecore.client.resource.ModelVariant -import mods.octarinecore.client.resource.TextureListModelProcessor -import mods.octarinecore.client.resource.registerSprite +import mods.octarinecore.client.resource.* import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ModelTextureList -import mods.octarinecore.findFirst import mods.octarinecore.tryDefault import net.minecraft.block.BlockLog import net.minecraft.block.state.IBlockState -import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.util.EnumFacing.Axis -import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly - class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { override val addToCutout: Boolean get() = false @@ -38,7 +36,7 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { } class RoundLogOverlayLayer : ColumnRenderLayer() { - override val registry: IColumnRegistry get() = LogRegistry + override val registry: ModelRenderRegistry get() = LogRegistry override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) } override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) } @@ -48,44 +46,17 @@ class RoundLogOverlayLayer : ColumnRenderLayer() { } @SideOnly(Side.CLIENT) -object LogRegistry : IColumnRegistry { - val subRegistries: MutableList = mutableListOf() - override fun get(state: IBlockState, rand: Int) = subRegistries.findFirst { it[state, rand] } -} - -@SideOnly(Side.CLIENT) -object StandardLogSupport : TextureListModelProcessor, IColumnRegistry { - - init { - LogRegistry.subRegistries.add(this) - MinecraftForge.EVENT_BUS.register(this) - } - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf>() - override var variantToValue = mapOf() +object LogRegistry : ModelRenderRegistryRoot() +object StandardLogRegistry : ModelRenderRegistryConfigurable() { override val logger = BetterFoliageMod.logDetail - override val logName = "StandardLogSupport" override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses override val modelTextures: List get() = Config.blocks.logModels.list - - override fun processStitch(variant: ModelVariant, key: List, atlas: TextureMap): IColumnTextureInfo? { - val topTex = atlas.registerSprite(key[0]) - val bottomTex = atlas.registerSprite(key[1]) - val sideTexList = key.drop(2).map { atlas.registerSprite(it) } - if (sideTexList.isEmpty()) return null - return StaticColumnInfo(getAxis(variant.state), topTex, bottomTex, sideTexList) - } - - override fun get(state: IBlockState, rand: Int): IColumnTextureInfo? { - val variant = getVariant(state, rand) ?: return null - return variantToValue[variant] - } + override fun processModel(state: IBlockState, textures: List) = SimpleColumnInfo.Key(logger, getAxis(state), textures) fun getAxis(state: IBlockState): Axis? { val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?: - state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString() + state.properties.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString() return when (axis) { "x" -> Axis.X "y" -> Axis.Y diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt index 2062d88..24b0d4a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt @@ -22,7 +22,7 @@ class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d") val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)) - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt index 913f44d..2ef9df1 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt @@ -28,7 +28,7 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) } - override fun afterStitch() { + override fun afterPreStitch() { Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt index 963e001..a810d65 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt @@ -40,7 +40,7 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { } } - override fun afterStitch() { + override fun afterPreStitch() { Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures") } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt similarity index 51% rename from src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt rename to src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt index 97a5689..cd2bfc0 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt @@ -1,15 +1,16 @@ -package mods.betterfoliage.client.render +package mods.betterfoliage.client.render.column import mods.betterfoliage.client.Client import mods.betterfoliage.client.chunk.ChunkOverlayLayer import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs -import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.BlockType.* -import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType -import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType.* +import mods.betterfoliage.client.render.* +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.* +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.octarinecore.client.render.* +import mods.octarinecore.client.resource.ModelRenderRegistry import mods.octarinecore.common.* import net.minecraft.block.state.IBlockState import net.minecraft.client.renderer.BlockRendererDispatcher @@ -22,80 +23,6 @@ import net.minecraft.world.IBlockAccess import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly -@SideOnly(Side.CLIENT) -interface IColumnTextureInfo { - val axis: Axis? - val top: QuadIconResolver - val bottom: QuadIconResolver - val side: QuadIconResolver -} - -/** - * Sealed class hierarchy for all possible render outcomes - */ -@SideOnly(Side.CLIENT) -sealed class ColumnLayerData { - /** - * Data structure to cache texture and world neighborhood data relevant to column rendering - */ - @Suppress("ArrayInDataClass") // not used in comparisons anywhere - @SideOnly(Side.CLIENT) - data class SpecialRender( - val column: IColumnTextureInfo, - val upType: BlockType, - val downType: BlockType, - val quadrants: Array, - val quadrantsTop: Array, - val quadrantsBottom: Array - ) : ColumnLayerData() { - enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR } - enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE } - } - - /** Column block should not be rendered at all */ - @SideOnly(Side.CLIENT) - object SkipRender : ColumnLayerData() - - /** Column block must be rendered normally */ - @SideOnly(Side.CLIENT) - object NormalRender : ColumnLayerData() - - /** Error while resolving render data, column block must be rendered normally */ - @SideOnly(Side.CLIENT) - object ResolveError : ColumnLayerData() -} - -@SideOnly(Side.CLIENT) -interface IColumnRegistry { - operator fun get(state: IBlockState, rand: Int): IColumnTextureInfo? -} - -@SideOnly(Side.CLIENT) -data class StaticColumnInfo(override val axis: Axis?, - val topTexture: TextureAtlasSprite, - val bottomTexture: TextureAtlasSprite, - val sideTextures: List) : IColumnTextureInfo { - - // index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture - val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5) - - override val top: QuadIconResolver = { _, _, _ -> topTexture } - override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } - override val side: QuadIconResolver = { ctx, idx, _ -> - val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation) - sideTextures[(blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size] - } -} - -/** Index of SOUTH-EAST quadrant. */ -const val SE = 0 -/** Index of NORTH-EAST quadrant. */ -const val NE = 1 -/** Index of NORTH-WEST quadrant. */ -const val NW = 2 -/** Index of SOUTH-WEST quadrant. */ -const val SW = 3 - @SideOnly(Side.CLIENT) @Suppress("NOTHING_TO_INLINE") abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) { @@ -293,128 +220,3 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl return true } } - -abstract class ColumnRenderLayer : ChunkOverlayLayer { - - abstract val registry: IColumnRegistry - abstract val blockPredicate: (IBlockState)->Boolean - abstract val surroundPredicate: (IBlockState) -> Boolean - abstract val connectSolids: Boolean - abstract val lenientConnect: Boolean - abstract val defaultToY: Boolean - - val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} - - override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) { - allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) } - } - - override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos)) - - fun calculate(ctx: BlockContext): ColumnLayerData { - if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender - val columnTextures = registry[ctx.blockState(Int3.zero), ctx.random(0)] ?: return ColumnLayerData.ResolveError - - // if log axis is not defined and "Default to vertical" config option is not set, render normally - val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender - - // check log neighborhood - val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] - - val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) - val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) - - val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0) - val quadrantsTop = Array(4) { SMALL_RADIUS } - if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1) - val quadrantsBottom = Array(4) { SMALL_RADIUS } - if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1) - return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom) - } - - /** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */ - inline fun Array.upgrade(idx: Int, value: QuadrantType) { - if (this[idx].ordinal < value.ordinal) this[idx] = value - } - - /** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */ - fun Array.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array { - val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1)) - val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0)) - val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1)) - val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0)) - - // a solid block on one side will make the 2 neighboring quadrants SQUARE - // if there are solid blocks to both sides of a quadrant, it is INVISIBLE - if (connectSolids) { - if (blkS == SOLID) { - upgrade(SW, SQUARE); upgrade(SE, SQUARE) - } - if (blkE == SOLID) { - upgrade(SE, SQUARE); upgrade(NE, SQUARE) - } - if (blkN == SOLID) { - upgrade(NE, SQUARE); upgrade(NW, SQUARE) - } - if (blkW == SOLID) { - upgrade(NW, SQUARE); upgrade(SW, SQUARE) - } - if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE) - if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE) - if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE) - if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE) - } - val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1)) - val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1)) - val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1)) - val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1)) - - if (lenientConnect) { - // if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants - if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) { - upgrade(SE, SQUARE); upgrade(NE, SQUARE) - } - if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) { - upgrade(NE, SQUARE); upgrade(NW, SQUARE) - } - if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) { - upgrade(NW, SQUARE); upgrade(SW, SQUARE) - } - if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) { - upgrade(SW, SQUARE); upgrade(SE, SQUARE) - } - } - - // if the block forms the middle of an L-shape, or is part of a 2x2 configuration, - // connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner - if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) { - upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE) - } - if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) { - upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE) - } - if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) { - upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE) - } - if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) { - upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE) - } - return this - } - - /** - * Get the type of the block at the given offset in a rotated reference frame. - */ - fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { - val offsetRot = offset.rotate(rotation) - val state = blockState(offsetRot) - return if (!blockPredicate(state)) { - if (state.isOpaqueCube) SOLID else NONSOLID - } else { - (registry[state, random(0)]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let { - if (it == axis) PARALLEL else PERPENDICULAR - } ?: SOLID - } - } -} - diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt new file mode 100644 index 0000000..a1a200e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt @@ -0,0 +1,190 @@ +package mods.betterfoliage.client.render.column + +import mods.betterfoliage.client.chunk.ChunkOverlayLayer +import mods.betterfoliage.client.chunk.ChunkOverlayManager +import mods.betterfoliage.client.config.Config +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.* +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType +import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* +import mods.betterfoliage.client.render.rotationFromUp +import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.resource.ModelRenderRegistry +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import mods.octarinecore.common.face +import mods.octarinecore.common.plus +import net.minecraft.block.state.IBlockState +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.BlockPos +import net.minecraft.world.IBlockAccess +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly + +/** Index of SOUTH-EAST quadrant. */ +const val SE = 0 +/** Index of NORTH-EAST quadrant. */ +const val NE = 1 +/** Index of NORTH-WEST quadrant. */ +const val NW = 2 +/** Index of SOUTH-WEST quadrant. */ +const val SW = 3 + +/** + * Sealed class hierarchy for all possible render outcomes + */ +@SideOnly(Side.CLIENT) +sealed class ColumnLayerData { + /** + * Data structure to cache texture and world neighborhood data relevant to column rendering + */ + @Suppress("ArrayInDataClass") // not used in comparisons anywhere + @SideOnly(Side.CLIENT) + data class SpecialRender( + val column: ColumnTextureInfo, + val upType: BlockType, + val downType: BlockType, + val quadrants: Array, + val quadrantsTop: Array, + val quadrantsBottom: Array + ) : ColumnLayerData() { + enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR } + enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE } + } + + /** Column block should not be rendered at all */ + @SideOnly(Side.CLIENT) + object SkipRender : ColumnLayerData() + + /** Column block must be rendered normally */ + @SideOnly(Side.CLIENT) + object NormalRender : ColumnLayerData() + + /** Error while resolving render data, column block must be rendered normally */ + @SideOnly(Side.CLIENT) + object ResolveError : ColumnLayerData() +} + + +abstract class ColumnRenderLayer : ChunkOverlayLayer { + + abstract val registry: ModelRenderRegistry + abstract val blockPredicate: (IBlockState)->Boolean + abstract val surroundPredicate: (IBlockState) -> Boolean + abstract val connectSolids: Boolean + abstract val lenientConnect: Boolean + abstract val defaultToY: Boolean + + val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} + + override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) { + allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) } + } + + override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos)) + + fun calculate(ctx: BlockContext): ColumnLayerData { + if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender + val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError + + // if log axis is not defined and "Default to vertical" config option is not set, render normally + val logAxis = columnTextures.axis ?: if (defaultToY) EnumFacing.Axis.Y else return ColumnLayerData.NormalRender + + // check log neighborhood + val baseRotation = rotationFromUp[(logAxis to EnumFacing.AxisDirection.POSITIVE).face.ordinal] + + val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) + val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) + + val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0) + val quadrantsTop = Array(4) { SMALL_RADIUS } + if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1) + val quadrantsBottom = Array(4) { SMALL_RADIUS } + if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1) + return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom) + } + + /** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */ + inline fun Array.upgrade(idx: Int, value: QuadrantType) { + if (this[idx].ordinal < value.ordinal) this[idx] = value + } + + /** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */ + fun Array.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: EnumFacing.Axis, yOff: Int): Array { + val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1)) + val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0)) + val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1)) + val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0)) + + // a solid block on one side will make the 2 neighboring quadrants SQUARE + // if there are solid blocks to both sides of a quadrant, it is INVISIBLE + if (connectSolids) { + if (blkS == SOLID) { + upgrade(SW, SQUARE); upgrade(SE, SQUARE) + } + if (blkE == SOLID) { + upgrade(SE, SQUARE); upgrade(NE, SQUARE) + } + if (blkN == SOLID) { + upgrade(NE, SQUARE); upgrade(NW, SQUARE) + } + if (blkW == SOLID) { + upgrade(NW, SQUARE); upgrade(SW, SQUARE) + } + if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE) + if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE) + if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE) + if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE) + } + val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1)) + val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1)) + val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1)) + val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1)) + + if (lenientConnect) { + // if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants + if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) { + upgrade(SE, SQUARE); upgrade(NE, SQUARE) + } + if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) { + upgrade(NE, SQUARE); upgrade(NW, SQUARE) + } + if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) { + upgrade(NW, SQUARE); upgrade(SW, SQUARE) + } + if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) { + upgrade(SW, SQUARE); upgrade(SE, SQUARE) + } + } + + // if the block forms the middle of an L-shape, or is part of a 2x2 configuration, + // connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner + if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) { + upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE) + } + if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) { + upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE) + } + if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) { + upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE) + } + if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) { + upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE) + } + return this + } + + /** + * Get the type of the block at the given offset in a rotated reference frame. + */ + fun BlockContext.blockType(rotation: Rotation, axis: EnumFacing.Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { + val offsetRot = offset.rotate(rotation) + val state = blockState(offsetRot) + return if (!blockPredicate(state)) { + if (state.isOpaqueCube) SOLID else NONSOLID + } else { + (registry[state, world!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) EnumFacing.Axis.Y else null)?.let { + if (it == axis) PARALLEL else PERPENDICULAR + } ?: SOLID + } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt new file mode 100644 index 0000000..6137182 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt @@ -0,0 +1,50 @@ +package mods.betterfoliage.client.render.column + +import mods.octarinecore.client.render.QuadIconResolver +import mods.octarinecore.client.render.blockContext +import mods.octarinecore.client.resource.ModelRenderKey +import mods.octarinecore.client.resource.get +import mods.octarinecore.common.rotate +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.util.EnumFacing +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly +import org.apache.logging.log4j.Logger + +@SideOnly(Side.CLIENT) +interface ColumnTextureInfo { + val axis: EnumFacing.Axis? + val top: QuadIconResolver + val bottom: QuadIconResolver + val side: QuadIconResolver +} + +@SideOnly(Side.CLIENT) +open class SimpleColumnInfo( + override val axis: EnumFacing.Axis?, + val topTexture: TextureAtlasSprite, + val bottomTexture: TextureAtlasSprite, + val sideTextures: List +) : ColumnTextureInfo { + + // index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture + val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5) + + override val top: QuadIconResolver = { _, _, _ -> topTexture } + override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } + override val side: QuadIconResolver = { ctx, idx, _ -> + val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation) + val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0 + sideTextures[sideIdx] + } + + class Key(override val logger: Logger, val axis: EnumFacing.Axis?, val textures: List) : ModelRenderKey { + override fun resolveSprites(atlas: TextureMap) = SimpleColumnInfo( + axis, + atlas[textures[0]] ?: atlas.missingSprite, + atlas[textures[1]] ?: atlas.missingSprite, + textures.drop(2).map { atlas[it] ?: atlas.missingSprite } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt index 9a6dd70..8c8ff50 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt @@ -1,12 +1,14 @@ package mods.betterfoliage.client.texture import mods.betterfoliage.BetterFoliageMod +import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.HSB import mods.octarinecore.client.resource.* import mods.octarinecore.common.Int3 import mods.octarinecore.common.config.ConfigurableBlockMatcher +import mods.octarinecore.common.config.IBlockMatcher import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.findFirst import net.minecraft.block.state.IBlockState @@ -19,6 +21,7 @@ import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly import org.apache.logging.log4j.Level +import org.apache.logging.log4j.Logger import java.lang.Math.min const val defaultGrassColor = 0 @@ -32,62 +35,25 @@ class GrassInfo( * Color to use for Short Grass rendering instead of the biome color. * * Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit), - * the average color of the texture (significantly ) otherwise. + * the average color of the texture otherwise. */ val overrideColor: Int? ) -interface IGrassRegistry { - operator fun get(state: IBlockState, rand: Int): GrassInfo? - operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): GrassInfo? -} - -/** Collects and manages rendering-related information for grass blocks. */ -@SideOnly(Side.CLIENT) -object GrassRegistry : IGrassRegistry { - val subRegistries: MutableList = mutableListOf(StandardGrassSupport) - - override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int) = - subRegistries.findFirst { it.get(state, world, pos, face, rand) } - - operator fun get(ctx: BlockContext, face: EnumFacing) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, face, ctx.random(0)) - - override fun get(state: IBlockState, rand: Int) = subRegistries.findFirst { it[state, rand] } -} - -object StandardGrassSupport : - TextureListModelProcessor, - TextureMediatedRegistry, GrassInfo>, - IGrassRegistry -{ - init { MinecraftForge.EVENT_BUS.register(this) } - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf>() - override var variantToValue = mapOf() - override var textureToValue = mutableMapOf() +object GrassRegistry : ModelRenderRegistryRoot() +object StandardGrassRegistry : ModelRenderRegistryConfigurable() { override val logger = BetterFoliageMod.logDetail - override val logName = "StandardGrassSupport" override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses override val modelTextures: List get() = Config.blocks.grassModels.list + override fun processModel(state: IBlockState, textures: List) = StandardGrassKey(logger, textures[0]) +} - override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): GrassInfo? { - val variant = getVariant(state, rand) ?: return null - val baseTexture = variantToValue[variant] ?: return null - return textureToValue[baseTexture] - } - - override fun get(state: IBlockState, rand: Int): GrassInfo? { - val variant = getVariant(state, rand) ?: return null - return variantToValue[variant].let { if (it == null) null else textureToValue[it] } - } - - override fun processStitch(variant: ModelVariant, key: List, atlas: TextureMap) = atlas.registerSprite(key[0]) - override fun processTexture(variants: List, texture: TextureAtlasSprite, atlas: TextureMap) { registerGrass(texture, atlas) } - - fun registerGrass(texture: TextureAtlasSprite, atlas: TextureMap) { - logger.log(Level.DEBUG, "$logName: texture ${texture.iconName}") +class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey { + override fun resolveSprites(atlas: TextureMap): GrassInfo { + val logName = "StandardGrassKey" + val texture = atlas[textureName] ?: atlas.missingSprite + logger.log(Level.DEBUG, "$logName: texture $textureName") val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor) val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) { logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}") @@ -97,7 +63,6 @@ object StandardGrassSupport : logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color") null } - - textureToValue[texture] = GrassInfo(texture, overrideColor) + return GrassInfo(texture, overrideColor) } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt new file mode 100644 index 0000000..223c877 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt @@ -0,0 +1,69 @@ +package mods.betterfoliage.client.texture + +import mods.betterfoliage.BetterFoliageMod +import mods.octarinecore.client.resource.* +import mods.octarinecore.stripStart +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.util.ResourceLocation +import net.minecraftforge.client.event.TextureStitchEvent +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object LeafParticleRegistry { + val typeMappings = TextureMatcher() + val particles = hashMapOf() + + operator fun get(type: String) = particles[type] ?: particles["default"]!! + + init { MinecraftForge.EVENT_BUS.register(this) } + + @SubscribeEvent(priority = EventPriority.HIGH) + fun handleLoadModelData(event: LoadModelDataEvent) { + particles.clear() + typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg")) + } + + @SubscribeEvent + fun handlePreStitch(event: TextureStitchEvent.Pre) { + val allTypes = (typeMappings.mappings.map { it.type } + "default").distinct() + allTypes.forEach { leafType -> + val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d").apply { onPreStitch(event.map) } + if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet + } + } + + @SubscribeEvent + fun handlePostStitch(event: TextureStitchEvent.Post) { + particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) } + } +} + +class TextureMatcher { + + data class Mapping(val domain: String?, val path: String, val type: String) { + fun matches(iconLocation: ResourceLocation): Boolean { + return (domain == null || domain == iconLocation.namespace) && + iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true) + } + } + + val mappings: MutableList = mutableListOf() + + fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull() + fun getType(iconName: String) = ResourceLocation(iconName).let { getType(it) } + + fun loadMappings(mappingLocation: ResourceLocation) { + mappings.clear() + resourceManager[mappingLocation]?.getLines()?.let { lines -> + lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line -> + val line2 = line.trim().split('=') + if (line2.size == 2) { + val mapping = line2[0].trim().split(':') + if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim())) + else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim())) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt index d7c0f48..445c3c9 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt @@ -7,6 +7,7 @@ import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.resource.* import mods.octarinecore.common.Int3 import mods.octarinecore.common.config.ConfigurableBlockMatcher +import mods.octarinecore.common.config.IBlockMatcher import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.findFirst import net.minecraft.block.state.IBlockState @@ -39,99 +40,31 @@ class LeafInfo( val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor ) { /** [IconSet] of the textures to use for leaf particles emitted from this block. */ - val particleTextures: IconSet? get() = LeafRegistry.particles[leafType] + val particleTextures: IconSet get() = LeafParticleRegistry[leafType] } -interface ILeafRegistry { - operator fun get(state: IBlockState, rand: Int): LeafInfo? - operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): LeafInfo? -} +object LeafRegistry : ModelRenderRegistryRoot() -/** Collects and manages rendering-related information for grass blocks. */ -object LeafRegistry : ILeafRegistry { - val subRegistries: MutableList = mutableListOf(StandardLeafSupport) - val typeMappings = TextureMatcher() - val particles = hashMapOf() - - init { MinecraftForge.EVENT_BUS.register(this) } - - @SubscribeEvent(priority = EventPriority.HIGHEST) - fun handlePreStitch(event: TextureStitchEvent.Pre) { - particles.clear() - typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.DOMAIN, "leaf_texture_mappings.cfg")) - } - - override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int) = - subRegistries.findFirst { it.get(state, world, pos, face, rand) } - - operator fun get(ctx: BlockContext, face: EnumFacing) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, face, ctx.random(0)) - - override fun get(state: IBlockState, rand: Int) = subRegistries.findFirst { it[state, rand] } - - fun getParticleType(texture: TextureAtlasSprite, atlas: TextureMap): String { - var leafType = typeMappings.getType(texture) ?: "default" - if (leafType !in particles.keys) { - val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d") - particleSet.onStitch(atlas) - if (particleSet.num == 0) { - Client.log(Level.WARN, "Leaf particle textures not found for leaf type: $leafType") - leafType = "default" - } else { - particles.put(leafType, particleSet) - } - } - return leafType - } -} - - -@SideOnly(Side.CLIENT) -object StandardLeafSupport : - TextureListModelProcessor, - TextureMediatedRegistry, LeafInfo>, - ILeafRegistry -{ - - init { MinecraftForge.EVENT_BUS.register(this) } - - override val logName = "StandardLeafSupport" +object StandardLeafRegistry : ModelRenderRegistryConfigurable() { + override val logger = BetterFoliageMod.logDetail override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses override val modelTextures: List get() = Config.blocks.leavesModels.list - override val logger: Logger? get() = BetterFoliageMod.logDetail - - override var variants = mutableMapOf>() - override var variantToKey = mutableMapOf>() - override var variantToValue = mapOf() - override var textureToValue = mutableMapOf() - - override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): LeafInfo? { - val variant = getVariant(state, rand) ?: return null - val baseTexture = variantToValue[variant] ?: return null - return textureToValue[baseTexture] - } - - override fun get(state: IBlockState, rand: Int): LeafInfo? { - val variant = getVariant(state, rand) ?: return null - return variantToValue[variant].let { if (it == null) null else textureToValue[it] } - } - - override fun processStitch(variant: ModelVariant, key: List, atlas: TextureMap) = atlas.registerSprite(key[0]) - - override fun processTexture(variants: List, texture: TextureAtlasSprite, atlas: TextureMap) { - logger?.log(Level.DEBUG, "$logName: leaf texture ${texture.iconName}") - logger?.log(Level.DEBUG, "$logName: #variants ${variants.size}") - logger?.log(Level.DEBUG, "$logName: #states ${variants.distinctBy { it.state }.size}") - registerLeaf(texture, atlas) - } - - fun registerLeaf(texture: TextureAtlasSprite, atlas: TextureMap) { - var leafType = LeafRegistry.typeMappings.getType(texture) ?: "default" - logger?.log(Level.DEBUG, "$logName: particle $leafType") - val generated = atlas.registerSprite( - Client.genLeaves.generatedResource(texture.iconName, "type" to leafType) - ) - textureToValue[texture] = LeafInfo(generated, LeafRegistry.getParticleType(texture, atlas)) - } - + override fun processModel(state: IBlockState, textures: List) = StandardLeafKey(logger, textures[0]) } +class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey { + lateinit var leafType: String + lateinit var generated: ResourceLocation + + override fun onPreStitch(atlas: TextureMap) { + val logName = "StandardLeafKey" + leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default" + generated = Client.genLeaves.generatedResource(textureName, "type" to leafType) + atlas.registerSprite(generated) + + logger.log(Level.DEBUG, "$logName: leaf texture $textureName") + logger.log(Level.DEBUG, "$logName: particle $leafType") + } + + override fun resolveSprites(atlas: TextureMap) = LeafInfo(atlas[generated] ?: atlas.missingSprite, leafType) +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/TextureMatcher.kt b/src/main/kotlin/mods/betterfoliage/client/texture/TextureMatcher.kt deleted file mode 100644 index 8c20fa0..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/texture/TextureMatcher.kt +++ /dev/null @@ -1,37 +0,0 @@ -package mods.betterfoliage.client.texture - -import mods.octarinecore.client.resource.resourceManager -import mods.octarinecore.client.resource.get -import mods.octarinecore.client.resource.getLines -import mods.octarinecore.stripStart -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.ResourceLocation - -class TextureMatcher() { - - data class Mapping(val domain: String?, val path: String, val type: String) { - fun matches(icon: TextureAtlasSprite): Boolean { - val iconLocation = ResourceLocation(icon.iconName) - return (domain == null || domain == iconLocation.namespace) && - iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true) - } - } - - val mappings: MutableList = mutableListOf() - - fun getType(icon: TextureAtlasSprite): String? = mappings.filter { it.matches(icon) }.map { it.type }.firstOrNull() - - fun loadMappings(mappingLocation: ResourceLocation) { - mappings.clear() - resourceManager[mappingLocation]?.getLines()?.let { lines -> - lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line -> - val line2 = line.trim().split('=') - if (line2.size == 2) { - val mapping = line2[0].trim().split(':') - if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim())) - else if (mapping.size == 2) mappings.add(Mapping(mapping[0].trim(), mapping[1].trim(), line2[1].trim())) - } - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt b/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt index bc8fbb0..0db7f8f 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt @@ -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 { + 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 { +interface ModelRenderDataExtractor { + fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey? +} + +interface ModelRenderKey { val logger: Logger? - var variants: MutableMap> - var variantToKey: MutableMap - var variantToValue: Map + 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 : ModelRenderRegistry { + val subRegistries = mutableListOf>() + override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] } + fun addRegistry(registry: ModelRenderRegistry) { + subRegistries.add(registry) + MinecraftForge.EVENT_BUS.register(registry) } +} - fun onPostLoad() { } - fun onPreStitch() { } +abstract class ModelRenderRegistryBase : ModelRenderRegistry, ModelRenderDataExtractor { + 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>() + var stateToValue = mapOf() - @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 { 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 : ModelProcessor, T2> { - val logName: String - val matchClasses: IBlockMatcher - val modelTextures: List +abstract class ModelRenderRegistryConfigurable : ModelRenderRegistryBase() { - override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { - val matchClass = matchClasses.matchingClass(state.block) ?: return + abstract val matchClasses: IBlockMatcher + abstract val modelTextures: List + + override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey? { + 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 : ModelProcessor, 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? { -// 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 : ModelProcessor { - - var textureToValue: MutableMap - - @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, texture: TextureAtlasSprite, atlas: TextureMap) + abstract fun processModel(state: IBlockState, textures: List) : ModelRenderKey? } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt index ac5b481..321a343 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt @@ -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() - 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(16) val icons = arrayOfNulls(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] } diff --git a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt index 7844037..2f65ecb 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt @@ -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) diff --git a/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt b/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt index 6859bde..64699f2 100644 --- a/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt +++ b/src/main/kotlin/mods/octarinecore/common/config/Matchers.kt @@ -37,9 +37,9 @@ class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, Bl } } -data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List) - -fun modelTextures(vararg args: String) = ModelTextureList(ResourceLocation(args[0]), listOf(*args).drop(1)) +data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List) { + constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1)) +} class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption(domain, path) { override fun convertValue(line: String): ModelTextureList? {