[WIP] async block texture reloading

This commit is contained in:
octarine-noise
2020-01-05 16:32:45 +01:00
parent b4f18c1e1d
commit c4ee768025
31 changed files with 587 additions and 704 deletions

View File

@@ -1,6 +1,5 @@
package mods.betterfoliage;
import net.minecraftforge.fml.ModLoader;
import org.spongepowered.asm.mixin.Mixins;
import org.spongepowered.asm.mixin.connect.IMixinConnector;

View File

@@ -5,7 +5,6 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.chunk.ChunkRender;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.client.MinecraftForgeClient;

View File

@@ -2,20 +2,12 @@ package mods.betterfoliage.mixin;
import mods.betterfoliage.client.Hooks;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.chunk.ChunkRender;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.client.model.data.IModelData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(ChunkRender.class)
public class ChunkRenderVanillaMixin {

View File

@@ -1,21 +1,26 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.client.Hooks;
import mods.octarinecore.client.resource.AsyncSpriteProviderManager;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ModelBakery.class)
public class ModelBakeryMixin {
abstract public class ModelBakeryMixin {
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;)V";
private static final String endStartSection = "Lnet/minecraft/profiler/IProfiler;endStartSection(Ljava/lang/String;)V";
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/lang/Iterable;Lnet/minecraft/profiler/IProfiler;)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
@Inject(method = processLoading, at = @At(value = "INVOKE_STRING", target = endStartSection, args = "ldc=stitching"))
void preStitchTextures(IProfiler profiler, CallbackInfo ci) {
Hooks.onLoadModelDefinitions(this);
@Redirect(method = processLoading, at = @At(value = "INVOKE", target = stitch))
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable<ResourceLocation> idList, IProfiler profiler) {
return AsyncSpriteProviderManager.INSTANCE.onStitchBlockAtlas(this, atlas, manager, idList, profiler);
}
}

View File

@@ -2,17 +2,12 @@ package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.isAfterPostInit
import mods.octarinecore.client.resource.GeneratorPack
import net.alexwells.kottle.FMLKotlinModLoadingContext
import net.minecraft.client.Minecraft
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
@@ -23,7 +18,7 @@ import java.util.*
@Mod(BetterFoliage.MOD_ID)
object BetterFoliage {
const val MOD_ID = "betterfoliage"
const val MOD_ID = ""
const val MOD_NAME = "Better Foliage"
val modBus = FMLKotlinModLoadingContext.get().modEventBus

View File

@@ -12,6 +12,7 @@ import mods.betterfoliage.client.texture.*
import mods.octarinecore.client.gui.textComponent
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.resource.CenteringTextureGenerator
import mods.octarinecore.client.resource.AsyncSpriteProviderManager
import mods.octarinecore.client.resource.IConfigChangeListener
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
@@ -59,7 +60,6 @@ object Client {
// init other singletons
val singletons = listOf(
BlockConfig,
StandardCactusRegistry,
LeafParticleRegistry,
ChunkOverlayManager,
LeafWindTracker,
@@ -76,9 +76,10 @@ object Client {
)
// add basic block support instances as last
GrassRegistry.addRegistry(StandardGrassRegistry)
LeafRegistry.addRegistry(StandardLeafRegistry)
LogRegistry.addRegistry(StandardLogRegistry)
AsyncLeafDiscovery.init()
AsyncGrassDiscovery.init()
AsyncLogDiscovery.init()
AsyncCactusDiscovery.init()
configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance<IConfigChangeListener>()
configListeners.forEach { it.onConfigChange() }

View File

@@ -6,12 +6,10 @@ import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.*
import mods.betterfoliage.loader.Refs
import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.DefaultLightingCtx
import mods.octarinecore.client.render.lighting.LightingCtx
import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
@@ -35,9 +33,6 @@ import net.minecraft.world.World
import net.minecraftforge.client.model.data.IModelData
import java.util.*
var isAfterPostInit = false
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)) return Config.roundLogs.dimming.toFloat();
return original
@@ -69,10 +64,6 @@ fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: Bloc
}
}
fun onLoadModelDefinitions(bakery: Any) {
BetterFoliage.modBus.post(LoadModelDataEvent(bakery as ModelBakery))
}
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
return state.func_215702_a(reader, pos, dir)

View File

@@ -1,10 +1,9 @@
package mods.betterfoliage.client.chunk
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.BasicBlockCtx
import mods.octarinecore.ChunkCacheOF
import mods.octarinecore.client.render.BlockCtx
import mods.octarinecore.metaprog.get
import mods.octarinecore.metaprog.isInstance
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
@@ -16,13 +15,19 @@ import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import org.apache.logging.log4j.Level
import java.util.*
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.associateWith
import kotlin.collections.forEach
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set
val IEnviromentBlockReader.dimType: DimensionType get() = when {
this is IWorldReader -> dimension.type
this is ChunkRenderCache -> world.dimension.type
Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkRenderCache).world.dimension.type
this.isInstance(ChunkCacheOF) -> this[ChunkCacheOF.chunkCache].world.dimension.type
else -> throw IllegalArgumentException("DimensionType of world with class ${this::class.qualifiedName} cannot be determined!")
}
@@ -42,7 +47,6 @@ object ChunkOverlayManager {
var tempCounter = 0
init {
Client.log(Level.INFO, "Initializing client overlay manager")
MinecraftForge.EVENT_BUS.register(this)
}
@@ -84,13 +88,11 @@ object ChunkOverlayManager {
@SubscribeEvent
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
BetterFoliage.log.debug("Unloaded world: id=${world.dimType.id} name=${world.dimType.registryName}")
chunkData[world.dimType] = mutableMapOf()
}
@SubscribeEvent
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
BetterFoliage.log.debug("Unloaded world: id=${world.dimType.id} name=${world.dimType.registryName}")
chunkData.remove(world.dimType)
}
@@ -121,26 +123,3 @@ class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val validYRange = 0 until 256
}
}
/**
* IWorldEventListener helper subclass
* No-op for everything except notifyBlockUpdate()
*/
/*
interface IBlockUpdateListener : IWorldEventListener {
override fun playSoundToAllNearExcept(player: EntityPlayer?, soundIn: SoundEvent, category: SoundCategory, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {}
override fun onEntityAdded(entityIn: Entity) {}
override fun broadcastSound(soundID: Int, pos: BlockPos, data: Int) {}
override fun playEvent(player: EntityPlayer?, type: Int, blockPosIn: BlockPos, data: Int) {}
override fun onEntityRemoved(entityIn: Entity) {}
override fun notifyLightSet(pos: BlockPos) {}
override fun spawnParticle(particleID: Int, ignoreRange: Boolean, xCoord: Double, yCoord: Double, zCoord: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
override fun spawnParticle(id: Int, ignoreRange: Boolean, minimiseParticleLevel: Boolean, x: Double, y: Double, z: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {}
override fun playRecord(soundIn: SoundEvent, pos: BlockPos) {}
override fun sendBlockBreakProgress(breakerId: Int, pos: BlockPos, progress: Int) {}
override fun markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {}
override fun addParticle(p0: IParticleData, p1: Boolean, p2: Double, p3: Double, p4: Double, p5: Double, p6: Double, p7: Double) {}
override fun addParticle(p0: IParticleData, p1: Boolean, p2: Boolean, p3: Double, p4: Double, p5: Double, p6: Double, p7: Double, p8: Double) {}
}
*/

