Model loading rework (1.14 prep)

remove unnecessary complexity
access sprites only at PostStitch
This commit is contained in:
octarine-noise
2019-12-30 17:35:52 +01:00
parent 558c9a2c34
commit 1ea2b6b946
26 changed files with 614 additions and 742 deletions

View File

@@ -15,7 +15,6 @@ import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextComponentString
import net.minecraft.util.text.TextComponentTranslation import net.minecraft.util.text.TextComponentTranslation
import net.minecraft.util.text.TextFormatting import net.minecraft.util.text.TextFormatting
import net.minecraftforge.fml.client.FMLClientHandler import net.minecraftforge.fml.client.FMLClientHandler
@@ -65,21 +64,29 @@ object Client {
RenderConnectedGrassLog() RenderConnectedGrassLog()
) )
// init singletons // init other singletons
val singletons = listOf( val singletons = listOf(
StandardCactusRegistry,
LeafParticleRegistry,
ChunkOverlayManager, ChunkOverlayManager,
LeafRegistry,
GrassRegistry,
LeafWindTracker, LeafWindTracker,
RisingSoulTextures, RisingSoulTextures
)
// init mod integrations
val integrations = listOf(
ShadersModIntegration, ShadersModIntegration,
OptifineCustomColors, OptifineCustomColors,
ForestryIntegration, ForestryIntegration,
IC2Integration, IC2RubberIntegration,
TechRebornIntegration, TechRebornRubberIntegration
StandardLogSupport // add _after_ all other log registries
) )
// add basic block support instances as last
GrassRegistry.addRegistry(StandardGrassRegistry)
LeafRegistry.addRegistry(StandardLeafRegistry)
LogRegistry.addRegistry(StandardLogRegistry)
// init config hotkey // init config hotkey
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") { val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
FMLClientHandler.instance().showGuiScreen( FMLClientHandler.instance().showGuiScreen(
@@ -88,7 +95,6 @@ object Client {
} }
} }
fun log(level: Level, msg: String) { fun log(level: Level, msg: String) {
BetterFoliageMod.log.log(level, "[BetterFoliage] $msg") BetterFoliageMod.log.log(level, "[BetterFoliage] $msg")
BetterFoliageMod.logDetail.log(level, msg) BetterFoliageMod.logDetail.log(level, msg)

View File

@@ -3,38 +3,47 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.texture.ILeafRegistry 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.LeafInfo
import mods.betterfoliage.client.texture.LeafRegistry import mods.betterfoliage.client.texture.LeafRegistry
import mods.betterfoliage.client.texture.StandardLeafKey
import mods.betterfoliage.loader.Refs import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.resource.ModelProcessor import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.ModelVariant import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.registerSprite import mods.octarinecore.client.resource.ModelRenderRegistryBase
import mods.octarinecore.getTileEntitySafe import mods.octarinecore.getTileEntitySafe
import mods.octarinecore.isBlockLoaded
import mods.octarinecore.metaprog.ClassRef import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.tryDefault
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.ModelResourceLocation 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.ResourceLocation
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel import net.minecraftforge.client.model.IModel
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.Loader import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level 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) @SideOnly(Side.CLIENT)
object ForestryIntegration { object ForestryIntegration {
@@ -64,50 +73,18 @@ object ForestryIntegration {
init { init {
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) { if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
Client.log(Level.INFO, "Forestry support initialized") Client.log(Level.INFO, "Forestry support initialized")
LeafRegistry.subRegistries.add(ForestryLeavesSupport) LeafRegistry.addRegistry(ForestryLeafRegistry)
LogRegistry.subRegistries.add(ForestryLogSupport) LogRegistry.addRegistry(ForestryLogRegistry)
} }
} }
} }
@SideOnly(Side.CLIENT) object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
object ForestryLeavesSupport : ILeafRegistry { val logger = BetterFoliageMod.logDetail
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
val textureToValue = mutableMapOf<ResourceLocation, LeafInfo>() override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? {
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? {
// check variant property (used in decorative leaves) // check variant property (used in decorative leaves)
state.properties.entries.find { state.properties.entries.find {
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value) 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 val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
return textureToValue[textureLoc] 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 ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
object ForestryLogSupport : ModelProcessor<List<String>, IColumnTextureInfo>, IColumnRegistry {
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, List<String>>()
override var variantToValue = mapOf<ModelVariant, IColumnTextureInfo>()
override val logger = BetterFoliageMod.logDetail override val logger = BetterFoliageMod.logDetail
init { MinecraftForge.EVENT_BUS.register(this) } override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) {
// respect class list to avoid triggering on fences, stairs, etc. // 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 // find wood type property
val woodType = state.properties.entries.find { val woodType = state.properties.entries.find {
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value) 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, "ForestryLogRegistry: block state $state")
logger.log(Level.DEBUG, "ForestryLogSupport: variant ${woodType.value.toString()}") logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type // get texture names for wood type
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String? val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String? val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]") 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<String>, 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]] }
} }

View File

@@ -2,13 +2,9 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client 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.LogRegistry
import mods.betterfoliage.client.render.StaticColumnInfo import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.texture.StandardLeafSupport import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.Quad import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.QuadIconResolver import mods.octarinecore.client.render.QuadIconResolver
import mods.octarinecore.client.render.ShadingContext import mods.octarinecore.client.render.ShadingContext
@@ -16,7 +12,6 @@ import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation 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.EnumFacing
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel import net.minecraftforge.client.model.IModel
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.Loader import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
@@ -33,91 +27,68 @@ import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
object IC2Integration { object IC2RubberIntegration {
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood") val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
init { init {
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) { if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 support initialized") Client.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.subRegistries.add(IC2LogSupport) LogRegistry.addRegistry(IC2LogSupport)
} }
} }
} }
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
object TechRebornIntegration { object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog") val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
init { init {
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) { if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn support initialized") Client.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.subRegistries.add(TechRebornLogSupport) LogRegistry.addRegistry(TechRebornLogSupport)
} }
} }
} }
@SideOnly(Side.CLIENT) class RubberLogInfo(
data class RubberLogModelInfo( axis: EnumFacing.Axis?,
val axis: EnumFacing.Axis?, val spotDir: EnumFacing,
val spotDir: EnumFacing?, topTexture: TextureAtlasSprite,
val textures: List<String> bottomTexture: TextureAtlasSprite,
) val spotTexture: TextureAtlasSprite,
sideTextures: List<TextureAtlasSprite>
) : 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 -> 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) val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
if (worldRelativeSide == spotDir) spotTexture else sideTexture 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<String>): ModelRenderKey<ColumnTextureInfo> {
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) object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
abstract class RubberLogSupportBase : ModelProcessor<RubberLogModelInfo, IColumnTextureInfo>, IColumnRegistry {
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, RubberLogModelInfo>()
override var variantToValue = mapOf<ModelVariant, IColumnTextureInfo>()
override val logger = BetterFoliageMod.logDetail override val logger = BetterFoliageMod.logDetail
init { MinecraftForge.EVENT_BUS.register(this) } override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
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) {
// check for proper block class, existence of ModelBlock, and "state" blockstate property // check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2Integration.BlockRubWood.isInstance(state.block)) return if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot // logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) { if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
@@ -128,12 +99,10 @@ object IC2LogSupport : RubberLogSupportBase() {
else -> null else -> null
} }
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) } 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: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}") logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" }) { return SimpleColumnInfo.Key(logger, axis, textureNames)
putKeySingle(state, RubberLogModelInfo(axis, null, textureNames))
}
return
} }
// logs with rubber spot // logs with rubber spot
@@ -144,39 +113,35 @@ object IC2LogSupport : RubberLogSupportBase() {
"dry_east", "wet_east" -> EnumFacing.EAST "dry_east", "wet_east" -> EnumFacing.EAST
else -> null 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: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}") logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) { return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, spotDir, textureNames))
}
} }
} }
@SideOnly(Side.CLIENT) object TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
object TechRebornLogSupport : RubberLogSupportBase() { override val logger = BetterFoliageMod.logDetail
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock // check for proper block class, existence of ModelBlock
if (!TechRebornIntegration.BlockRubberLog.isInstance(state.block)) return if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: 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 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, "$logName: block state $state")
logger.log(Level.DEBUG, "TechRebornLogSupport: block state ${state.toString()}")
if (hasSap) { if (hasSap) {
logger.log(Level.DEBUG, "TechRebornLogSupport: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}") val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.all { it != "missingno" }) { logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, sapSide, textureNames)) if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
}
} else { } else {
logger.log(Level.DEBUG, "TechRebornLogSupport: end=${textureNames[0]}, side=${textureNames[2]}") val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.all { it != "missingno" }) { logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, null, textureNames)) if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
}
} }
return null
} }
} }

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.LeafParticleRegistry
import mods.betterfoliage.client.texture.LeafRegistry import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX import mods.octarinecore.client.render.AbstractEntityFX
@@ -8,10 +9,8 @@ import mods.octarinecore.client.render.HSB
import mods.octarinecore.common.Double3 import mods.octarinecore.common.Double3
import mods.octarinecore.minmax import mods.octarinecore.minmax
import mods.octarinecore.random import mods.octarinecore.random
import mods.octarinecore.semiRandom
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.EnumFacing.DOWN
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.World 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 state = world.getBlockState(pos)
val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0) 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) { if (leafInfo != null) {
particleTexture = leafInfo.particleTextures?.get(rand.nextInt(1024)) particleTexture = leafInfo.particleTextures[rand.nextInt(1024)]
calculateParticleColor(leafInfo.averageColor, blockColor) calculateParticleColor(leafInfo.averageColor, blockColor)
} else { } else {
particleTexture = LeafRegistry.particles["default"]?.get(rand.nextInt(1024)) particleTexture = LeafParticleRegistry["default"][rand.nextInt(1024)]
setColor(blockColor) setColor(blockColor)
} }
} }

