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

View File

@@ -3,38 +3,47 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.ILeafRegistry
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.StandardLogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.client.texture.LeafInfo
import mods.betterfoliage.client.texture.LeafRegistry
import mods.betterfoliage.client.texture.StandardLeafKey
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.resource.ModelProcessor
import mods.octarinecore.client.resource.ModelVariant
import mods.octarinecore.client.resource.registerSprite
import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryBase
import mods.octarinecore.getTileEntitySafe
import mods.octarinecore.isBlockLoaded
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.tryDefault
import net.minecraft.block.state.IBlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.block.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import kotlin.collections.Map
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.emptyMap
import kotlin.collections.find
import kotlin.collections.forEach
import kotlin.collections.get
import kotlin.collections.listOf
import kotlin.collections.mapValues
import kotlin.collections.mutableMapOf
import kotlin.collections.set
@SideOnly(Side.CLIENT)
object ForestryIntegration {
@@ -64,50 +73,18 @@ object ForestryIntegration {
init {
if (Loader.isModLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
Client.log(Level.INFO, "Forestry support initialized")
LeafRegistry.subRegistries.add(ForestryLeavesSupport)
LogRegistry.subRegistries.add(ForestryLogSupport)
LeafRegistry.addRegistry(ForestryLeafRegistry)
LogRegistry.addRegistry(ForestryLogRegistry)
}
}
}
@SideOnly(Side.CLIENT)
object ForestryLeavesSupport : ILeafRegistry {
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
val logger = BetterFoliageMod.logDetail
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
val textureToValue = mutableMapOf<ResourceLocation, 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? {
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos): LeafInfo? {
// check variant property (used in decorative leaves)
state.properties.entries.find {
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
@@ -124,44 +101,54 @@ object ForestryLeavesSupport : ILeafRegistry {
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
return textureToValue[textureLoc]
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
textureToValue = emptyMap()
val allLeaves = ForestryIntegration.TeLleafTextures.getStatic() as Map<*, *>
allLeaves.entries.forEach {
logger.log(Level.DEBUG, "ForestryLeavesSupport: base leaf type ${it.key.toString()}")
listOf(
ForestryIntegration.TeLplain.get(it.value) as ResourceLocation,
ForestryIntegration.TeLfancy.get(it.value) as ResourceLocation,
ForestryIntegration.TeLpollplain.get(it.value) as ResourceLocation,
ForestryIntegration.TeLpollfancy.get(it.value) as ResourceLocation
).forEach { textureLocation ->
val key = StandardLeafKey(logger, textureLocation.toString()).apply { onPreStitch(event.map) }
textureToKey[textureLocation] = key
}
}
}
@SideOnly(Side.CLIENT)
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>()
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
textureToKey.clear()
}
}
object ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
init { MinecraftForge.EVENT_BUS.register(this) }
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) {
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!Config.blocks.logClasses.matchesClass(state.block)) return
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
// find wood type property
val woodType = state.properties.entries.find {
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
} ?: return
} ?: return null
logger.log(Level.DEBUG, "ForestryLogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "ForestryLogSupport: variant ${woodType.value.toString()}")
logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
if (bark != null && heart != null) putKeySingle(state, listOf(heart, bark))
if (bark != null && heart != null) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark))
return null
}
override fun processStitch(variant: ModelVariant, key: List<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.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.IColumnRegistry
import mods.betterfoliage.client.render.IColumnTextureInfo
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.StaticColumnInfo
import mods.betterfoliage.client.texture.StandardLeafSupport
import mods.betterfoliage.loader.Refs
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.QuadIconResolver
import mods.octarinecore.client.render.ShadingContext
@@ -16,7 +12,6 @@ import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.block.model.ModelResourceLocation
@@ -25,7 +20,6 @@ import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
@@ -33,91 +27,68 @@ import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
@SideOnly(Side.CLIENT)
object IC2Integration {
object IC2RubberIntegration {
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
init {
if (Loader.isModLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 support initialized")
LogRegistry.subRegistries.add(IC2LogSupport)
Client.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.addRegistry(IC2LogSupport)
}
}
}
@SideOnly(Side.CLIENT)
object TechRebornIntegration {
object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
init {
if (Loader.isModLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn support initialized")
LogRegistry.subRegistries.add(TechRebornLogSupport)
Client.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.addRegistry(TechRebornLogSupport)
}
}
}
@SideOnly(Side.CLIENT)
data class RubberLogModelInfo(
val axis: EnumFacing.Axis?,
val spotDir: EnumFacing?,
val textures: List<String>
)
// TODO avoid copy-paste pattern with regards to StaticColumnInfo
@SideOnly(Side.CLIENT)
data class RubberLogColumnInfo(override val axis: EnumFacing.Axis?,
class RubberLogInfo(
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 }
topTexture: TextureAtlasSprite,
bottomTexture: TextureAtlasSprite,
val spotTexture: TextureAtlasSprite,
sideTextures: List<TextureAtlasSprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad ->
val worldRelativeSide = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
if (worldRelativeSide == spotDir) spotTexture else sideTexture
val worldFace = (if ((idx and 1) == 0) EnumFacing.SOUTH else EnumFacing.EAST).rotate(ctx.rotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
@SideOnly(Side.CLIENT)
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>()
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 }
)
}
}
object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
init { MinecraftForge.EVENT_BUS.register(this) }
override fun processStitch(variant: ModelVariant, key: RubberLogModelInfo, atlas: TextureMap): IColumnTextureInfo? {
val topTex = atlas.registerSprite(key.textures[0])
val bottomTex = atlas.registerSprite(key.textures[1])
val sideTex = atlas.registerSprite(key.textures[2])
if (key.spotDir == null)
return StaticColumnInfo(key.axis, topTex, bottomTex, listOf(sideTex))
else {
val spotTex = atlas.registerSprite(key.textures[3])
return RubberLogColumnInfo(key.axis, key.spotDir, topTex, bottomTex, sideTex, spotTex)
}
}
override fun get(state: IBlockState, rand: Int): IColumnTextureInfo? {
val variant = getVariant(state, rand) ?: return null
return variantToValue[variant]
}
}
@SideOnly(Side.CLIENT)
object IC2LogSupport : RubberLogSupportBase() {
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel) {
override fun processModel(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): ModelRenderKey<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2Integration.BlockRubWood.isInstance(state.block)) return
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val type = state.properties.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
@@ -128,12 +99,10 @@ object IC2LogSupport : RubberLogSupportBase() {
else -> null
}
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" }) {
putKeySingle(state, RubberLogModelInfo(axis, null, textureNames))
}
return
return SimpleColumnInfo.Key(logger, axis, textureNames)
}
// logs with rubber spot
@@ -144,39 +113,35 @@ object IC2LogSupport : RubberLogSupportBase() {
"dry_east", "wet_east" -> EnumFacing.EAST
else -> null
}
val textureNames = listOf("up", "down", "south", "north").map { blockLoc.first.resolveTextureName(it) }
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(Level.DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, spotDir, textureNames))
}
return if (spotDir != null) RubberLogInfo.Key(logger, EnumFacing.Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
}
}
@SideOnly(Side.CLIENT)
object TechRebornLogSupport : RubberLogSupportBase() {
object TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
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
if (!TechRebornIntegration.BlockRubberLog.isInstance(state.block)) return
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
val blockLoc = model.modelBlockAndLoc.firstOrNull() ?: return null
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return
val hasSap = state.properties.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = state.properties.entries.find { it.key.getName() == "sapside" }?.value as? EnumFacing ?: return null
val textureNames = listOf("end", "end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "TechRebornLogSupport: block state ${state.toString()}")
logger.log(Level.DEBUG, "$logName: block state $state")
if (hasSap) {
logger.log(Level.DEBUG, "TechRebornLogSupport: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, sapSide, textureNames))
}
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, EnumFacing.Axis.Y, sapSide, textureNames)
} else {
logger.log(Level.DEBUG, "TechRebornLogSupport: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" }) {
putKeySingle(state, RubberLogModelInfo(EnumFacing.Axis.Y, null, textureNames))
}
}
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(Level.DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, EnumFacing.Axis.Y, textureNames)
}
return null
}
}