View File

@@ -2,10 +2,8 @@ package mods.betterfoliage.client.config
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.config.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
private fun featureEnable() = boolean(true).lang("enabled")
@@ -165,12 +163,4 @@ object BlockConfig {
init { BetterFoliage.modBus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, ResourceLocation(BetterFoliage.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults()
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}

View File

@@ -1,153 +1,133 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.StandardLogRegistry
import mods.betterfoliage.client.render.AsyncLogDiscovery
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.client.resource.Identifier
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.ModelRenderKey
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryBase
import mods.octarinecore.getTileEntitySafe
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import mods.octarinecore.metaprog.allAvailable
import mods.betterfoliage.client.texture.defaultRegisterLeaf
import mods.octarinecore.HasLogger
import mods.octarinecore.Map
import mods.octarinecore.ResourceLocation
import mods.octarinecore.String
import mods.octarinecore.client.resource.*
import mods.octarinecore.metaprog.*
import mods.octarinecore.metaprog.ClassRef.Companion.boolean
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.util.ResourceLocation
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraft.world.IWorldReader
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.IModel
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level
import kotlin.collections.Map
import java.util.concurrent.CompletableFuture
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
val TextureLeaves = ClassRef<Any>("forestry.arboriculture.models.TextureLeaves")
val TextureLeaves_leafTextures = FieldRef(TextureLeaves, "leafTextures", Map)
val TextureLeaves_plain = FieldRef(TextureLeaves, "plain", ResourceLocation)
val TextureLeaves_fancy = FieldRef(TextureLeaves, "fancy", ResourceLocation)
val TextureLeaves_pollinatedPlain = FieldRef(TextureLeaves, "pollinatedPlain", ResourceLocation)
val TextureLeaves_pollinatedFancy = FieldRef(TextureLeaves, "pollinatedFancy", ResourceLocation)
val TileLeaves = ClassRef<Any>("forestry.arboriculture.tiles.TileLeaves")
val TileLeaves_getLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", ResourceLocation, boolean)
val PropertyWoodType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyWoodType")
val IWoodType = ClassRef<Any>("forestry.api.arboriculture.IWoodType")
val IWoodType_barkTex = MethodRef(IWoodType, "getBarkTexture", String)
val IWoodType_heartTex = MethodRef(IWoodType, "getHeartTexture", String)
val PropertyTreeType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyTreeType")
val IAlleleTreeSpecies = ClassRef<Any>("forestry.api.arboriculture.IAlleleTreeSpecies")
val ILeafSpriteProvider = ClassRef<Any>("forestry.api.arboriculture.ILeafSpriteProvider")
val TreeDefinition = ClassRef<Any>("forestry.arboriculture.genetics.TreeDefinition")
val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
val TreeDefinition_species = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
val ILeafSpriteProvider_getSprite = MethodRef(ILeafSpriteProvider, "getSprite", ResourceLocation, boolean, boolean)
object ForestryIntegration {
val TextureLeaves = ClassRef("forestry.arboriculture.models.TextureLeaves")
val TeLleafTextures = FieldRef(TextureLeaves, "leafTextures", Refs.Map)
val TeLplain = FieldRef(TextureLeaves, "plain", Refs.ResourceLocation)
val TeLfancy = FieldRef(TextureLeaves, "fancy", Refs.ResourceLocation)
val TeLpollplain = FieldRef(TextureLeaves, "pollinatedPlain", Refs.ResourceLocation)
val TeLpollfancy = FieldRef(TextureLeaves, "pollinatedFancy", Refs.ResourceLocation)
val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves")
val TiLgetLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", Refs.ResourceLocation, ClassRef.boolean)
val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType")
val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType")
val barkTex = MethodRef(IWoodType, "getBarkTexture", Refs.String)
val heartTex = MethodRef(IWoodType, "getHeartTexture", Refs.String)
val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType")
val TreeDefinition = ClassRef("forestry.arboriculture.genetics.TreeDefinition")
val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies")
val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider")
val TdSpecies = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
val getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
val getSprite = MethodRef(ILeafSpriteProvider, "getSprite", Refs.ResourceLocation, ClassRef.boolean, ClassRef.boolean)
init {
if (ModList.get().isLoaded("forestry") && allAvailable(TiLgetLeaveSprite, getLeafSpriteProvider, getSprite)) {
Client.log(Level.INFO, "Forestry support initialized")
LeafRegistry.addRegistry(ForestryLeafRegistry)
LogRegistry.addRegistry(ForestryLogRegistry)
if (ModList.get().isLoaded("forestry") && allAvailable(TileLeaves_getLeaveSprite, IAlleleTreeSpecies_getLeafSpriteProvider, ILeafSpriteProvider_getSprite)) {
}
}
}
object ForestryLeafRegistry : ModelRenderRegistry<LeafInfo> {
val logger = BetterFoliage.logDetail
val textureToKey = mutableMapOf<ResourceLocation, ModelRenderKey<LeafInfo>>()
var textureToValue = emptyMap<ResourceLocation, LeafInfo>()
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry<LeafInfo> {
override val logger = BetterFoliage.logDetail
var idToValue = emptyMap<Identifier, LeafInfo>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos): LeafInfo? {
// check variant property (used in decorative leaves)
state.values.entries.find {
ForestryIntegration.PropertyTreeType.isInstance(it.key) && ForestryIntegration.TreeDefinition.isInstance(it.value)
PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value)
} ?.let {
val species = ForestryIntegration.TdSpecies.get(it.value)
val spriteProvider = ForestryIntegration.getLeafSpriteProvider.invoke(species!!)
val textureLoc = ForestryIntegration.getSprite.invoke(spriteProvider!!, false, Minecraft.isFancyGraphicsEnabled())
return textureToValue[textureLoc]
val species = it.value[TreeDefinition_species]
val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]()
val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, Minecraft.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
// extract leaf texture information from TileEntity
val tile = world.getTileEntity(pos) ?: return null
if (!ForestryIntegration.TileLeaves.isInstance(tile)) return null
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
return textureToValue[textureLoc]
if (!TileLeaves.isInstance(tile)) return null
val textureLoc = tile[TileLeaves_getLeaveSprite](Minecraft.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
textureToValue = emptyMap()
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
val futures = mutableMapOf<Identifier, CompletableFuture<LeafInfo>>()
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) }
textureToKey[textureLocation] = key
return StitchPhases(
discovery = bakeryFuture.thenRunAsync {
val allLeaves = TextureLeaves_leafTextures.getStatic()
allLeaves.entries.forEach { (type, leaves) ->
log("base leaf type $type")
leaves!!
listOf(
leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain],
leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy]
).forEach { textureLocation ->
futures[textureLocation] = defaultRegisterLeaf(textureLocation, atlasFuture)
}
}
},
cleanup = atlasFuture.sheet.thenRunAsync {
idToValue = futures.mapValues { it.value.get() }
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
textureToValue = textureToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
textureToKey.clear()
)
}
}
object ForestryLogRegistry : ModelRenderRegistryBase<ColumnTextureInfo>() {
object ForestryLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!BlockConfig.logBlocks.matchesClass(state.block)) return null
if (!BlockConfig.logBlocks.matchesClass(ctx.state.block)) return null
// find wood type property
val woodType = state.values.entries.find {
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
} ?: return null
val woodType = ctx.state.values.entries.find {
PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value)
}
if (woodType != null) {
logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
logger.log(Level.DEBUG, "ForestryLogRegistry: block state $state")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type
val bark = woodType.value[IWoodType_barkTex]()
val heart = woodType.value[IWoodType_heartTex]()
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
// 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) return SimpleColumnInfo.Key(logger, StandardLogRegistry.getAxis(state), listOf(heart, heart, bark))
val heartSprite = atlas.sprite(heart)
val barkSprite = atlas.sprite(bark)
return atlas.afterStitch {
SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get()))
}
}
return null
}
}