View File

@@ -71,7 +71,7 @@ object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d") val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d")
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track") val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track")
override fun afterStitch() { override fun afterPreStitch() {
Client.log(INFO, "Registered ${headIcons.num} soul particle textures") Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
} }
} }

View File

@@ -23,7 +23,7 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d") val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d")
val algaeModels = modelSet(64, RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)) 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") Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
} }

View File

@@ -3,19 +3,18 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config 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.render.*
import mods.octarinecore.client.resource.ModelVariant import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.client.resource.TextureListModelProcessor
import mods.octarinecore.client.resource.registerSprite
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation import mods.octarinecore.common.Rotation
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.config.SimpleBlockMatcher import mods.octarinecore.common.config.SimpleBlockMatcher
import mods.octarinecore.common.config.modelTextures
import net.minecraft.block.BlockCactus import net.minecraft.block.BlockCactus
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.BlockRenderLayer import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.* import net.minecraft.util.EnumFacing.*
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
@@ -23,6 +22,14 @@ import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
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<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
init { MinecraftForge.EVENT_BUS.register(this) }
}
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d") val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
val cactusTextures: IColumnRegistry = object : TextureListModelProcessor<IColumnTextureInfo>, IColumnRegistry {
init { MinecraftForge.EVENT_BUS.register(this) }
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, List<String>>()
override var variantToValue = mapOf<ModelVariant, IColumnTextureInfo>()
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<String>, 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 { val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5) horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
.scaleUV(cactusStemRadius * 2.0) .scaleUV(cactusStemRadius * 2.0)
@@ -88,7 +67,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll() .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") Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
} }
@@ -102,7 +81,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
// get AO data // get AO data
modelRenderer.updateShading(Int3.zero, allFaces) 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( modelRenderer.render(
renderer, renderer,

View File

@@ -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 ${coralIcons.num} coral textures")
Client.log(INFO, "Registered ${crustIcons.num} coral crust textures") Client.log(INFO, "Registered ${crustIcons.num} coral crust textures")
} }