View File

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

View File

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

View File

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

View File

@@ -3,19 +3,18 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.ModelVariant
import mods.octarinecore.client.resource.TextureListModelProcessor
import mods.octarinecore.client.resource.registerSprite
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.config.SimpleBlockMatcher
import mods.octarinecore.common.config.modelTextures
import net.minecraft.block.BlockCactus
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.*
import net.minecraftforge.common.MinecraftForge
@@ -23,6 +22,14 @@ import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
object StandardCactusRegistry : ModelRenderRegistryConfigurable<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)
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
@@ -32,34 +39,6 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
val cactusTextures: IColumnRegistry = object : TextureListModelProcessor<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 {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
.scaleUV(cactusStemRadius * 2.0)
@@ -88,7 +67,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
}
override fun afterStitch() {
override fun afterPreStitch() {
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
}
@@ -102,7 +81,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
// get AO data
modelRenderer.updateShading(Int3.zero, allFaces)
val icons = cactusTextures[ctx.blockState(Int3.zero), ctx.random(0)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
modelRenderer.render(
renderer,

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

View File

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

View File

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

View File

@@ -3,23 +3,21 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.AbstractRenderColumn
import mods.betterfoliage.client.render.column.ColumnRenderLayer
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.ModelVariant
import mods.octarinecore.client.resource.TextureListModelProcessor
import mods.octarinecore.client.resource.registerSprite
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import mods.octarinecore.tryDefault
import net.minecraft.block.BlockLog
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing.Axis
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override val addToCutout: Boolean get() = false
@@ -38,7 +36,7 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
}
class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: IColumnRegistry get() = LogRegistry
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) }
@@ -48,40 +46,13 @@ class RoundLogOverlayLayer : ColumnRenderLayer() {
}
@SideOnly(Side.CLIENT)
object LogRegistry : IColumnRegistry {
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 LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
override val logger = BetterFoliageMod.logDetail
override val logName = "StandardLogSupport"
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
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]
}
override fun processModel(state: IBlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
fun getAxis(state: IBlockState): Axis? {
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:

View File

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

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")
}

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")
}

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.chunk.ChunkOverlayLayer
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.common.*
import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
@@ -22,80 +23,6 @@ import net.minecraft.world.IBlockAccess
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
@SideOnly(Side.CLIENT)
interface IColumnTextureInfo {
val axis: Axis?
val top: QuadIconResolver
val bottom: QuadIconResolver
val side: QuadIconResolver
}
/**
* Sealed class hierarchy for all possible render outcomes
*/
@SideOnly(Side.CLIENT)
sealed class ColumnLayerData {
/**
* Data structure to cache texture and world neighborhood data relevant to column rendering
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
@SideOnly(Side.CLIENT)
data class SpecialRender(
val column: IColumnTextureInfo,
val upType: BlockType,
val downType: BlockType,
val quadrants: Array<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)
@Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
@@ -293,128 +220,3 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
return true
}
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<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
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
@@ -19,6 +21,7 @@ import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.min
const val defaultGrassColor = 0
@@ -32,62 +35,25 @@ class GrassInfo(
* Color to use for Short Grass rendering instead of the biome color.
*
* Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit),
* the average color of the texture (significantly ) otherwise.
* the average color of the texture otherwise.
*/
val overrideColor: Int?
)
interface IGrassRegistry {
operator fun get(state: IBlockState, rand: Int): GrassInfo?
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): GrassInfo?
}
/** Collects and manages rendering-related information for grass blocks. */
@SideOnly(Side.CLIENT)
object GrassRegistry : IGrassRegistry {
val subRegistries: MutableList<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 GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
override val logger = BetterFoliageMod.logDetail
override val logName = "StandardGrassSupport"
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.grassClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): GrassInfo? {
val variant = getVariant(state, rand) ?: return null
val baseTexture = variantToValue[variant] ?: return null
return textureToValue[baseTexture]
override fun processModel(state: IBlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
}
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}")
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
override fun resolveSprites(atlas: TextureMap): GrassInfo {
val logName = "StandardGrassKey"
val texture = atlas[textureName] ?: atlas.missingSprite
logger.log(Level.DEBUG, "$logName: texture $textureName")
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
@@ -97,7 +63,6 @@ object StandardGrassSupport :
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null
}
textureToValue[texture] = GrassInfo(texture, overrideColor)
return GrassInfo(texture, overrideColor)
}
}

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.common.Int3
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState
@@ -39,99 +40,31 @@ class LeafInfo(
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: IconSet? get() = LeafRegistry.particles[leafType]
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
}
interface ILeafRegistry {
operator fun get(state: IBlockState, rand: Int): LeafInfo?
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing, rand: Int): LeafInfo?
}
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
/** Collects and manages rendering-related information for grass blocks. */
object LeafRegistry : ILeafRegistry {
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"
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
override val logger = BetterFoliageMod.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
override val logger: Logger? get() = BetterFoliageMod.logDetail
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 processModel(state: IBlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
}
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] }
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 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)
override fun resolveSprites(atlas: TextureMap) = LeafInfo(atlas[generated] ?: atlas.missingSprite, leafType)
}
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))
}
}

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

View File

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

View File

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

View File

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