View File

@@ -1,10 +1,8 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs
import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.*
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.Int3
import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.metaprog.reflectField
import net.minecraft.block.BlockState
@@ -13,7 +11,6 @@ import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import org.apache.logging.log4j.Level
/**
@@ -22,9 +19,7 @@ import org.apache.logging.log4j.Level
@Suppress("UNCHECKED_CAST")
object OptifineCustomColors {
val isColorAvailable = allAvailable(
Refs.CustomColors, Refs.getColorMultiplier
)
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
init {
Client.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
@@ -36,21 +31,19 @@ object OptifineCustomColors {
fun getBlockColor(ctx: CombinedContext): Int {
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.state, ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
} else null
return if (ofColor == null || ofColor == -1) ctx.lightingCtx.color else ofColor
}
}
class OptifineRenderEnv {
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
Refs.BlockState.element, Refs.BlockPos.element
).let {
val wrapped: Any = RenderEnv.element!!.getDeclaredConstructor(BlockState.element, BlockPos.element).let {
it.isAccessible = true
it.newInstance(null, null)
}
fun reset(state: BlockState, pos: BlockPos) {
Refs.RenderEnv_reset.invoke(wrapped, state, pos)
RenderEnv.reset.invoke(wrapped, state, pos)
}
}

View File

@@ -8,15 +8,11 @@ import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.lighting.QuadIconResolver
import mods.octarinecore.client.render.lighting.LightingCtx
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction
@@ -24,29 +20,31 @@ import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Logger
import java.util.concurrent.CompletableFuture
object IC2RubberIntegration {
val BlockRubWood = ClassRef("ic2.core.block.BlockRubWood")
val BlockRubWood = ClassRef<Any>("ic2.core.block.BlockRubWood")
init {
if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.addRegistry(IC2LogSupport)
LogRegistry.registries.add(IC2LogDiscovery)
AsyncSpriteProviderManager.providers.add(IC2LogDiscovery)
}
}
}
object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef("techreborn.blocks.BlockRubberLog")
val BlockRubberLog = ClassRef<Any>("techreborn.blocks.BlockRubberLog")
init {
if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.addRegistry(TechRebornLogSupport)
LogRegistry.registries.add(TechRebornLogDiscovery)
AsyncSpriteProviderManager.providers.add(TechRebornLogDiscovery)
}
}
}
@@ -67,27 +65,16 @@ class RubberLogInfo(
this.sideTextures[sideIdx]
}
}
class Key(override val logger: Logger, val axis: Axis?, val spotDir: Direction, val textures: List<String>): ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: AtlasTexture) = RubberLogInfo(
axis,
spotDir,
atlas[textures[0]] ?: missingSprite,
atlas[textures[1]] ?: missingSprite,
atlas[textures[2]] ?: missingSprite,
textures.drop(3).map { atlas[it] ?: missingSprite }
)
}
}
object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(state.block)) return null
val blockLoc = models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
val type = state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
val type = ctx.state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
@@ -97,11 +84,15 @@ object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
"plain_z" -> Axis.Z
else -> null
}
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(DEBUG, "IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[2]}")
return SimpleColumnInfo.Key(logger, axis, textureNames)
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.afterStitch {
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
// logs with rubber spot
@@ -114,32 +105,53 @@ object IC2LogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it) }
if (textureNames.any { it == "missingno" }) return null
logger.log(DEBUG, "IC2LogSupport: block state ${state.toString()}")
logger.log(DEBUG, "IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
return if (spotDir != null) RubberLogInfo.Key(logger, Axis.Y, spotDir, textureNames) else SimpleColumnInfo.Key(logger, Axis.Y, textureNames)
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
val upSprite = atlas.sprite(textureNames[0])
val downSprite = atlas.sprite(textureNames[1])
val sideSprite = atlas.sprite(textureNames[2])
val spotSprite = atlas.sprite(textureNames[3])
return if (spotDir != null) atlas.afterStitch {
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
} else atlas.afterStitch {
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
}
}
}
object TechRebornLogSupport : ModelRenderRegistryBase<ColumnTextureInfo>() {
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<ColumnTextureInfo>? {
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(state.block)) return null
val blockLoc = models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.map { it as? Pair<BlockModel, ResourceLocation> }.firstOrNull() ?: return null
val hasSap = state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
val hasSap = ctx.state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = ctx.state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
logger.log(DEBUG, "$logName: block state $state")
log("$logName: block state ${ctx.state}")
if (hasSap) {
val textureNames = listOf("end", "end", "sapside", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(DEBUG, "$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) return RubberLogInfo.Key(logger, Axis.Y, sapSide, textureNames)
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) }
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
val sapSprite = atlas.sprite(textureNames[2])
return atlas.afterStitch {
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
}
}
} else {
val textureNames = listOf("end", "end", "side").map { blockLoc.first.resolveTextureName(it) }
logger.log(DEBUG, "$logName: end=${textureNames[0]}, side=${textureNames[2]}")
if (textureNames.all { it != "missingno" })return SimpleColumnInfo.Key(logger, Axis.Y, textureNames)
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) }
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.afterStitch {
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
}
return null
}

View File

@@ -3,9 +3,10 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.loader.Refs
import mods.octarinecore.SVertexBuilder
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.metaprog.get
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
@@ -20,7 +21,7 @@ import org.apache.logging.log4j.Level.INFO
*/
object ShadersModIntegration {
@JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.pushNum, SVertexBuilder.pop)
val grassDefaultBlockId = 31L
val leavesDefaultBlockId = 18L
@@ -42,10 +43,10 @@ object ShadersModIntegration {
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(blockId: Long, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
if ((isAvailable && enabled)) {
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
Refs.pushEntity_num.invoke(vertexBuilder, blockId)
val vertexBuilder = renderer[mods.octarinecore.BufferBuilder.sVertexBuilder]!!
SVertexBuilder.pushNum.invoke(vertexBuilder, blockId)
func()
Refs.popEntity.invoke(vertexBuilder)
SVertexBuilder.pop.invoke(vertexBuilder)
} else {
func()
}

View File

@@ -5,30 +5,39 @@ 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.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.common.Int3
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.config.SimpleBlockMatcher
import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
import java.util.concurrent.CompletableFuture
object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: BlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, Axis.Y, textures)
init { BetterFoliage.modBus.register(this) }
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
val sprites = textures.map { atlas.sprite(Identifier(it)) }
return atlas.afterStitch {
SimpleColumnInfo(
Axis.Y,
sprites[0].get(),
sprites[1].get(),
sprites.drop(2).map { it.get() }
)
}
}
fun init() {
AsyncSpriteProviderManager.providers.add(this)
}
}
class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
@@ -73,12 +82,12 @@ class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled &&
StandardCactusRegistry[ctx] != null
AsyncCactusDiscovery[ctx] != null
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val icons = StandardCactusRegistry[ctx]!!
val icons = AsyncCactusDiscovery[ctx]!!
ctx.render(
modelStem.model,

View File

@@ -8,18 +8,17 @@ 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.BlockCtx
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.client.resource.ModelRenderRegistryRoot
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis
import net.minecraft.world.IEnviromentBlockReader
import org.apache.logging.log4j.Level
import java.util.concurrent.CompletableFuture
class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
@@ -49,12 +48,24 @@ class RoundLogOverlayLayer : ColumnRenderLayer() {
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>() {
object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = SimpleColumnInfo.Key(logger, getAxis(state), textures)
init { BetterFoliage.modBus.register(this) }
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo> {
val axis = getAxis(state)
logger.log(Level.DEBUG, "$logName: axis $axis")
val spriteList = textures.map { atlas.sprite(Identifier(it)) }
return atlas.afterStitch {
SimpleColumnInfo(
axis,
spriteList[0].get(),
spriteList[1].get(),
spriteList.drop(2).map { it.get() }
)
}
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
@@ -66,4 +77,9 @@ object StandardLogRegistry : ModelRenderRegistryConfigurable<ColumnTextureInfo>(
else -> null
}
}
fun init() {
LogRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
}
}

View File

@@ -1,14 +1,9 @@
package mods.betterfoliage.client.render.column
import mods.octarinecore.client.render.lighting.QuadIconResolver
import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.missingSprite
import mods.octarinecore.common.rotate
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.*
import org.apache.logging.log4j.Logger
interface ColumnTextureInfo {
val axis: Axis?
@@ -34,13 +29,4 @@ open class SimpleColumnInfo(
val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx]
}
class Key(override val logger: Logger, val axis: Axis?, val textures: List<String>) : ModelRenderKey<ColumnTextureInfo> {
override fun resolveSprites(atlas: AtlasTexture) = SimpleColumnInfo(
axis,
atlas[textures[0]] ?: missingSprite,
atlas[textures[1]] ?: missingSprite,
textures.drop(2).map { atlas[it] ?: missingSprite }
)
}
}

View File

@@ -1,4 +1,4 @@
package mods.octarinecore.resource
package mods.betterfoliage.client.resource
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.TextureAtlasSprite

View File

@@ -3,16 +3,16 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.lighting.HSB
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.lang.Math.min
import java.util.concurrent.CompletableFuture
const val defaultGrassColor = 0
@@ -32,28 +32,34 @@ class GrassInfo(
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
object StandardGrassRegistry : ModelRenderRegistryConfigurable<GrassInfo>() {
object AsyncGrassDiscovery : ConfigurableModelDiscovery<GrassInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = StandardGrassKey(logger, textures[0])
init { BetterFoliage.modBus.register(this) }
}
class StandardGrassKey(override val logger: Logger, val textureName: String) : ModelRenderKey<GrassInfo> {
override fun resolveSprites(atlas: AtlasTexture): GrassInfo {
val logName = "StandardGrassKey"
val texture = atlas[textureName] ?: 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}")
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
} else {
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<GrassInfo> {
val textureName = textures[0]
val spriteF = atlas.sprite(Identifier(textureName))
logger.log(Level.DEBUG, "$logName: texture $textureName")
return atlas.afterStitch {
val sprite = spriteF.get()
logger.log(Level.DEBUG, "$logName: block state $state")
logger.log(Level.DEBUG, "$logName: texture $textureName")
val hsb = HSB.fromColor(sprite.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
} else {
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null
}
GrassInfo(sprite, overrideColor)
}
return GrassInfo(texture, overrideColor)
}
fun init() {
GrassRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
}
}

View File

@@ -3,16 +3,14 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.HasLogger
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import java.util.concurrent.CompletableFuture
const val defaultLeafColor = 0
@@ -25,7 +23,7 @@ class LeafInfo(
val leafType: String,
/** Average color of the round leaf texture. */
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
val averageColor: Int = roundLeafTexture.averageColor
) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
@@ -33,27 +31,27 @@ class LeafInfo(
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
object StandardLeafRegistry : ModelRenderRegistryConfigurable<LeafInfo>() {
object AsyncLeafDiscovery : ConfigurableModelDiscovery<LeafInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(state: BlockState, textures: List<String>) = StandardLeafKey(logger, textures[0])
init { BetterFoliage.modBus.register(this) }
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture) = defaultRegisterLeaf(Identifier(textures[0]), atlas)
fun init() {
LeafRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
}
}
class StandardLeafKey(override val logger: Logger, val textureName: String) : ModelRenderKey<LeafInfo> {
lateinit var leafType: String
lateinit var generated: ResourceLocation
fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: AtlasFuture): CompletableFuture<LeafInfo> {
val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default"
val generated = Client.genLeaves.register(sprite, leafType)
val roundLeaf = atlas.sprite(generated)
override fun onPreStitch(event: TextureStitchEvent.Pre) {
val logName = "StandardLeafKey"
leafType = LeafParticleRegistry.typeMappings.getType(textureName) ?: "default"
generated = Client.genLeaves.register(ResourceLocation(textureName), leafType)
event.addSprite(generated)
logger.log(Level.DEBUG, "$logName: leaf texture $textureName")
logger.log(Level.DEBUG, "$logName: particle $leafType")
log(" leaf texture $sprite")
log(" particle $leafType")
return atlas.afterStitch {
LeafInfo(roundLeaf.get(), leafType)
}
override fun resolveSprites(atlas: AtlasTexture) = LeafInfo(atlas[generated] ?: missingSprite, leafType)
}

View File

@@ -1,130 +0,0 @@
package mods.betterfoliage.loader
//import mods.octarinecore.metaprog.Transformer
import mods.octarinecore.metaprog.allAvailable
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.*
/*
class BetterFoliageTransformer : Transformer() {
val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer)
init {
// where: WorldClient.animateTick(), replacing invocation to Block.animateTick
// what: invoke BF code for every random display tick
// why: allows us to catch random display ticks
transformMethod(Refs.WC_animeteTick) {
find(invokeRef(Refs.B_animateTick))?.replace {
log.info("[BetterFoliageLoader] Applying random display tick call hook")
invokeStatic(Refs.onRandomDisplayTick)
} ?: log.warn("[BetterFoliageLoader] Failed to apply random display tick call hook!")
}
// where: BlockStateContainer$StateImplementation.getAmbientOcclusionLightValue()
// what: invoke BF code to overrule AO transparency value
// why: allows us to have light behave properly on non-solid log blocks
transformMethod(Refs.getAmbientOcclusionLightValue) {
find(FRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying getAmbientOcclusionLightValue() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.getAmbientOcclusionLightValueOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply getAmbientOcclusionLightValue() override!")
}
// where: BlockStateContainer$StateImplementation.useNeighborBrightness()
// what: invoke BF code to overrule _useNeighborBrightness_
// why: allows us to have light behave properly on non-solid log blocks
transformMethod(Refs.useNeighborBrightness) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying useNeighborBrightness() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.useNeighborBrightnessOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply useNeighborBrightness() override!")
}
// where: BlockStateContainer$StateImplementation.doesSideBlockRendering()
// what: invoke BF code to overrule condition
// why: allows us to make log blocks non-solid
transformMethod(Refs.doesSideBlockRendering) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying doesSideBlockRendering() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.doesSideBlockRenderingOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply doesSideBlockRendering() override!")
}
// where: BlockStateContainer$StateImplementation.isOpaqueCube()
// what: invoke BF code to overrule condition
// why: allows us to make log blocks non-solid
transformMethod(Refs.isOpaqueCube) {
find(IRETURN)?.insertBefore {
log.info("[BetterFoliageLoader] Applying isOpaqueCube() override")
varinsn(ALOAD, 0)
invokeStatic(Refs.isOpaqueCubeOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply isOpaqueCube() override!")
}
// where: ModelBakery.setupModelRegistry(), after all models are loaded
// what: invoke handler code with ModelBakery instance
// why: allows us to iterate the unbaked models in ModelBakery in time to register textures
transformMethod(Refs.setupModelRegistry) {
find(invokeName("newLinkedHashSet"))?.insertBefore {
log.info("[BetterFoliageLoader] Applying ModelLoader lifecycle callback")
varinsn(ALOAD, 0)
invokeStatic(Refs.onAfterLoadModelDefinitions)
} ?: log.warn("[BetterFoliageLoader] Failed to apply ModelLoader lifecycle callback!")
}
// where: RenderChunk.rebuildChunk()
// what: replace call to BlockRendererDispatcher.renderBlock()
// why: allows us to perform additional rendering for each block
// what: invoke code to overrule result of Block.canRenderInLayer()
// why: allows us to render transparent quads for blocks which are only on the SOLID layer
transformMethod(Refs.rebuildChunk) {
applyWriterFlags(COMPUTE_FRAMES, COMPUTE_MAXS)
find(invokeRef(Refs.renderBlock))?.replace {
log.info("[BetterFoliageLoader] Applying RenderChunk block render override")
varinsn(ALOAD, if (isOptifinePresent) 22 else 20)
invokeStatic(Refs.renderWorldBlock)
}
if (isOptifinePresent) {
find(varinsn(ISTORE, 23))?.insertAfter {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
varinsn(ALOAD, 19)
varinsn(ALOAD, 18)
varinsn(ALOAD, 22)
invokeStatic(Refs.canRenderBlockInLayer)
varinsn(ISTORE, 23)
}
} else {
find(invokeRef(Refs.canRenderInLayer))?.replace {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
invokeStatic(Refs.canRenderBlockInLayer)
}
}
}
// where: net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
// what: make constructor public
// why: use vanilla AO calculation at will without duplicating code
transformMethod(Refs.AOF_constructor) {
log.info("[BetterFoliageLoader] Setting AmbientOcclusionFace constructor public")
makePublic()
}
// where: shadersmod.client.SVertexBuilder.pushEntity()
// what: invoke code to overrule block data
// why: allows us to change the block ID seen by shader programs
transformMethod(Refs.pushEntity_state) {
find(invokeRef(Refs.pushEntity_num))?.insertBefore {
log.info("[BetterFoliageLoader] Applying SVertexBuilder.pushEntity() block ID override")
varinsn(ALOAD, 0)
invokeStatic(Refs.getBlockIdOverride)
} ?: log.warn("[BetterFoliageLoader] Failed to apply SVertexBuilder.pushEntity() block ID override!")
}
}
}
*/

View File

@@ -1,58 +0,0 @@
package mods.betterfoliage.loader
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
/** Singleton object holding references to foreign code elements. */
object Refs {
// val mcVersion = FMLInjectionData.data()[4].toString()
// Java
val String = ClassRef("java.lang.String")
val Map = ClassRef("java.util.Map")
val List = ClassRef("java.util.List")
val Random = ClassRef("java.util.Random")
// Minecraft
val IBlockReader = ClassRef("net.minecraft.world.IBlockReader")
val IEnvironmentBlockReader = ClassRef("net.minecraft.world.IEnvironmentBlockReader")
val BlockState = ClassRef("net.minecraft.block.state.BlockState")
val BlockPos = ClassRef("net.minecraft.util.math.BlockPos")
val BlockRenderLayer = ClassRef("net.minecraft.util.BlockRenderLayer")
val Block = ClassRef("net.minecraft.block.Block")
val BufferBuilder = ClassRef("net.minecraft.client.renderer.BufferBuilder")
val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher")
val ChunkCache = ClassRef("net.minecraft.client.renderer.chunk.ChunkRenderCache")
val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite")
val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation")
// Optifine
val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer")
val OptifineChunkCache = ClassRef("net.optifine.override.ChunkCacheOF")
val CCOFChunkCache = FieldRef(OptifineChunkCache, "chunkCache", ChunkCache)
val getBlockId = MethodRef(BlockState, "getBlockId", ClassRef.int);
val getMetadata = MethodRef(BlockState, "getMetadata", ClassRef.int);
// Optifine
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, BlockState, BlockPos)
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
val BlockPosM = ClassRef("net.optifine.BlockPosM")
val IColorizer = ClassRef("net.optifine.CustomColors\$IColorizer")
// Optifine: custom colors
val CustomColors = ClassRef("net.optifine.CustomColors")
val getColorMultiplier = MethodRef(CustomColors, "getSmoothColorMultiplier", ClassRef.int, BlockState, IEnvironmentBlockReader, BlockPos, IColorizer, BlockPosM)
// Optifine: shaders
val SVertexBuilder = ClassRef("net.optifine.shaders.SVertexBuilder")
val sVertexBuilder = FieldRef(BufferBuilder, "sVertexBuilder", SVertexBuilder)
val pushEntity_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, BlockState, BlockPos, IBlockReader, BufferBuilder)
val pushEntity_num = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, ClassRef.long)
val popEntity = MethodRef(SVertexBuilder, "popEntity", ClassRef.void)
val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration")
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, BlockState)
}