View File

@@ -41,7 +41,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val grassModels = modelSet(64, grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)) 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 ${normalIcons.num} grass textures")
Client.log(INFO, "Registered ${snowedIcons.num} snowed 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) = override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.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 { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer // 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 isSnowed = ctx.blockState(up1).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled) val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grassInfo = GrassRegistry[ctx, UP] val grass = GrassRegistry[ctx]
if (grassInfo == null) { if (grass == null) {
// shouldn't happen // shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, null) return renderWorldBlockBase(ctx, dispatcher, renderer, null)
@@ -83,12 +83,12 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
renderer, renderer,
fullCube, fullCube,
quadFilter = { qi, _ -> !isHidden[qi] }, quadFilter = { qi, _ -> !isHidden[qi] },
icon = { _, _, _ -> grassInfo.grassTopTexture }, icon = { _, _, _ -> grass.grassTopTexture },
postProcess = { ctx, _, _, _, _ -> postProcess = { ctx, _, _, _, _ ->
rotateUV(2) rotateUV(2)
if (isSnowed) { if (isSnowed) {
if (!ctx.aoEnabled) setGrey(1.4f) 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, Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! }, 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) }
) )
} }

View File

@@ -45,14 +45,14 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.enabled &&
Config.leaves.enabled && Config.leaves.enabled &&
LeafRegistry[ctx, DOWN] != null && LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } ) !(Config.leaves.hideInternal && ctx.isSurroundedBy { it.isFullCube || it.material == Material.LEAVES } )
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).material.let { val isSnowed = ctx.blockState(up1).material.let {
it == Material.SNOW || it == Material.CRAFTED_SNOW it == Material.SNOW || it == Material.CRAFTED_SNOW
} }
val leafInfo = LeafRegistry[ctx, DOWN] val leafInfo = LeafRegistry[ctx]
if (leafInfo == null) { if (leafInfo == null) {
// shouldn't happen // shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)

View File

@@ -34,7 +34,7 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d") val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d")
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset } 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 ${rootIcon.num} lilypad root textures")
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures") Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
} }