View File

@@ -0,0 +1,68 @@
package mods.octarinecore
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.FieldRef
import mods.octarinecore.metaprog.MethodRef
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraft.world.IEnviromentBlockReader
import java.util.*
// Java
val String = ClassRef<String>("java.lang.String")
val Map = ClassRef<Map<*, *>>("java.util.Map")
val List = ClassRef<List<*>>("java.util.List")
val Random = ClassRef<Random>("java.util.Random")
// Minecraft
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
val IEnvironmentBlockReader = ClassRef<IEnviromentBlockReader>("net.minecraft.world.IEnvironmentBlockReader")
val BlockState = ClassRef<BlockState>("net.minecraft.block.state.BlockState")
val BlockPos = ClassRef<BlockPos>("net.minecraft.util.math.BlockPos")
val BlockRenderLayer = ClassRef<BlockRenderLayer>("net.minecraft.util.BlockRenderLayer")
val Block = ClassRef<Block>("net.minecraft.block.Block")
object BufferBuilder : ClassRef<BufferBuilder>("net.minecraft.client.renderer.BufferBuilder") {
/** Optifine only */
val sVertexBuilder = FieldRef(this, "sVertexBuilder", SVertexBuilder)
/** Optifine only */
val quadSprite = FieldRef(this, "quadSprite", TextureAtlasSprite)
}
val BlockRendererDispatcher = ClassRef<BlockRendererDispatcher>("net.minecraft.client.renderer.BlockRendererDispatcher")
val ChunkRenderCache = ClassRef<ChunkRenderCache>("net.minecraft.client.renderer.chunk.ChunkRenderCache")
val TextureAtlasSprite = ClassRef<TextureAtlasSprite>("net.minecraft.client.renderer.texture.TextureAtlasSprite")
val ResourceLocation = ClassRef<ResourceLocation>("net.minecraft.util.ResourceLocation")
// Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")
object ChunkCacheOF : ClassRef<Any>("net.optifine.override.ChunkCacheOF") {
val chunkCache = FieldRef(this, "chunkCache", ChunkRenderCache)
}
object RenderEnv : ClassRef<Any>("net.optifine.render.RenderEnv") {
val reset = MethodRef(this, "reset", void, BlockState, BlockPos)
}
// Optifine custom colors
val IColorizer = ClassRef<Any>("net.optifine.CustomColors\$IColorizer")
object CustomColors : ClassRef<Any>("net.optifine.CustomColors") {
val getColorMultiplier = MethodRef(this, "getSmoothColorMultiplier", int, BlockState, IEnvironmentBlockReader, BlockPos, IColorizer, BlockPosM)
}
// Optifine shaders
object SVertexBuilder : ClassRef<Any>("net.optifine.shaders.SVertexBuilder") {
val pushState = MethodRef(this, "pushEntity", void, BlockState, BlockPos, IBlockReader, BufferBuilder)
val pushNum = MethodRef(this, "pushEntity", void, long)
val pop = MethodRef(this, "popEntity", void)
}

View File

@@ -2,11 +2,12 @@
@file:Suppress("NOTHING_TO_INLINE")
package mods.octarinecore
import mods.betterfoliage.loader.Refs
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.*
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
import kotlin.reflect.KProperty
import java.lang.Math.*
@@ -118,4 +119,11 @@ fun nextPowerOf2(x: Int): Int {
*/
fun IWorldReader.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) {
if (isBlockLoaded(pos)) getTileEntity(pos) else null
}
interface HasLogger {
val logger: Logger
val logName: String get() = this::class.simpleName!!
fun log(msg: String) = log(Level.DEBUG, msg)
fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg")
}

View File

@@ -2,23 +2,21 @@ package mods.octarinecore.client.render
import mods.betterfoliage.client.render.canRenderInCutout
import mods.betterfoliage.client.render.isCutout
import mods.betterfoliage.loader.Refs
import mods.octarinecore.BufferBuilder
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.get
import mods.octarinecore.metaprog.set
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.fluid.Fluids
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IEnviromentBlockReader
import net.minecraft.world.LightType
import net.minecraft.world.biome.Biomes
import java.util.*
class CombinedContext(
val blockCtx: BlockCtx, val renderCtx: RenderCtx, val lightingCtx: DefaultLightingCtx
@@ -64,7 +62,7 @@ class CombinedContext(
if (drawIcon != null) {
// let OptiFine know the texture we're using, so it can
// transform UV coordinates to quad-relative
Refs.quadSprite.set(renderCtx!!.renderBuffer, drawIcon)
renderCtx.renderBuffer[BufferBuilder.quadSprite] = drawIcon
quad.verts.forEachIndexed { vertIdx, vert ->
temp.init(vert).rotate(lightingCtx.modelRotation).translate(translation)

View File

@@ -0,0 +1,59 @@
package mods.octarinecore.client.resource
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.resource.Sprite
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.profiler.IProfiler
import net.minecraft.resources.IResourceManager
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.function.Consumer
import java.util.function.Function
class StitchPhases(
val discovery: CompletableFuture<Void>,
val cleanup: CompletableFuture<Void>
)
interface AsyncSpriteProvider {
fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases
}
object AsyncSpriteProviderManager {
val providers = mutableListOf<ModelDiscovery<*>>()
fun onStitchBlockAtlas(bakeryObj: Any, atlas: AtlasTexture, manager: IResourceManager, idList: Iterable<Identifier>, profiler: IProfiler): AtlasTexture.SheetData {
profiler.startSection("additional-sprites")
val bakery = CompletableFuture<ModelBakery>()
val atlasFuture = AtlasFuture(idList)
val phases = providers.map { it.setup(bakery, atlasFuture) }
bakery.complete(bakeryObj as ModelBakery)
phases.forEach { it.discovery.get() }
val sheetData = atlas.stitch(manager, idList, profiler)
atlasFuture.sheet.complete(sheetData)
phases.forEach { it.cleanup.get() }
profiler.endSection()
return sheetData
}
}
class AtlasFuture(initial: Iterable<Identifier>) {
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>().apply { addAll(initial) })
val sheet = CompletableFuture<AtlasTexture.SheetData>()
fun sprite(id: String) = sprite(Identifier(id))
fun sprite(id: Identifier): CompletableFuture<Sprite> {
idSet.add(id)
return sheet.thenApply { sheetData -> sheetData.sprites.find { it.name == id } ?: throw IllegalStateException("Atlas does not contain $id") }.toCompletableFuture()
}
fun <T> afterStitch(supplier: ()->T): CompletableFuture<T> = sheet.thenApplyAsync(Function { supplier() }, Minecraft.getInstance())
}
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)
fun <T> CompletableFuture<T>.thenRunAsync(run: (T)->Unit) = thenAcceptAsync(Consumer(run), Minecraft.getInstance()).toCompletableFuture()!!
fun Collection<CompletableFuture<*>>.allComplete() = CompletableFuture.allOf(*toTypedArray())

View File

@@ -0,0 +1,132 @@
package mods.octarinecore.client.resource
import com.google.common.base.Joiner
import mods.betterfoliage.client.resource.ModelIdentifier
import mods.octarinecore.HasLogger
import mods.octarinecore.client.render.BlockCtx
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.common.plus
import mods.octarinecore.findFirst
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraftforge.registries.ForgeRegistries
import java.util.concurrent.CompletableFuture
interface ModelRenderRegistry<T> {
operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos)
operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset)
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
}
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
val registries = mutableListOf<ModelRenderRegistry<T>>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] }
}
class ModelDiscoveryContext(
bakery: ModelBakery,
val state: BlockState,
val modelId: ModelIdentifier
) {
val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId)
.filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) }
fun ModelBakery.unwrapVariants(modelAndLoc: Pair<IUnbakedModel, ResourceLocation>): List<Pair<IUnbakedModel, ResourceLocation>> = when(val model = modelAndLoc.first) {
is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) }
is BlockModel -> listOf(modelAndLoc)
else -> emptyList()
}
}
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider, ModelRenderRegistry<T> {
var modelData: Map<BlockState, T> = emptyMap()
protected set
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state]
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>?
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
val modelDataTemp = mutableMapOf<BlockState, CompletableFuture<T>>()
return StitchPhases(
discovery = bakeryFuture.thenRunAsync { bakery ->
var errors = 0
bakery.iterateModels { ctx ->
try {
processModel(ctx, atlasFuture)?.let { modelDataTemp[ctx.state] = it }
} catch (e: Exception) {
errors++
}
}
log("${modelDataTemp.size} BlockStates discovered, $errors errors")
},
cleanup = atlasFuture.sheet.thenRunAsync { sheetData ->
modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() }
val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size
log("${modelData.size} BlockStates loaded, $errors errors")
}
)
}
fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) {
ForgeRegistries.BLOCKS.flatMap { block ->
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
}.forEach { (state, stateModelResource) ->
func(ModelDiscoveryContext(this, state, stateModelResource))
}
}
}
abstract class ConfigurableModelDiscovery<T> : ModelDiscovery<T>() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<T>?
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>? {
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
log("block state ${ctx.state.toString()}")
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
if (ctx.models.isEmpty()) {
log(" no models found")
return null
}
ctx.models.filter { it.first is BlockModel }.forEach { (model, location) ->
model as BlockModel
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
if (modelMatch != null) {
log(" model ${model} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
log(" sprites [$texMapString]")
if (textures.all { it.second != "missingno" }) {
// found a valid model (all required textures exist)
return processModel(ctx.state, textures.map { it.second}, atlas)
}
}
}
return null
}
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
(matchClasses as? ConfigurableBlockMatcher)?.readDefaults()
return super.setup(bakeryFuture, atlasFuture)
}
}