View File

@@ -3,23 +3,21 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config 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.render.BlockContext
import mods.octarinecore.client.resource.ModelVariant import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.TextureListModelProcessor
import mods.octarinecore.client.resource.registerSprite
import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import mods.octarinecore.tryDefault import mods.octarinecore.tryDefault
import net.minecraft.block.BlockLog import net.minecraft.block.BlockLog
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing.Axis import net.minecraft.util.EnumFacing.Axis
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override val addToCutout: Boolean get() = false override val addToCutout: Boolean get() = false
@@ -38,7 +36,7 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
} }
class RoundLogOverlayLayer : ColumnRenderLayer() { class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: IColumnRegistry get() = LogRegistry override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) } override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !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) @SideOnly(Side.CLIENT)
object LogRegistry : IColumnRegistry { object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
val subRegistries: MutableList<IColumnRegistry> = mutableListOf()
override fun get(state: IBlockState, rand: Int) = subRegistries.findFirst { it[state, rand] }
}
@SideOnly(Side.CLIENT)
object StandardLogSupport : TextureListModelProcessor<IColumnTextureInfo>, IColumnRegistry {
init {
LogRegistry.subRegistries.add(this)
MinecraftForge.EVENT_BUS.register(this)
}
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, List<String>>()
override var variantToValue = mapOf<ModelVariant, IColumnTextureInfo>()
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail override val logger = BetterFoliageMod.logDetail
override val logName = "StandardLogSupport"
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
override fun processStitch(variant: ModelVariant, key: List<String>, 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]
}
fun getAxis(state: IBlockState): Axis? { fun getAxis(state: IBlockState): Axis? {
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?: 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) { return when (axis) {
"x" -> Axis.X "x" -> Axis.X
"y" -> Axis.Y "y" -> Axis.Y

View File

@@ -22,7 +22,7 @@ class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d") val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d")
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)) 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") Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
} }

View File

@@ -28,7 +28,7 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID)
} }
override fun afterStitch() { override fun afterPreStitch() {
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures") Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
} }

View File

@@ -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") Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
} }

View File

@@ -1,15 +1,16 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render.column
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.chunk.ChunkOverlayLayer import mods.betterfoliage.client.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.BlockType.* import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType.* 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.render.*
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.common.* import mods.octarinecore.common.*
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher 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.Side
import net.minecraftforge.fml.relauncher.SideOnly 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<QuadrantType>,
val quadrantsTop: Array<QuadrantType>,
val quadrantsBottom: Array<QuadrantType>
) : 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<TextureAtlasSprite>) : 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) @SideOnly(Side.CLIENT)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) { abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
@@ -293,128 +220,3 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
return true return true
} }
} }
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
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<QuadrantType>.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<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
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
}
}
}

View File

@@ -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<QuadrantType>,
val quadrantsTop: Array<QuadrantType>,
val quadrantsBottom: Array<QuadrantType>
) : 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<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
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<QuadrantType>.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<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: EnumFacing.Axis, yOff: Int): Array<QuadrantType> {
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
}
}
}

View File

@@ -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<TextureAtlasSprite>
) : 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<String>) : ModelRenderKey<ColumnTextureInfo> {
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 }
)
}
}