View File

@@ -1,138 +0,0 @@
package mods.octarinecore.client.resource
import com.google.common.base.Joiner
import mods.octarinecore.client.render.BlockCtx
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.common.plus
import mods.octarinecore.findFirst
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.*
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraft.world.IEnviromentBlockReader
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger
class LoadModelDataEvent(val bakery: ModelBakery) : Event()
interface ModelRenderRegistry<T> {
operator fun get(ctx: BlockCtx) = get(ctx.state(Int3.zero), ctx.world, ctx.pos)
operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset)
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
}
interface ModelRenderDataExtractor<T> {
fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): ModelRenderKey<T>?
}
interface ModelRenderKey<T> {
val logger: Logger?
fun onPreStitch(event: TextureStitchEvent.Pre) {}
fun resolveSprites(atlas: AtlasTexture): T
}
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
val subRegistries = mutableListOf<ModelRenderRegistry<T>>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = subRegistries.findFirst { it[state, world, pos] }
fun addRegistry(registry: ModelRenderRegistry<T>) {
subRegistries.add(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<BlockState, ModelRenderKey<T>>()
var stateToValue = mapOf<BlockState, T>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = stateToValue[state]
@Suppress("UNCHECKED_CAST")
@SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) {
stateToValue = emptyMap()
val stateMappings = ForgeRegistries.BLOCKS.flatMap { block ->
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
}
// val unbakedModels = (Refs.unbakedModels.get(event.loader) as Map<*, *>).filter { it.value is IUnbakedModel } as Map<ModelResourceLocation, IUnbakedModel>
val missingModel = event.bakery.getUnbakedModel(ModelBakery.MODEL_MISSING)
stateMappings.forEach { (state, stateModelResource) ->
val allModels = event.bakery.let { it.unwrapVariants(it.getUnbakedModel(stateModelResource) to stateModelResource) }.filter { it.second != missingModel }
try {
processModel(state, stateModelResource, allModels)?.let { stateToKey[state] = it }
} catch (e: Exception) {
logger?.warn("Exception while trying to process model ${stateModelResource}", e)
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.basePath != "textures") return
stateToKey.forEach { (_, key) -> key.onPreStitch(event) }
}
@SubscribeEvent(priority = EventPriority.LOW)
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (event.map.basePath != "textures") return
stateToValue = stateToKey.mapValues { (_, key) -> key.resolveSprites(event.map) }
stateToKey.clear()
}
}
abstract class ModelRenderRegistryConfigurable<T> : ModelRenderRegistryBase<T>() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
override fun processModel(state: BlockState, modelLoc: ModelResourceLocation, models: List<Pair<IUnbakedModel, ResourceLocation>>): 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}")
if (models.isEmpty()) {
logger?.log(Level.DEBUG, "$logName: no models found")
return null
}
models.filter { it.first is BlockModel }.forEach { (model, location) ->
model as BlockModel
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
if (modelMatch != null) {
logger?.log(Level.DEBUG, "$logName: model ${model} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
if (textures.all { it.second != "missingno" }) {
// found a valid model (all required textures exist)
return processModel(state, textures.map { it.second} )
}
}
}
return null
}
abstract fun processModel(state: BlockState, textures: List<String>) : ModelRenderKey<T>?
}
fun ModelBakery.unwrapVariants(modelAndLoc: Pair<IUnbakedModel, ResourceLocation>): List<Pair<IUnbakedModel, ResourceLocation>> = when(val model = modelAndLoc.first) {
is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) }
is BlockModel -> listOf(modelAndLoc)
else -> emptyList()
}

View File

@@ -1,10 +1,10 @@
package mods.octarinecore.client.resource
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.resource.Sprite
import mods.octarinecore.client.render.Model
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.resource.Identifier
import mods.octarinecore.resource.Sprite
import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.AtlasTexture
@@ -63,10 +63,10 @@ open class ResourceHandler(
// ============================
// Resource declarations
// ============================
fun iconStatic(location: ()->Identifier) = IconHolder(location).apply { resources.add(this) }
fun iconStatic(location: ()-> Identifier) = IconHolder(location).apply { resources.add(this) }
fun iconStatic(location: Identifier) = iconStatic { location }
fun iconStatic(domain: String, path: String) = iconStatic(Identifier(domain, path))
fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)->Identifier) = IconSet(targetAtlas, location).apply { this@ResourceHandler.resources.add(this) }
fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)-> Identifier) = IconSet(targetAtlas, location).apply { this@ResourceHandler.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 vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
@@ -102,7 +102,7 @@ open class ResourceHandler(
// ============================
// Resource container classes
// ============================
class IconHolder(val location: ()->Identifier) : IStitchListener {
class IconHolder(val location: ()-> Identifier) : IStitchListener {
var iconRes: Identifier? = null
var icon: Sprite? = null
override fun onPreStitch(event: TextureStitchEvent.Pre) {
@@ -119,7 +119,7 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
override fun onConfigChange() { model = Model().apply(init) }
}
class IconSet(val targetAtlas: Atlas, val location: (Int)->Identifier) : IStitchListener {
class IconSet(val targetAtlas: Atlas, val location: (Int)-> Identifier) : IStitchListener {
val resources = arrayOfNulls<Identifier>(16)
val icons = arrayOfNulls<Sprite>(16)
var num = 0

View File

@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.lang.Math.*
import javax.imageio.ImageIO
import kotlin.math.atan2
/** Concise getter for the Minecraft resource manager. */
val resourceManager: SimpleReloadableResourceManager
@@ -65,19 +66,15 @@ val BufferedImage.asStream: InputStream get() =
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space.
*/
val TextureAtlasSprite.averageColor: Int? get() {
// val locationNoDirs = ResourceLocation(iconName).stripStart("blocks/")
// val locationWithDirs = ResourceLocation(locationNoDirs.namespace, "textures/blocks/%s.png".format(locationNoDirs.path))
// val image = resourceManager[locationWithDirs]?.loadImage() ?: return null
val TextureAtlasSprite.averageColor: Int get() {
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0..width - 1)
for (y in 0..height - 1) {
val pixel = getPixelRGBA(0, x, y);
for (x in 0 until width)
for (y in 0 until height) {
val pixel = getPixelRGBA(0, x, y)
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
@@ -90,7 +87,7 @@ val TextureAtlasSprite.averageColor: Int? get() {
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY.toDouble(), sumHueX.toDouble()) / PI2 + 0.5).toFloat()
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
}

View File

@@ -5,6 +5,8 @@ import java.lang.reflect.Field
import java.lang.reflect.Method
import mods.octarinecore.metaprog.Namespace.*
import mods.octarinecore.tryDefault
import kotlin.reflect.KCallable
import kotlin.reflect.KFunction
/** Get a Java class with the given name. */
fun getJavaClass(name: String) = tryDefault(null) { Class.forName(name) }
@@ -66,19 +68,19 @@ fun allAvailable(vararg codeElement: Resolvable<*>) = codeElement.all { it.eleme
*
* @param[name] MCP name of the class
*/
open class ClassRef(val name: String) : Resolvable<Class<*>>() {
open class ClassRef<T: Any?>(val name: String) : Resolvable<Class<T>>() {
companion object {
val int = ClassRefPrimitive("I", Int::class.java)
val long = ClassRefPrimitive("J", Long::class.java)
val float = ClassRefPrimitive("F", Float::class.java)
val boolean = ClassRefPrimitive("Z", Boolean::class.java)
val void = ClassRefPrimitive("V", null)
val void = ClassRefPrimitive("V", Void::class.java)
}
open fun asmDescriptor(namespace: Namespace) = "L${name.replace(".", "/")};"
override fun resolve() = getJavaClass(name)
override fun resolve() = getJavaClass(name) as Class<T>?
fun isInstance(obj: Any) = element?.isInstance(obj) ?: false
}
@@ -89,18 +91,11 @@ open class ClassRef(val name: String) : Resolvable<Class<*>>() {
* @param[name] ASM descriptor of this primitive type
* @param[clazz] class of this primitive type
*/
class ClassRefPrimitive(name: String, val clazz: Class<*>?) : ClassRef(name) {
class ClassRefPrimitive<T>(name: String, val clazz: Class<T>?) : ClassRef<T>(name) {
override fun asmDescriptor(namespace: Namespace) = name
override fun resolve() = clazz
}
class ClassRefArray(name: String) : ClassRef(name) {
override fun asmDescriptor(namespace: Namespace) = "[" + super.asmDescriptor(namespace)
override fun resolve() = getJavaClass("[L$name;")
}
fun ClassRef.array() = ClassRefArray(name)
/**
* Reference to a method.
*
@@ -110,13 +105,13 @@ fun ClassRef.array() = ClassRefArray(name)
* @param[returnType] reference to the return type
* @param[returnType] references to the argument types
*/
class MethodRef(val parentClass: ClassRef,
class MethodRef<T: Any?>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val returnType: ClassRef,
vararg val argTypes: ClassRef
val returnType: ClassRef<T>,
vararg val argTypes: ClassRef<*>
) : Resolvable<Method>() {
constructor(parentClass: ClassRef, mcpName: String, returnType: ClassRef, vararg argTypes: ClassRef) :
constructor(parentClass: ClassRef<*>, mcpName: String, returnType: ClassRef<T>, vararg argTypes: ClassRef<*>) :
this(parentClass, mcpName, mcpName, returnType, *argTypes)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
@@ -133,11 +128,10 @@ class MethodRef(val parentClass: ClassRef,
}
/** Invoke this method using reflection. */
fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args)
operator fun invoke(receiver: Any, vararg args: Any?) = element?.invoke(receiver, *args) as T
/** Invoke this static method using reflection. */
fun invokeStatic(vararg args: Any) = element?.invoke(null, *args)
}
/**
@@ -148,30 +142,41 @@ class MethodRef(val parentClass: ClassRef,
* @param[srgName] SRG name of the field
* @param[type] reference to the field type
*/
class FieldRef(val parentClass: ClassRef,
class FieldRef<T>(val parentClass: ClassRef<*>,
val mcpName: String,
val srgName: String,
val type: ClassRef?
val type: ClassRef<T>
) : Resolvable<Field>() {
constructor(parentClass: ClassRef, mcpName: String, type: ClassRef?) : this(parentClass, mcpName, mcpName, type)
constructor(parentClass: ClassRef<*>, mcpName: String, type: ClassRef<T>) : this(parentClass, mcpName, mcpName, type)
fun name(namespace: Namespace) = when(namespace) { SRG -> srgName; MCP -> mcpName }
fun asmDescriptor(namespace: Namespace) = type!!.asmDescriptor(namespace)
fun asmDescriptor(namespace: Namespace) = type.asmDescriptor(namespace)
override fun resolve(): Field? =
if (parentClass.element == null) null
else {
listOf(srgName, mcpName).map { tryDefault(null) {
listOf(srgName, mcpName).mapNotNull { tryDefault(null) {
parentClass.element!!.getDeclaredField(it)
}}.filterNotNull().firstOrNull()
} }.firstOrNull()
?.apply{ isAccessible = true }
}
/** Get this field using reflection. */
fun get(receiver: Any?) = element?.get(receiver)
operator fun get(receiver: Any?) = element?.get(receiver) as T
/** Get this static field using reflection. */
fun getStatic() = get(null)
fun set(receiver: Any?, obj: Any?) { element?.set(receiver, obj) }
}
fun Any.isInstance(cls: ClassRef<*>) = cls.isInstance(this)
interface ReflectionCallable<T> {
operator fun invoke(vararg args: Any): T
}
inline operator fun <reified T> Any.get(field: FieldRef<T>) = field.get(this)
inline operator fun <reified T> Any.set(field: FieldRef<T>, value: T) = field.set(this, value)
inline operator fun <T> Any.get(methodRef: MethodRef<T>) = object : ReflectionCallable<T> {
override fun invoke(vararg args: Any) = methodRef.invoke(this@get, args)
}

View File

@@ -7,14 +7,4 @@ public net.minecraft.block.BlockState$Cache
public net.minecraft.client.renderer.chunk.ChunkRenderCache field_212408_i #world
#public net.minecraft.client.renderer.block.model.ModelBakery field_177610_k # blockModelShapes
#public net.minecraft.client.renderer.block.statemap.BlockStateMapper field_178450_a # blockStateMap
#public net.minecraft.client.renderer.BufferBuilder field_178999_b # rawIntBuffer
#public net.minecraft.client.renderer.BufferBuilder func_181670_b(I)V # growBuffer
#public net.minecraft.client.renderer.BufferBuilder func_181664_j()I # getBufferSize
#public net.minecraft.client.renderer.BlockModelRenderer field_187499_a # blockColors
#public net.minecraft.world.ChunkCache field_72815_e # world
public net.minecraft.client.renderer.texture.AtlasTexture$SheetData field_217808_d # sprites