View File

@@ -1,12 +1,14 @@
package mods.betterfoliage.client.texture package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.HSB import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState 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.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.min import java.lang.Math.min
const val defaultGrassColor = 0 const val defaultGrassColor = 0
@@ -32,62 +35,25 @@ class GrassInfo(
* Color to use for Short Grass rendering instead of the biome color. * 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), * 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? val overrideColor: Int?
) )
interface IGrassRegistry { object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
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<IGrassRegistry> = 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<TextureAtlasSprite>,
TextureMediatedRegistry<List<String>, GrassInfo>,
IGrassRegistry
{
init { MinecraftForge.EVENT_BUS.register(this) }
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, List<String>>()
override var variantToValue = mapOf<ModelVariant, TextureAtlasSprite>()
override var textureToValue = mutableMapOf<TextureAtlasSprite, GrassInfo>()
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
override val logger = BetterFoliageMod.logDetail override val logger = BetterFoliageMod.logDetail
override val logName = "StandardGrassSupport"
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
}
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): GrassInfo? { class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
val variant = getVariant(state, rand) ?: return null override fun resolveSprites(atlas: TextureMap): GrassInfo {
val baseTexture = variantToValue[variant] ?: return null val logName = "StandardGrassKey"
return textureToValue[baseTexture] val texture = atlas[textureName] ?: atlas.missingSprite
} logger.log(Level.DEBUG, "$logName: texture $textureName")
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<String>, atlas: TextureMap) = atlas.registerSprite(key[0])
override fun processTexture(variants: List<ModelVariant>, texture: TextureAtlasSprite, atlas: TextureMap) { registerGrass(texture, atlas) }
fun registerGrass(texture: TextureAtlasSprite, atlas: TextureMap) {
logger.log(Level.DEBUG, "$logName: texture ${texture.iconName}")
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor) val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) { val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}") 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") logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null null
} }
return GrassInfo(texture, overrideColor)
textureToValue[texture] = GrassInfo(texture, overrideColor)
} }
} }

View File

@@ -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<String, IconSet>()
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<Mapping> = 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()))
}
}
}
}
}

View File

@@ -7,6 +7,7 @@ import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
@@ -39,99 +40,31 @@ class LeafInfo(
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
) { ) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */ /** [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 { object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
operator fun get(state: IBlockState, rand: Int): LeafInfo?
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): LeafInfo?
}
/** Collects and manages rendering-related information for grass blocks. */ object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
object LeafRegistry : ILeafRegistry { override val logger = BetterFoliageMod.logDetail
val subRegistries: MutableList<ILeafRegistry> = mutableListOf(StandardLeafSupport)
val typeMappings = TextureMatcher()
val particles = hashMapOf<String, IconSet>()
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<TextureAtlasSprite>,
TextureMediatedRegistry<List<String>, LeafInfo>,
ILeafRegistry
{
init { MinecraftForge.EVENT_BUS.register(this) }
override val logName = "StandardLeafSupport"
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
override val logger: Logger? get() = BetterFoliageMod.logDetail override fun processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
override var variants = mutableMapOf<IBlockState, MutableList<ModelVariant>>()
override var variantToKey = mutableMapOf<ModelVariant, List<String>>()
override var variantToValue = mapOf<ModelVariant, TextureAtlasSprite>()
override var textureToValue = mutableMapOf<TextureAtlasSprite, LeafInfo>()
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<String>, atlas: TextureMap) = atlas.registerSprite(key[0])
override fun processTexture(variants: List<ModelVariant>, 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))
}
} }
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
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)
}

View File

@@ -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<Mapping> = 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()))
}
}
}
}
}

View File

@@ -1,10 +1,14 @@
package mods.octarinecore.client.resource package mods.octarinecore.client.resource
import com.google.common.base.Joiner import com.google.common.base.Joiner
import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs 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.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.filterValuesNotNull import mods.octarinecore.filterValuesNotNull
import mods.octarinecore.findFirst
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation 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.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.ResourceLocation 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.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel import net.minecraftforge.client.model.IModel
import net.minecraftforge.client.model.ModelLoader import net.minecraftforge.client.model.ModelLoader
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.Event import net.minecraftforge.fml.common.eventhandler.Event
import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
@@ -24,41 +31,43 @@ import org.apache.logging.log4j.Logger
class LoadModelDataEvent(val loader: ModelLoader) : Event() class LoadModelDataEvent(val loader: ModelLoader) : Event()
data class ModelVariant( interface ModelRenderRegistry<T> {
val state: IBlockState, operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos)
val modelLocation: ResourceLocation?, operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): T?
val weight: Int }
)
interface ModelProcessor<T1, T2> { interface ModelRenderDataExtractor<T> {
fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<T>?
}
interface ModelRenderKey<T> {
val logger: Logger? val logger: Logger?
var variants: MutableMap<IBlockState, MutableList<ModelVariant>> fun onPreStitch(atlas: TextureMap) {}
var variantToKey: MutableMap<ModelVariant, T1> fun resolveSprites(atlas: TextureMap): T
var variantToValue: Map<ModelVariant, T2> }
fun addVariant(state: IBlockState, variant: ModelVariant) { variants.getOrPut(state) { mutableListOf() }.add(variant) } abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
fun getVariant(state: IBlockState, rand: Int) = variants[state]?.let { it[rand % it.size] } val subRegistries = mutableListOf<ModelRenderRegistry<T>>()
fun putKeySingle(state: IBlockState, key: T1) { override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
val variant = ModelVariant(state, null, 1) fun addRegistry(registry: ModelRenderRegistry<T>) {
variants[state] = mutableListOf(variant) subRegistries.add(registry)
variantToKey[variant] = key MinecraftForge.EVENT_BUS.register(registry)
} }
}
fun onPostLoad() { } abstract class ModelRenderRegistryBase<T> : ModelRenderRegistry<T>, ModelRenderDataExtractor<T> {
fun onPreStitch() { } open val logger: Logger? = null
open val logName: String get() = this::class.java.name
fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) val stateToKey = mutableMapOf<IBlockState, ModelRenderKey<T>>()
fun processStitch(variant: ModelVariant, key: T1, atlas: TextureMap): T2? var stateToValue = mapOf<IBlockState, T>()
@SubscribeEvent(priority = EventPriority.HIGHEST) override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos) = stateToValue[state]
fun clearBeforeLoadModelData(event: LoadModelDataEvent) {
variants.clear()
variantToKey.clear()
}
@Suppress("UNCHECKED_CAST")
@SubscribeEvent @SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) { fun handleLoadModelData(event: LoadModelDataEvent) {
onPostLoad() stateToValue = emptyMap()
val stateMappings = Block.REGISTRY.flatMap { block -> val stateMappings = Block.REGISTRY.flatMap { block ->
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper() val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
@@ -68,33 +77,41 @@ interface ModelProcessor<T1, T2> {
stateMappings.forEach { mapping -> stateMappings.forEach { mapping ->
if (mapping.key.block != null) stateModels[mapping.value]?.let { model -> 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) @SubscribeEvent(priority = EventPriority.LOW)
fun handlePreStitch(event: TextureStitchEvent.Pre) { fun handlePreStitch(event: TextureStitchEvent.Pre) {
onPreStitch() stateToKey.forEach { (_, key) -> key.onPreStitch(event.map) }
variantToValue = variantToKey.mapValues { processStitch(it.key, it.value, event.map) }.filterValuesNotNull() }
@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> { abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>() {
val logName: String
val matchClasses: IBlockMatcher
val modelTextures: List<ModelTextureList>
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) { abstract val matchClasses: IBlockMatcher
val matchClass = matchClasses.matchingClass(state.block) ?: return 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: block state ${state.toString()}")
logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}") logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
val allModels = model.modelBlockAndLoc.distinctBy { it.second } val allModels = model.modelBlockAndLoc.distinctBy { it.second }
if (allModels.isEmpty()) { if (allModels.isEmpty()) {
logger?.log(Level.DEBUG, "$logName: no models found") logger?.log(Level.DEBUG, "$logName: no models found")
return return null
} }
allModels.forEach { blockLoc -> allModels.forEach { blockLoc ->
@@ -107,53 +124,13 @@ interface TextureListModelProcessor<T2> : ModelProcessor<List<String>, T2> {
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]") logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
if (textures.all { it.second != "missingno" }) { if (textures.all { it.second != "missingno" }) {
// found a valid variant (all required textures exist) // found a valid model (all required textures exist)
val variant = ModelVariant(state, blockLoc.second, 1) return processModel(state, textures.map { it.second} )
addVariant(state, variant)
variantToKey[variant] = textures.map { it.second }
} }
} }
} }
return null
} }
// override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): List<String>? { abstract fun processModel(state: IBlockState, textures: List<String>) : 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
// 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)
} }

View File

@@ -20,7 +20,10 @@ import java.util.*
// ============================ // ============================
// Resource types // Resource types
// ============================ // ============================
interface IStitchListener { fun onStitch(atlas: TextureMap) } interface IStitchListener {
fun onPreStitch(atlas: TextureMap)
fun onPostStitch(atlas: TextureMap)
}
interface IConfigChangeListener { fun onConfigChange() } interface IConfigChangeListener { fun onConfigChange() }
interface IWorldLoadListener { fun onWorldLoad(world: World) } interface IWorldLoadListener { fun onWorldLoad(world: World) }
@@ -34,7 +37,8 @@ interface IWorldLoadListener { fun onWorldLoad(world: World) }
open class ResourceHandler(val modId: String) { open class ResourceHandler(val modId: String) {
val resources = mutableListOf<Any>() val resources = mutableListOf<Any>()
open fun afterStitch() {} open fun afterPreStitch() {}
open fun afterPostStitch() {}
// ============================ // ============================
// Self-registration // 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(domain: String, path: String) = IconHolder(domain, path).apply { resources.add(this) }
fun iconStatic(location: ResourceLocation) = iconStatic(location.namespace, location.path) 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 iconSet(location: ResourceLocation) = iconSet(location.namespace, location.path)
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) } 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) } 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 // Event registration
// ============================ // ============================
@SubscribeEvent @SubscribeEvent
fun onStitch(event: TextureStitchEvent.Pre) { fun onPreStitch(event: TextureStitchEvent.Pre) {
resources.forEach { (it as? IStitchListener)?.onStitch(event.map) } resources.forEach { (it as? IStitchListener)?.onPreStitch(event.map) }
afterStitch() afterPreStitch()
}
@SubscribeEvent
fun onPostStitch(event: TextureStitchEvent.Post) {
resources.forEach { (it as? IStitchListener)?.onPostStitch(event.map) }
afterPostStitch()
} }
@SubscribeEvent @SubscribeEvent
@@ -76,8 +86,10 @@ open class ResourceHandler(val modId: String) {
// Resource container classes // Resource container classes
// ============================ // ============================
class IconHolder(val domain: String, val name: String) : IStitchListener { class IconHolder(val domain: String, val name: String) : IStitchListener {
val iconRes = ResourceLocation(domain, name)
var icon: TextureAtlasSprite? = null 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 { 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 { class IconSet(val domain: String, val namePattern: String) : IStitchListener {
val resources = arrayOfNulls<ResourceLocation>(16)
val icons = arrayOfNulls<TextureAtlasSprite>(16) val icons = arrayOfNulls<TextureAtlasSprite>(16)
var num = 0 var num = 0
override fun onStitch(atlas: TextureMap) { override fun onPreStitch(atlas: TextureMap) {
num = 0 num = 0
(0..15).forEach { idx -> (0..15).forEach { idx ->
icons[idx] = null icons[idx] = null
val locReal = ResourceLocation(domain, "textures/${namePattern.format(idx)}.png") 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] operator fun get(idx: Int) = if (num == 0) null else icons[idx % num]
} }

View File

@@ -37,8 +37,7 @@ operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryD
/** Index operator to get a texture sprite. */ /** Index operator to get a texture sprite. */
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString()) operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
operator fun TextureMap.get(res: ResourceLocation): TextureAtlasSprite? = getTextureExtry(res.toString())
fun TextureMap.registerSprite(name: String): TextureAtlasSprite = registerSprite(ResourceLocation(name))!!
/** Load an image resource. */ /** Load an image resource. */
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream) fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)

View File

@@ -37,9 +37,9 @@ class ConfigurableBlockMatcher(domain: String, path: String) : IBlockMatcher, Bl
} }
} }
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>) {
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
fun modelTextures(vararg args: String) = ModelTextureList(ResourceLocation(args[0]), listOf(*args).drop(1)) }
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) { class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
override fun convertValue(line: String): ModelTextureList? { override fun convertValue(line: String): ModelTextureList? {