[WIP] more async texture loading

keep stitch() call in original method body, in case others want to mod it too
This commit is contained in:
octarine-noise
2020-01-06 17:09:40 +01:00
parent a3d99c3076
commit 4efa831296
43 changed files with 530 additions and 633 deletions

View File

@@ -15,6 +15,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
* Needed to avoid excessive darkening of Round Logs at the corners, now that they are not full blocks.
*/
@Mixin(BlockState.class)
@SuppressWarnings({"UnnecessaryQualifiedMemberReference", "deprecation"})
public class BlockStateMixin {
private static final String callFrom = "Lnet/minecraft/block/BlockState;func_215703_d(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";
private static final String callTo = "Lnet/minecraft/block/Block;func_220080_a(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)F";

View File

@@ -18,7 +18,7 @@ import java.util.Random;
@Mixin(ChunkRender.class)
public class ChunkRenderMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String renderBlock = "Lnet/minecraft/client/renderer/BlockRendererDispatcher;renderBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;Ljava/util/Random;Lnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = renderBlock))

View File

@@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(ChunkRender.class)
public class ChunkRenderOptifineMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String invokeReflector = "Lnet/optifine/reflect/Reflector;callBoolean(Ljava/lang/Object;Lnet/optifine/reflect/ReflectorMethod;[Ljava/lang/Object;)Z";
private static final String forgeBlockCanRender = "Lnet/minecraft/client/renderer/chunk/ChunkRender;FORGE_BLOCK_CAN_RENDER_IN_LAYER:Z";

View File

@@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ChunkRender.class)
public class ChunkRenderVanillaMixin {
private static final String rebuildChunk = "Lnet/minecraft/client/renderer/chunk/ChunkRender;rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String rebuildChunk = "rebuildChunk(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderTask;)V";
private static final String canRenderInLayer = "Lnet/minecraft/block/BlockState;canRenderInLayer(Lnet/minecraft/util/BlockRenderLayer;)Z";
@Redirect(method = rebuildChunk, at = @At(value = "INVOKE", target = canRenderInLayer))

View File

@@ -1,7 +1,7 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.client.Hooks;
import mods.octarinecore.client.resource.AsyncSpriteProviderManager;
import mods.betterfoliage.BetterFoliage;
import mods.octarinecore.client.resource.AsnycSpriteProviderManager;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.profiler.IProfiler;
@@ -9,18 +9,19 @@ 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)
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 processLoading = "processLoading(Lnet/minecraft/profiler/IProfiler;)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;";
@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);
AsnycSpriteProviderManager.StitchWrapper wrapper = BetterFoliage.INSTANCE.getBlockSprites().prepare(this, atlas, manager, idList, profiler);
AtlasTexture.SheetData sheet = atlas.stitch(manager, wrapper.getIdList(), profiler);
wrapper.complete(sheet);
return sheet;
}
}

View File

@@ -0,0 +1,29 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliage;
import mods.octarinecore.client.resource.AsnycSpriteProviderManager;
import net.minecraft.client.particle.ParticleManager;
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.Redirect;
@Mixin(ParticleManager.class)
public class ParticleManagerMixin {
private static final String reload = "reload(Lnet/minecraft/resources/IFutureReloadListener$IStage;Lnet/minecraft/resources/IResourceManager;Lnet/minecraft/profiler/IProfiler;Lnet/minecraft/profiler/IProfiler;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;";
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;";
// ewww :S
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = "*", at = @At(value = "INVOKE", target = stitch))
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Iterable<ResourceLocation> idList, IProfiler profiler) {
AsnycSpriteProviderManager.StitchWrapper wrapper = BetterFoliage.INSTANCE.getParticleSprites().prepare(this, atlas, manager, idList, profiler);
AtlasTexture.SheetData sheet = atlas.stitch(manager, wrapper.getIdList(), profiler);
wrapper.complete(sheet);
return sheet;
}
}

View File

@@ -1,14 +1,21 @@
package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.resource.GeneratorPack
import net.alexwells.kottle.FMLKotlinModLoadingContext
import mods.betterfoliage.client.render.RisingSoulTextures
import mods.octarinecore.client.gui.textComponent
import mods.octarinecore.client.resource.AsnycSpriteProviderManager
import mods.octarinecore.client.resource.AsyncSpriteProvider
import mods.octarinecore.client.resource.Atlas
import mods.octarinecore.client.resource.GeneratedBlockTexturePack
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import org.apache.logging.log4j.Level.DEBUG
import net.minecraft.client.particle.ParticleManager
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TranslationTextComponent
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
@@ -16,17 +23,11 @@ import java.io.File
import java.io.PrintStream
import java.util.*
@Mod(BetterFoliage.MOD_ID)
object BetterFoliage {
const val MOD_ID = ""
const val MOD_NAME = "Better Foliage"
val modBus = FMLKotlinModLoadingContext.get().modEventBus
var log = LogManager.getLogger("BetterFoliage")
var logDetail = SimpleLogger(
"BetterFoliage",
DEBUG,
Level.DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
@@ -37,16 +38,39 @@ object BetterFoliage {
})
)
val genPack = GeneratorPack(
"bf_gen",
"Better Foliage generated assets",
"bf_generated_pack.png"
)
val blockSprites = AsnycSpriteProviderManager<ModelBakery>("bf-blocks-extra")
val particleSprites = AsnycSpriteProviderManager<ParticleManager>("bf-particles-extra")
val asyncPack = GeneratedBlockTexturePack("bf_gen", "Better Foliage generated assets", logDetail)
fun getSpriteManager(atlas: Atlas) = when(atlas) {
Atlas.BLOCKS -> blockSprites
Atlas.PARTICLES -> particleSprites
} as AsnycSpriteProviderManager<Any>
init {
log.log(DEBUG, "Constructing mod")
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
Minecraft.getInstance().resourcePackList.addPackFinder(genPack.packFinder)
Client.init()
blockSprites.providers.add(asyncPack)
}
fun log(level: Level, msg: String) {
log.log(level, "[BetterFoliage] $msg")
logDetail.log(level, msg)
}
fun logDetail(msg: String) {
logDetail.log(Level.DEBUG, msg)
}
fun logRenderError(state: BlockState, location: BlockPos) {
if (state in Client.suppressRenderErrors) return
Client.suppressRenderErrors.add(state)
val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString()
val blockLoc = "${location.x},${location.y},${location.z}"
Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent(
"betterfoliage.rendererror",
textComponent(blockName, TextFormatting.GOLD),
textComponent(blockLoc, TextFormatting.GOLD)
))
logDetail("Error rendering block $state at $blockLoc")
}
}

View File

@@ -0,0 +1,35 @@
package mods.betterfoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.resource.AsnycSpriteProviderManager
import mods.octarinecore.client.resource.GeneratedBlockTexturePack
import net.alexwells.kottle.FMLKotlinModLoadingContext
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.ParticleManager
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
@Mod(BetterFoliageMod.MOD_ID)
object BetterFoliageMod {
const val MOD_ID = "betterfoliage"
val bus = FMLKotlinModLoadingContext.get().modEventBus
init {
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
Minecraft.getInstance().resourcePackList.addPackFinder(BetterFoliage.asyncPack.finder)
bus.register(BlockConfig)
Client.init()
}
}

View File

@@ -1,6 +1,6 @@
package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.integration.ForestryIntegration
@@ -8,11 +8,11 @@ import mods.betterfoliage.client.integration.IC2RubberIntegration
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.TechRebornRubberIntegration
import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.*
import mods.betterfoliage.client.texture.AsyncGrassDiscovery
import mods.betterfoliage.client.texture.AsyncLeafDiscovery
import mods.betterfoliage.client.texture.LeafParticleRegistry
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
@@ -32,15 +32,7 @@ object Client {
val suppressRenderErrors = mutableSetOf<BlockState>()
// texture generators
val genGrass = GrassGenerator("bf_gen_grass")
val genLeaves = LeafGenerator("bf_gen_leaves")
val genReeds = CenteringTextureGenerator("bf_gen_reeds", 1, 2)
fun init() {
// add resource generators to pack
listOf(genGrass, genLeaves, genReeds).forEach { BetterFoliage.genPack.generators.add(it) }
// init renderers
renderers = listOf(
RenderGrass(),
@@ -60,7 +52,6 @@ object Client {
// init other singletons
val singletons = listOf(
BlockConfig,
LeafParticleRegistry,
ChunkOverlayManager,
LeafWindTracker,
RisingSoulTextures
@@ -75,6 +66,8 @@ object Client {
TechRebornRubberIntegration
)
LeafParticleRegistry.init()
// add basic block support instances as last
AsyncLeafDiscovery.init()
AsyncGrassDiscovery.init()
@@ -84,28 +77,5 @@ object Client {
configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance<IConfigChangeListener>()
configListeners.forEach { it.onConfigChange() }
}
fun log(level: Level, msg: String) {
BetterFoliage.log.log(level, "[BetterFoliage] $msg")
BetterFoliage.logDetail.log(level, msg)
}
fun logDetail(msg: String) {
BetterFoliage.logDetail.log(Level.DEBUG, msg)
}
fun logRenderError(state: BlockState, location: BlockPos) {
if (state in suppressRenderErrors) return
suppressRenderErrors.add(state)
val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString()
val blockLoc = "${location.x},${location.y},${location.z}"
Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent(
"betterfoliage.rendererror",
textComponent(blockName, TextFormatting.GOLD),
textComponent(blockLoc, TextFormatting.GOLD)
))
logDetail("Error rendering block $state at $blockLoc")
}
}

View File

@@ -1,7 +1,6 @@
@file:JvmName("Hooks")
package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
@@ -9,16 +8,13 @@ import mods.betterfoliage.client.render.*
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.common.plus
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.BlockRenderLayer.CUTOUT

View File

@@ -1,14 +1,17 @@
package mods.betterfoliage.client.config
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.common.config.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
private fun featureEnable() = boolean(true).lang("enabled")
// Config singleton
object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) {
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_ID) {
val enabled by boolean(true)
val nVidia by boolean(false)
@@ -160,7 +163,15 @@ object BlockConfig {
val cactus = blocks("cactus_default.cfg")
val netherrack = blocks("netherrack_blocks_default.cfg")
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) }
init { BetterFoliageMod.bus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun onConfig(event: ModConfig.ModConfigEvent) {
list.forEach { when(it) {
is ConfigurableBlockMatcher -> it.readDefaults()
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}

View File

@@ -18,6 +18,7 @@ import mods.octarinecore.metaprog.ClassRef.Companion.boolean
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.resources.IResourceManager
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraftforge.fml.ModList
@@ -57,7 +58,7 @@ object ForestryIntegration {
}
}
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry<LeafInfo> {
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<LeafInfo> {
override val logger = BetterFoliage.logDetail
var idToValue = emptyMap<Identifier, LeafInfo>()
@@ -79,11 +80,11 @@ object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegist
return idToValue[textureLoc]
}
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
val futures = mutableMapOf<Identifier, CompletableFuture<LeafInfo>>()
return StitchPhases(
discovery = bakeryFuture.thenRunAsync {
discovery = bakeryF.thenRunAsync {
val allLeaves = TextureLeaves_leafTextures.getStatic()
allLeaves.entries.forEach { (type, leaves) ->
log("base leaf type $type")
@@ -96,7 +97,7 @@ object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegist
}
}
},
cleanup = atlasFuture.sheet.thenRunAsync {
cleanup = atlasFuture.runAfter {
idToValue = futures.mapValues { it.value.get() }
}
)
@@ -124,7 +125,7 @@ object ForestryLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
val heartSprite = atlas.sprite(heart)
val barkSprite = atlas.sprite(bark)
return atlas.afterStitch {
return atlas.mapAfter {
SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get()))
}
}

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.octarinecore.*
import mods.octarinecore.client.render.CombinedContext
@@ -22,7 +23,7 @@ object OptifineCustomColors {
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
init {
Client.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
BetterFoliage.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
}
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }

View File

@@ -1,7 +1,6 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
@@ -13,14 +12,12 @@ import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction
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.Logger
import java.util.concurrent.CompletableFuture
object IC2RubberIntegration {
@@ -29,9 +26,9 @@ object IC2RubberIntegration {
init {
if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
Client.log(Level.INFO, "IC2 rubber support initialized")
BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
LogRegistry.registries.add(IC2LogDiscovery)
AsyncSpriteProviderManager.providers.add(IC2LogDiscovery)
BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
}
}
}
@@ -42,9 +39,9 @@ object TechRebornRubberIntegration {
init {
if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
Client.log(Level.INFO, "TechReborn rubber support initialized")
BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.registries.add(TechRebornLogDiscovery)
AsyncSpriteProviderManager.providers.add(TechRebornLogDiscovery)
BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
}
}
}
@@ -90,7 +87,7 @@ object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
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 {
return atlas.mapAfter {
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
@@ -111,9 +108,9 @@ object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
val downSprite = atlas.sprite(textureNames[1])
val sideSprite = atlas.sprite(textureNames[2])
val spotSprite = atlas.sprite(textureNames[3])
return if (spotDir != null) atlas.afterStitch {
return if (spotDir != null) atlas.mapAfter {
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
} else atlas.afterStitch {
} else atlas.mapAfter {
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
}
}
@@ -138,7 +135,7 @@ object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
val sapSprite = atlas.sprite(textureNames[2])
return atlas.afterStitch {
return atlas.mapAfter {
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
}
}
@@ -148,7 +145,7 @@ object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.afterStitch {
return atlas.mapAfter {
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.client.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
@@ -37,7 +38,7 @@ object ShadersModIntegration {
}
init {
Client.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
BetterFoliage.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
}
/** Quads rendered inside this block will use the given block entity data in shader programs. */
@@ -59,9 +60,9 @@ object ShadersModIntegration {
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.grassId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func)
renderAs(Config.shaders.grassId, MODEL, ctx.renderCtx.renderBuffer, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.leavesId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func)
renderAs(Config.shaders.leavesId, MODEL, ctx.renderCtx.renderBuffer, enabled, func)
}

View File

@@ -1,8 +1,10 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.resource.Atlas
import mods.octarinecore.client.resource.ResourceHandler
@@ -59,17 +61,13 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
prevPos = previous,
size = scale,
alpha = alpha,
icon = RisingSoulTextures.trackIcon.icon!!
icon = RisingSoulTextures.trackIcon
)
}
}
}
object RisingSoulTextures : ResourceHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus, targetAtlas = Atlas.PARTICLES) {
val headIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "rising_soul_$idx") }
val trackIcon = iconStatic(BetterFoliage.MOD_ID, "soul_track")
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${headIcons.num} soul particle textures")
}
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track"))
}

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
@@ -12,17 +13,13 @@ import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
import org.apache.logging.log4j.Level.DEBUG
class RenderAlgae : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderAlgae : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val algaeIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_algae_$idx") }
val algaeIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") }
val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) }
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${algaeIcons.num} algae textures")
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.algae.enabled &&
ctx.state(up2).material == Material.WATER &&
@@ -38,7 +35,7 @@ class RenderAlgae : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.render(
algaeModels[rand[2]],
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! }
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]] }
)
}
}

View File

@@ -1,7 +1,7 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
@@ -15,7 +15,6 @@ import mods.octarinecore.common.config.SimpleBlockMatcher
import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
import java.util.concurrent.CompletableFuture
@@ -25,7 +24,7 @@ object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: BlockState, textures: List<String>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
val sprites = textures.map { atlas.sprite(Identifier(it)) }
return atlas.afterStitch {
return atlas.mapAfter {
SimpleColumnInfo(
Axis.Y,
sprites[0].get(),
@@ -36,17 +35,17 @@ object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
}
fun init() {
AsyncSpriteProviderManager.providers.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}
class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderCactus : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross = iconStatic(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_cactus"))
val iconArm = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_cactus_arm_$idx") }
val iconCross by sprite(Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus"))
val iconArm = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx") }
val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
@@ -76,10 +75,6 @@ class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
}
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${iconArm.num} cactus arm textures")
}
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled &&
AsyncCactusDiscovery[ctx] != null
@@ -97,13 +92,13 @@ class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
)
ctx.render(
modelCross[ctx.semiRandom(0)],
icon = { _, _, _ -> iconCross.icon!!}
icon = { _, _, _ -> iconCross }
)
ctx.render(
modelArm[ctx.semiRandom(1)],
cactusArmRotation[ctx.semiRandom(2) % 4],
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)]!!}
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)] }
)
}
}

View File

@@ -1,6 +1,6 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.CombinedContext
@@ -10,7 +10,7 @@ import mods.octarinecore.common.horizontalDirections
import mods.octarinecore.common.offset
import net.minecraft.tags.BlockTags
class RenderConnectedGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderConnectedGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.connectedGrass.enabled &&
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
@@ -27,7 +27,7 @@ class RenderConnectedGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage
}
}
class RenderConnectedGrassLog : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderConnectedGrassLog : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&

View File

@@ -1,34 +1,27 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.allDirOffsets
import mods.octarinecore.common.allDirections
import mods.octarinecore.common.offset
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
class RenderCoral : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderCoral : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val coralIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_coral_$idx") }
val crustIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_crust_$idx") }
val coralIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") }
val crustIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") }
val coralModels = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.coral.size).move(0.5 to UP)
@@ -44,11 +37,6 @@ class RenderCoral : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
}
}
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${coralIcons.num} coral textures")
Client.log(DEBUG, "Registered ${crustIcons.num} coral crust textures")
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.coral.enabled &&
(ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) &&
@@ -67,7 +55,7 @@ class RenderCoral : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
ctx.render(
coralModels[variation++],
rotationFromUp[idx],
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!}
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation] else coralIcons[variation + (qi and 1)] }
)
}
}

View File

@@ -1,10 +1,13 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.texture.GeneratedGrass
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.Model
@@ -18,10 +21,9 @@ import mods.octarinecore.common.allDirections
import mods.octarinecore.random
import net.minecraft.tags.BlockTags
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
class RenderGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
@@ -36,18 +38,13 @@ class RenderGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
val noise = simplexNoise()
val normalIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_grass_long_$idx") }
val snowedIcons = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_grass_snowed_$idx") }
val normalGenIcon = iconStatic { Client.genGrass.register(texture = "minecraft:blocks/tallgrass", isSnowed = false) }
val snowedGenIcon = iconStatic { Client.genGrass.register(texture = "minecraft:blocks/tallgrass", isSnowed = true) }
val normalIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") }
val snowedIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_snowed_$idx") }
val normalGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tallgrass", isSnowed = false).register(BetterFoliage.asyncPack) }
val snowedGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tallgrass", isSnowed = true).register(BetterFoliage.asyncPack) }
val grassModels = modelSet(64) { idx -> grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${normalIcons.num} grass textures")
Client.log(DEBUG, "Registered ${snowedIcons.num} snowed grass textures")
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
@@ -97,7 +94,7 @@ class RenderGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
ctx.render(
grassModels[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! },
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen else iconset[rand[qi and 1]] },
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
)
}

View File

@@ -1,9 +1,10 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.CombinedContext
@@ -17,11 +18,10 @@ import mods.octarinecore.common.allDirections
import mods.octarinecore.common.vec
import mods.octarinecore.random
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import java.lang.Math.cos
import java.lang.Math.sin
class RenderLeaves : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderLeaves : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val leavesModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
@@ -30,7 +30,7 @@ class RenderLeaves : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
.scale(Config.leaves.size)
.toCross(UP).addAll()
}
val snowedIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_leaves_snowed_$idx") }
val snowedIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") }
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0

View File

@@ -1,20 +1,21 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
import mods.octarinecore.common.Int3
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderLilypad : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
@@ -27,15 +28,10 @@ class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val rootIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_lilypad_roots_$idx") }
val flowerIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_lilypad_flower_$idx") }
val rootIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") }
val flowerIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") }
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${rootIcon.num} lilypad root textures")
Client.log(DEBUG, "Registered ${flowerIcon.num} lilypad flower textures")
}
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
BlockConfig.lilypad.matchesClass(ctx.state.block)
@@ -49,7 +45,7 @@ class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus
rootModel.model,
translation = ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! }
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]] }
)
}
@@ -57,7 +53,7 @@ class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus
flowerModel.model,
translation = ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]]!! }
icon = { _, _, _ -> flowerIcon[rand[0]] }
)
}
}

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
@@ -20,7 +21,7 @@ import net.minecraft.util.Direction.Axis
import org.apache.logging.log4j.Level
import java.util.concurrent.CompletableFuture
class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override val renderOnCutout: Boolean get() = false
@@ -57,7 +58,7 @@ object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
val axis = getAxis(state)
logger.log(Level.DEBUG, "$logName: axis $axis")
val spriteList = textures.map { atlas.sprite(Identifier(it)) }
return atlas.afterStitch {
return atlas.mapAfter {
SimpleColumnInfo(
axis,
spriteList[0].get(),
@@ -80,6 +81,6 @@ object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
fun init() {
LogRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}

View File

@@ -1,26 +1,23 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.noPost
import mods.octarinecore.common.Double3
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
class RenderMycelium : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderMycelium : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val myceliumIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${myceliumIcon.num} mycelium textures")
}
override fun isEligible(ctx: CombinedContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return BlockConfig.mycelium.matchesClass(ctx.state.block)
@@ -38,7 +35,7 @@ class RenderMycelium : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBu
ctx.render(
myceliumModel[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]] },
postProcess = if (isSnowed) whitewash else noPost
)
}

View File

@@ -1,27 +1,21 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.random
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.Axis
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.*
class RenderNetherrack : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderNetherrack : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val netherrackIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
@@ -31,10 +25,6 @@ class RenderNetherrack : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.mod
}
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${netherrackIcon.num} netherrack textures")
}
override fun isEligible(ctx: CombinedContext): Boolean {
if (!Config.enabled || !Config.netherrack.enabled) return false
return BlockConfig.netherrack.matchesClass(ctx.state.block)
@@ -48,7 +38,7 @@ class RenderNetherrack : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.mod
val rand = ctx.semiRandomArray(2)
ctx.render(
netherrackModel[rand[0]],
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! }
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]] }
)
}
}

View File

@@ -1,23 +1,28 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
import mods.octarinecore.client.resource.CenteredSprite
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.tags.BlockTags
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
class RenderReeds : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderReeds : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val reedIcons = iconSet { idx -> Client.genReeds.registerResource(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_reed_$idx")) }
val reedIcons = spriteSetTransformed(
check = { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx")},
register = { CenteredSprite(it).register(BetterFoliage.asyncPack) }
)
val reedModels = modelSet(64) { modelIdx ->
val height = random(Config.reed.heightMin, Config.reed.heightMax)
val waterline = 0.875f
@@ -36,10 +41,6 @@ class RenderReeds : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
}
}
override fun afterPreStitch() {
Client.log(DEBUG, "Registered ${reedIcons.num} reed textures")
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.reed.enabled &&
ctx.state(up2).material == Material.AIR &&
@@ -59,7 +60,7 @@ class RenderReeds : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus)
ctx.render(
reedModels[ctx.semiRandom(0)],
forceFlat = true,
icon = { _, _, _ -> reedIcons[iconVar]!! }
icon = { _, _, _ -> reedIcons[iconVar] }
)
}
}

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.client.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
@@ -97,7 +98,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : RenderDe
ColumnLayerData.SkipRender -> return
ColumnLayerData.NormalRender -> return ctx.render()
ColumnLayerData.ResolveError, null -> {
Client.logRenderError(ctx.state, ctx.pos)
BetterFoliage.logRenderError(ctx.state, ctx.pos)
return ctx.render()
}
}

View File

@@ -1,11 +1,9 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.Atlas
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES
import net.minecraft.resources.IResourceManager
import java.awt.image.BufferedImage
import java.io.InputStream
/**
* Generate Short Grass textures from [Blocks.tallgrass] block textures.
@@ -13,16 +11,13 @@ import java.io.InputStream
*
* @param[domain] Resource domain of generator
*/
class GrassGenerator(domain: String) : GeneratorBase<GrassGenerator.Key>(domain, TEXTURES) {
data class GeneratedGrass(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed)
override val locationMapper = Atlas.BLOCKS::unwrap
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun register(texture: String, isSnowed: Boolean) = registerResource(Key(ResourceLocation(texture), isSnowed))
override fun exists(key: Key) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key.texture))
override fun get(key: Key): InputStream? {
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key.texture)]?.loadImage() ?: return null
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics()
@@ -31,7 +26,7 @@ class GrassGenerator(domain: String) : GeneratorBase<GrassGenerator.Key>(domain,
val frames = baseTexture.height / size
// iterate all frames
for (frame in 0 .. frames - 1) {
for (frame in 0 until frames) {
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
val grassFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
@@ -45,14 +40,11 @@ class GrassGenerator(domain: String) : GeneratorBase<GrassGenerator.Key>(domain,
}
// blend with white if snowed
if (key.isSnowed) {
if (isSnowed) {
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
}
}
return result.asStream
return result.bytes
}
data class Key(val texture: ResourceLocation, val isSnowed: Boolean)
}
}

View File

@@ -1,47 +1,38 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.Atlas
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES
import java.awt.image.BufferedImage
import java.io.InputStream
/**
* Generate round leaf textures from leaf block textures.
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
*
* Generator parameter _type_: Leaf type (configurable by user). Different leaf types may have their own alpha mask.
* Different leaf types may have their own alpha mask.
*
* @param[domain] Resource domain of generator
*/
class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, TEXTURES) {
data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
override val locationMapper = Atlas.BLOCKS::unwrap
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun register(texture: ResourceLocation, leafType: String) = registerResource(Key(texture, leafType))
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
override fun exists(key: Key) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key.texture))
override fun get(key: Key): InputStream? {
// val handDrawnLoc = Atlas.BLOCKS.wrap(key.texture)
// resourceManager[handDrawnLoc]?.loadImage()?.let { return it.asStream }
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key.texture)]?.loadImage() ?: return null
val size = baseTexture.width
val frames = baseTexture.height / size
val maskTexture = (getLeafMask(key.leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = leafTexture.createGraphics()
// iterate all frames
for (frame in 0 .. frames - 1) {
for (frame in 0 until frames) {
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
@@ -55,7 +46,7 @@ class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, T
// overlay alpha mask
if (maskTexture != null) {
for (x in 0 .. size * 2 - 1) for (y in 0 .. size * 2 - 1) {
for (x in 0 until size * 2) for (y in 0 until size * 2) {
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
leafFrame[x, y] = (basePixel and maskPixel).toInt()
@@ -66,7 +57,7 @@ class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, T
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
}
return leafTexture.asStream
return leafTexture.bytes
}
/**
@@ -76,7 +67,7 @@ class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, T
* @param[maxSize] Preferred mask size.
*/
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
ResourceLocation(BetterFoliage.MOD_ID, "textures/blocks/leafmask_${size}_${type}.png")
ResourceLocation(BetterFoliageMod.MOD_ID, "textures/blocks/leafmask_${size}_${type}.png")
}
/**
@@ -92,6 +83,4 @@ class LeafGenerator(domain: String) : GeneratorBase<LeafGenerator.Key>(domain, T
while(size > 2) { sizes.add(size); size /= 2 }
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
}
data class Key(val texture: ResourceLocation, val leafType: String)
}

View File

@@ -41,11 +41,11 @@ object AsyncGrassDiscovery : ConfigurableModelDiscovery<GrassInfo>() {
val textureName = textures[0]
val spriteF = atlas.sprite(Identifier(textureName))
logger.log(Level.DEBUG, "$logName: texture $textureName")
return atlas.afterStitch {
return atlas.mapAfter {
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 hsb = HSB.fromColor(sprite.averageColor)
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")
@@ -60,6 +60,6 @@ object AsyncGrassDiscovery : ConfigurableModelDiscovery<GrassInfo>() {
fun init() {
GrassRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}

View File

@@ -1,42 +1,57 @@
package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.resource.Sprite
import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart
import mods.octarinecore.client.resource.Atlas
import mods.octarinecore.common.sinkAsync
import net.minecraft.client.particle.ParticleManager
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.concurrent.CompletableFuture
object LeafParticleRegistry {
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
}
object LeafParticleRegistry : AsyncSpriteProvider<ParticleManager> {
val targetAtlas = Atlas.PARTICLES
val typeMappings = TextureMatcher()
val particles = hashMapOf<String, IconSet>()
val particles = hashMapOf<String, SpriteSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
init { BetterFoliage.modBus.register(this) }
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (!targetAtlas.matches(event)) return
override fun setup(manager: IResourceManager, particleF: CompletableFuture<ParticleManager>, atlasFuture: AtlasFuture): StitchPhases {
particles.clear()
typeMappings.loadMappings(ResourceLocation(BetterFoliage.MOD_ID, "leaf_texture_mappings.cfg"))
val futures = mutableMapOf<String, List<CompletableFuture<Sprite>>>()
val allTypes = (typeMappings.mappings.map { it.type } + "default").distinct()
allTypes.forEach { leafType ->
val particleSet = IconSet(Atlas.PARTICLES) {
idx -> ResourceLocation(BetterFoliage.MOD_ID, "falling_leaf_${leafType}_$idx")
}.apply { onPreStitch(event) }
if (leafType == "default" || particleSet.num > 0) particles[leafType] = particleSet
}
return StitchPhases(
discovery = particleF.sinkAsync {
typeMappings.loadMappings(Identifier(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
val ids = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
val wids = ids.map { Atlas.PARTICLES.wrap(it) }
futures[leafType] = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
.filter { manager.hasResource(Atlas.PARTICLES.wrap(it)) }
.map { atlasFuture.sprite(it) }
}
},
cleanup = atlasFuture.runAfter {
futures.forEach { leafType, spriteFutures ->
val sprites = spriteFutures.filter { !it.isCompletedExceptionally }.map { it.get() }
if (sprites.isNotEmpty()) particles[leafType] = FixedSpriteSet(sprites)
}
if (particles["default"] == null) particles["default"] = FixedSpriteSet(listOf(atlasFuture.missing.get()!!))
}
)
}
@SubscribeEvent
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (!targetAtlas.matches(event)) return
particles.forEach { (_, particleSet) -> particleSet.onPostStitch(event.map) }
fun init() {
BetterFoliage.particleSprites.providers.add(this)
}
}

View File

@@ -1,7 +1,6 @@
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
@@ -26,7 +25,7 @@ class LeafInfo(
val averageColor: Int = roundLeafTexture.averageColor
) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: IconSet get() = LeafParticleRegistry[leafType]
val particleTextures: SpriteSet get() = LeafParticleRegistry[leafType]
}
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
@@ -40,18 +39,18 @@ object AsyncLeafDiscovery : ConfigurableModelDiscovery<LeafInfo>() {
fun init() {
LeafRegistry.registries.add(this)
AsyncSpriteProviderManager.providers.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}
fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: AtlasFuture): CompletableFuture<LeafInfo> {
val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default"
val generated = Client.genLeaves.register(sprite, leafType)
val generated = GeneratedLeaf(sprite, leafType).register(BetterFoliage.asyncPack)
val roundLeaf = atlas.sprite(generated)
log(" leaf texture $sprite")
log(" particle $leafType")
return atlas.afterStitch {
return atlas.mapAfter {
LeafInfo(roundLeaf.get(), leafType)
}
}

View File

@@ -1,6 +1,13 @@
@file:JvmName("Utils")
package mods.betterfoliage.client.texture
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.client.resource.Atlas
import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.loadImage
import net.minecraft.resources.IResourceManager
import java.io.IOException
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
@@ -8,4 +15,6 @@ fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
val a = (rgb1 shr 24) and 255
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt()
return result
}
}
fun IResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")

View File

@@ -2,58 +2,86 @@ 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 mods.octarinecore.common.map
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.MissingTextureSprite
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
/**
* Main entry point to atlas manipulation. Called from mixins that wrap [AtlasTexture.stitch] calls.
*
* 1. All registered providers receive an [AsyncSpriteProvider.setup] call. Providers can set up their
* processing chain at this point, but should not do anything yet except configuration and housekeeping.
* 2. The [CompletableFuture] of the stitch source finishes, starting the "discovery" phase. Providers
* may register sprites in the [AtlasFuture].
* 3. After all providers finish their discovery, the atlas is stitched.
* 4. The [AtlasFuture] finishes, starting the "cleanup" phase. All [AtlasFuture.runAfter] and
* [AtlasFuture.mapAfter] tasks are processed.
* 5. After all providers finish their cleanup, we return to the original code path.
*/
class AsnycSpriteProviderManager<SOURCE: Any>(val profilerSection: String) {
val providers = mutableListOf<AsyncSpriteProvider<SOURCE>>()
/**
* Needed in order to keep the actual [AtlasTexture.stitch] call in the original method, in case
* other modders want to modify it too.
*/
class StitchWrapper(val idList: Iterable<Identifier>, val onComplete: (AtlasTexture.SheetData)->Unit) {
fun complete(sheet: AtlasTexture.SheetData) = onComplete(sheet)
}
@Suppress("UNCHECKED_CAST")
fun prepare(sourceObj: Any, atlas: AtlasTexture, manager: IResourceManager, idList: Iterable<Identifier>, profiler: IProfiler): StitchWrapper {
profiler.startSection(profilerSection)
val source = CompletableFuture<SOURCE>()
val atlasFuture = AtlasFuture(idList)
val phases = providers.map { it.setup(manager, source, atlasFuture) }
source.complete(sourceObj as SOURCE)
phases.forEach { it.discovery.get() }
return StitchWrapper(atlasFuture.idSet) { sheet ->
atlasFuture.complete(sheet)
phases.forEach { it.cleanup.get() }
profiler.endSection()
}
}
}
/**
* Provides a way for [AsyncSpriteProvider]s to register sprites to receive [CompletableFuture]s.
* Tracks sprite ids that need to be stitched.
*/
class AtlasFuture(initial: Iterable<Identifier>) {
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>().apply { addAll(initial) })
protected val sheet = CompletableFuture<AtlasTexture.SheetData>()
protected val finished = CompletableFuture<Void>()
fun complete(sheetData: AtlasTexture.SheetData) {
sheet.complete(sheetData)
finished.complete(null)
}
fun sprite(id: String) = sprite(Identifier(id))
fun sprite(id: Identifier): CompletableFuture<Sprite> {
idSet.add(id)
return sheet.map { sheetData -> sheetData.sprites.find { it.name == id } ?: throw IllegalStateException("Atlas does not contain $id") }
}
val missing = sheet.map { sheetData -> sheetData.sprites.find { it.name == MissingTextureSprite.getLocation() } }
fun <T> mapAfter(supplier: ()->T): CompletableFuture<T> = finished.map{ supplier() }
fun runAfter(action: ()->Unit): CompletableFuture<Void> = finished.thenRun(action)
}
class StitchPhases(
val discovery: CompletableFuture<Void>,
val cleanup: CompletableFuture<Void>
)
interface AsyncSpriteProvider {
fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases
interface AsyncSpriteProvider<SOURCE: Any> {
fun setup(manager: IResourceManager, source: CompletableFuture<SOURCE>, atlas: 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

@@ -1,23 +1,17 @@
package mods.octarinecore.client.resource
import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType
import mods.betterfoliage.client.resource.Identifier
import mods.betterfoliage.client.texture.loadSprite
import net.minecraft.resources.IResourceManager
import java.awt.image.BufferedImage
import java.io.InputStream
import java.lang.Math.*
import java.lang.Math.max
class CenteringTextureGenerator(
domain: String,
val aspectWidth: Int,
val aspectHeight: Int
) : GeneratorBase<ResourceLocation>(domain, VanillaResourceType.TEXTURES) {
data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
override val locationMapper = Atlas.BLOCKS::unwrap
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
override fun exists(key: ResourceLocation) = resourceManager.hasResource(Atlas.BLOCKS.wrap(key))
override fun get(key: ResourceLocation): InputStream? {
val baseTexture = resourceManager[Atlas.BLOCKS.wrap(key)]?.loadImage() ?: return null
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth
@@ -28,7 +22,7 @@ class CenteringTextureGenerator(
val graphics = resultTexture.createGraphics()
// iterate all frames
for (frame in 0 .. frames - 1) {
for (frame in 0 until frames) {
val baseFrame = baseTexture.getSubimage(0, size * frame, frameWidth, frameHeight)
val resultFrame = BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR)
@@ -38,6 +32,6 @@ class CenteringTextureGenerator(
graphics.drawImage(resultFrame, 0, size * frame, null)
}
return resultTexture.asStream
return resultTexture.bytes
}
}

View File

@@ -5,17 +5,18 @@ 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 mods.octarinecore.common.sinkAsync
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.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
@@ -33,6 +34,9 @@ abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] }
}
/**
* Information about a single BlockState and all the IUnbakedModel it could render as.
*/
class ModelDiscoveryContext(
bakery: ModelBakery,
val state: BlockState,
@@ -48,7 +52,7 @@ class ModelDiscoveryContext(
}
}
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider, ModelRenderRegistry<T> {
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<T> {
var modelData: Map<BlockState, T> = emptyMap()
protected set
@@ -57,22 +61,22 @@ abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider, ModelRenderRe
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>?
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
val modelDataTemp = mutableMapOf<BlockState, CompletableFuture<T>>()
return StitchPhases(
discovery = bakeryFuture.thenRunAsync { bakery ->
discovery = bakeryF.sinkAsync { bakery ->
var errors = 0
bakery.iterateModels { ctx ->
try {
processModel(ctx, atlasFuture)?.let { modelDataTemp[ctx.state] = it }
processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it }
} catch (e: Exception) {
errors++
}
}
log("${modelDataTemp.size} BlockStates discovered, $errors errors")
},
cleanup = atlasFuture.sheet.thenRunAsync { sheetData ->
cleanup = atlas.runAfter {
modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() }
val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size
log("${modelData.size} BlockStates loaded, $errors errors")
@@ -125,8 +129,4 @@ abstract class ConfigurableModelDiscovery<T> : ModelDiscovery<T>() {
return null
}
override fun setup(bakeryFuture: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
(matchClasses as? ConfigurableBlockMatcher)?.readDefaults()
return super.setup(bakeryFuture, atlasFuture)
}
}

View File

@@ -1,16 +1,21 @@
package mods.octarinecore.client.resource
import net.minecraft.client.Minecraft
import mods.betterfoliage.client.resource.Identifier
import mods.octarinecore.HasLogger
import mods.octarinecore.common.completedVoid
import mods.octarinecore.common.map
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.resources.ClientResourcePackInfo
import net.minecraft.resources.*
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
import net.minecraft.resources.data.IMetadataSectionSerializer
import net.minecraft.resources.data.PackMetadataSection
import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.StringTextComponent
import net.minecraftforge.resource.IResourceType
import net.minecraftforge.resource.ISelectiveResourceReloadListener
import java.io.InputStream
import org.apache.logging.log4j.Logger
import java.io.IOException
import java.lang.IllegalStateException
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.function.Predicate
import java.util.function.Supplier
@@ -20,149 +25,61 @@ import java.util.function.Supplier
* @param[name] Name of the resource pack
* @param[generators] List of resource generators
*/
class GeneratorPack(val packName: String, val packDescription: String, val packImage: String) : IResourcePack {
class GeneratedBlockTexturePack(val nameSpace: String, val packName: String, override val logger: Logger) : HasLogger, IResourcePack, AsyncSpriteProvider<ModelBakery> {
val generators = mutableListOf<GeneratorBase<*>>()
val packFinder = Finder(this)
override fun getName() = packName
override fun getResourceNamespaces(type: ResourcePackType) = if (type == CLIENT_RESOURCES) generators.map { it.namespace }.toSet() else emptySet()
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>): T? {
if (deserializer.sectionName != "pack") return null
return PackMetadataSection(StringTextComponent(packDescription), 4) as? T
}
override fun resourceExists(type: ResourcePackType, location: ResourceLocation?) =
location != null &&
type == CLIENT_RESOURCES &&
generators.find { it.namespace == location.namespace && it.resourceExists(location) } != null
override fun getResourceStream(type: ResourcePackType, location: ResourceLocation) =
if (location != null && type == CLIENT_RESOURCES)
generators.firstOrNull { it.namespace == location.namespace && it.resourceExists(location) }?.getInputStream(location)
else
null
override fun getAllResourceLocations(type: ResourcePackType, pathIn: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun getRootResourceStream(fileName: String) = fileName.let { if (it == "pack.png") packImage else it }.let { this::class.java.classLoader.getResourceAsStream(it) }
override fun getResourceNamespaces(type: ResourcePackType) = setOf(nameSpace)
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>) = null
override fun getRootResourceStream(id: String) = null
override fun getAllResourceLocations(type: ResourcePackType, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>()
override fun close() {}
class Finder(val pack: GeneratorPack) : IPackFinder {
override fun <T : ResourcePackInfo> addPackInfosToMap(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackInfo.IFactory<T>) {
val packInfo = ResourcePackInfo.createResourcePack(
pack.packName,
true,
Supplier { pack } as Supplier<IResourcePack>,
packInfoFactory,
ResourcePackInfo.Priority.BOTTOM
)
nameToPackMap[pack.packName] = packInfo!!
}
}
}
protected var manager: CompletableFuture<IResourceManager>? = null
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, Identifier>())
val resources = Collections.synchronizedMap(mutableMapOf<Identifier, CompletableFuture<ByteArray>>())
/**
* Abstract base class for resource generators
*
* @param[namespace] Resource namespace of generator
* @param[generatedType] IResourceType of generated resources
*/
abstract class GeneratorBase<T>(val namespace: String, val generatedType: IResourceType) : ISelectiveResourceReloadListener {
val keyToId = mutableMapOf<T, String>()
val idToKey = mutableMapOf<String, T>()
open val locationMapper: (ResourceLocation)->ResourceLocation = { it }
fun register(key: Any, func: (IResourceManager)->ByteArray): Identifier {
if (manager == null) throw IllegalStateException("Cannot register resources unless block textures are being reloaded")
identifiers[key]?.let { return it }
init { resourceManager.addReloadListener(this) }
val id = Identifier(nameSpace, UUID.randomUUID().toString())
val resource = manager!!.map { func(it) }
abstract fun get(key: T): InputStream?
abstract fun exists(key: T): Boolean
fun registerResource(key: T): ResourceLocation {
keyToId[key]?.let { return ResourceLocation(namespace, it) }
val id = UUID.randomUUID().toString()
keyToId[key] = id
idToKey[id] = key
return ResourceLocation(namespace, id)
identifiers[key] = id
resources[Atlas.BLOCKS.wrap(id)] = resource
log("generated resource $key -> $id")
return id
}
fun resourceExists(location: ResourceLocation?): Boolean {
val key = location?.let { locationMapper(it) }?.path?.let { idToKey[it] } ?: return false
return exists(key)
}
override fun getResourceStream(type: ResourcePackType, id: Identifier) =
if (type != CLIENT_RESOURCES) null else
try { resources[id]!!.get().inputStream() }
catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present
fun getInputStream(location: ResourceLocation?): InputStream? {
val key = location?.let { locationMapper(it) }?.path?.let { idToKey[it] } ?: return null
return get(key)
}
override fun resourceExists(type: ResourcePackType, id: Identifier) =
type == CLIENT_RESOURCES && resources.containsKey(id)
open fun onReload(resourceManager: IResourceManager) {
keyToId.clear()
idToKey.clear()
}
override fun onResourceManagerReload(resourceManager: IResourceManager, resourcePredicate: Predicate<IResourceType>) {
if (resourcePredicate.test(generatedType)) onReload(resourceManager)
}
}
/**
* Collection of named [String]-valued key-value pairs, with an extra unnamed (keyless) value.
* Meant to be encoded as a pipe-delimited list, and used as a [ResourceLocation] path
* to parametrized generated resources.
*
* @param[params] key-value pairs
* @param[value] keyless extra value
*/
/*
class ParameterList(val params: Map<String, String>, val value: String?) {
override fun toString() =
params.entries
.sortedBy { it.key }
.fold("") { result, entry -> result + "|${entry.key}=${entry.value}"} +
(value?.let { "|$it" } ?: "")
/** Return the value of the given parameter. */
operator fun get(key: String) = params[key]
/** Check if the given parameter exists in this list. */
operator fun contains(key: String) = key in params
/** Return a new [ParameterList] with the given key-value pair appended to it. */
operator fun plus(pair: Pair<String, String>) = ParameterList(params + pair, this.value)
companion object {
/**
* Recreate the parameter list from the encoded string, i.e. the opposite of [toString].
*
* Everything before the first pipe character is dropped, so the decoding works even if
* something is prepended to the list (like _textures/blocks/_)
*/
fun fromString(input: String): ParameterList {
val params = hashMapOf<String, String>()
var value: String? = null
val slices = input.dropWhile { it != '|'}.split('|')
slices.forEach {
if (it.contains('=')) {
val keyValue = it.split('=')
if (keyValue.size == 2) params.put(keyValue[0], keyValue[1])
} else value = it
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
this.manager = CompletableFuture.completedFuture(manager)
return StitchPhases(
completedVoid(),
atlas.runAfter {
this.manager = null
identifiers.clear()
resources.clear()
}
return ParameterList(params, value)
}
)
}
}
abstract class ParameterBasedGenerator(domain: String) : GeneratorBase(domain) {
abstract fun resourceExists(params: ParameterList): Boolean
abstract fun getInputStream(params: ParameterList): InputStream?
override fun resourceExists(location: ResourceLocation?) =
resourceExists(ParameterList.fromString(location?.path ?: ""))
override fun getInputStream(location: ResourceLocation?) =
getInputStream(ParameterList.fromString(location?.path ?: ""))
}
*/
val finder = object : IPackFinder {
val packInfo = ClientResourcePackInfo(
packName, true, Supplier { this@GeneratedBlockTexturePack },
StringTextComponent(packName),
StringTextComponent("Generated block textures resource pack"),
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true
)
override fun <T : ResourcePackInfo> addPackInfosToMap(nameToPackMap: MutableMap<String, T>, packInfoFactory: ResourcePackInfo.IFactory<T>) {
(nameToPackMap as MutableMap<String, ResourcePackInfo>).put(packName, packInfo)
}
}
}

View File

@@ -1,13 +1,16 @@
package mods.octarinecore.client.resource
import mods.betterfoliage.BetterFoliage
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.common.completedVoid
import mods.octarinecore.common.sink
import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.resources.IResourceManager
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.IWorld
@@ -18,6 +21,9 @@ import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
enum class Atlas(val basePath: String) {
BLOCKS("textures"),
@@ -31,10 +37,6 @@ enum class Atlas(val basePath: String) {
// ============================
// Resource types
// ============================
interface IStitchListener {
fun onPreStitch(event: TextureStitchEvent.Pre)
fun onPostStitch(atlas: AtlasTexture)
}
interface IConfigChangeListener { fun onConfigChange() }
interface IWorldLoadListener { fun onWorldLoad(world: IWorld) }
@@ -52,9 +54,6 @@ open class ResourceHandler(
) {
val resources = mutableListOf<Any>()
open fun afterPreStitch() {}
open fun afterPostStitch() {}
// ============================
// Self-registration
// ============================
@@ -63,10 +62,11 @@ open class ResourceHandler(
// ============================
// Resource declarations
// ============================
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 sprite(id: Identifier) = sprite { id }
fun sprite(idFunc: ()->Identifier) = AsyncSpriteDelegate(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
fun spriteSet(idFunc: (Int)->Identifier) = AsyncSpriteSet(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
fun spriteSetTransformed(check: (Int)->Identifier, register: (Identifier)->Identifier) =
AsyncSpriteSet(check, register).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.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) }
@@ -75,20 +75,6 @@ open class ResourceHandler(
// ============================
// Event registration
// ============================
@SubscribeEvent
fun onPreStitch(event: TextureStitchEvent.Pre) {
if (!targetAtlas.matches(event)) return
resources.forEach { (it as? IStitchListener)?.onPreStitch(event) }
afterPreStitch()
}
@SubscribeEvent
fun onPostStitch(event: TextureStitchEvent.Post) {
if (!targetAtlas.matches(event)) return
resources.forEach { (it as? IStitchListener)?.onPostStitch(event.map) }
afterPostStitch()
}
@SubscribeEvent
fun handleModConfigChange(event: ModConfig.ModConfigEvent) {
resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
@@ -102,16 +88,49 @@ open class ResourceHandler(
// ============================
// Resource container classes
// ============================
class IconHolder(val location: ()-> Identifier) : IStitchListener {
var iconRes: Identifier? = null
var icon: Sprite? = null
override fun onPreStitch(event: TextureStitchEvent.Pre) {
iconRes = location()
event.addSprite(iconRes)
class AsyncSpriteDelegate(val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, AsyncSpriteProvider<Any> {
protected lateinit var value: Sprite
override fun getValue(thisRef: Any, property: KProperty<*>) = value
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
sourceF.thenRun {
val sprite = atlas.sprite(idFunc())
atlas.runAfter {
sprite.handle { sprite, error -> value = sprite ?: atlas.missing.get()!! }
}
}
return StitchPhases(completedVoid(), completedVoid())
}
override fun onPostStitch(atlas: AtlasTexture) {
icon = atlas[iconRes!!]
}
interface SpriteSet {
val num: Int
operator fun get(idx: Int): Sprite
}
class AsyncSpriteSet(val idFunc: (Int)->Identifier, val transform: (Identifier)->Identifier = { it }) : AsyncSpriteProvider<Any> {
var num = 0
protected set
protected var sprites: List<Sprite> = emptyList()
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
var list: List<CompletableFuture<Sprite>> = emptyList()
return StitchPhases(
discovery = sourceF.sink {
list = (0 until 16).map { idFunc(it) }
.filter { manager.hasResource( Atlas.BLOCKS.wrap(it)) }
.map { transform(it) }
.map { atlas.sprite(it) }
},
cleanup = atlas.runAfter {
sprites = list.filter { !it.isCompletedExceptionally }.map { it.get() }
if (sprites.isEmpty()) sprites = listOf(atlas.missing.get()!!)
num = sprites.size
}
)
}
operator fun get(idx: Int) = sprites[idx % num]
}
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
@@ -119,27 +138,6 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
override fun onConfigChange() { model = Model().apply(init) }
}
class IconSet(val targetAtlas: Atlas, val location: (Int)-> Identifier) : IStitchListener {
val resources = arrayOfNulls<Identifier>(16)
val icons = arrayOfNulls<Sprite>(16)
var num = 0
override fun onPreStitch(event: TextureStitchEvent.Pre) {
num = 0
(0..15).forEach { idx ->
icons[idx] = null
val loc = location(idx)
if (resourceManager[targetAtlas.wrap(loc)] != null) resources[num++] = loc.apply { event.addSprite(this) }
}
}
override fun onPostStitch(atlas: AtlasTexture) {
(0 until num).forEach { idx -> icons[idx] = atlas[resources[idx]!!] }
}
operator fun get(idx: Int) = if (num == 0) null else icons[idx % num]
}
class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener {
val models = Array(num) { Model() }
override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } }

View File

@@ -1,89 +0,0 @@
package mods.octarinecore.client.resource
import mods.octarinecore.client.resource.ResourceType.*
import net.minecraft.resources.IResource
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
import java.io.InputStream
/** Type of generated texture resource */
enum class ResourceType {
COLOR, // regular diffuse map
METADATA, // texture metadata
NORMAL, // ShadersMod normal map
SPECULAR // ShadersMod specular map
}
/**
* Generator returning textures based on a single other texture. This texture is located with the
* _dom_ and _path_ parameters of a [ParameterList].
*
* @param[domain] Resource domain of generator
*/
/*
abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain) {
/**
* Obtain a [ResourceLocation] to a generated texture
*
* @param[iconName] the name of the [TextureAtlasSprite] (not the full location) backing the generated texture
* @param[extraParams] additional parameters of the generated texture
*/
fun generatedResource(iconName: String, vararg extraParams: Pair<String, Any>) = ResourceLocation(
namespace,
textureLocation(iconName).let {
ParameterList(
mapOf("dom" to it.namespace, "path" to it.path) +
extraParams.map { Pair(it.first, it.second.toString()) },
"generate"
).toString()
}
)
/** Get the type and location of the texture resource encoded by the given [ParameterList]. */
fun targetResource(params: ParameterList): Pair<ResourceType, ResourceLocation>? {
val baseTexture =
if (listOf("dom", "path").all { it in params }) ResourceLocation(params["dom"]!!, params["path"]!!)
else return null
return when(params.value?.toLowerCase()) {
"generate.png" -> COLOR to baseTexture + ".png"
"generate.png.mcmeta" -> METADATA to baseTexture + ".png.mcmeta"
"generate_n.png" -> NORMAL to baseTexture + "_n.png"
"generate_s.png" -> SPECULAR to baseTexture + "_s.png"
else -> null
}
}
override fun resourceExists(params: ParameterList) =
targetResource(params)?.second?.let { resourceManager[it] != null } ?: false
override fun getInputStream(params: ParameterList): InputStream? {
val target = targetResource(params)
return when(target?.first) {
null -> null
METADATA -> resourceManager[target!!.second]?.inputStream
else -> generate(params)?.asStream
}
}
/**
* Generate image data from the parameter list.
*/
abstract fun generate(params: ParameterList): BufferedImage?
/**
* Get a texture resource when multiple sizes may exist.
*
* @param[maxSize] Maximum size to consider. This value is progressively halved when searching for smaller versions.
* @param[maskPath] Location of the texture of the given size
*
*/
fun getMultisizeTexture(maxSize: Int, maskPath: (Int)->ResourceLocation): IResource? {
var size = maxSize
val sizes = mutableListOf<Int>()
while(size > 2) { sizes.add(size); size /= 2 }
return sizes.map { resourceManager[maskPath(it)] }.filterNotNull().firstOrNull()
}
}
*/

View File

@@ -59,7 +59,8 @@ operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, v
/** Get an [InputStream] to an image object in PNG format. */
val BufferedImage.asStream: InputStream get() =
ByteArrayInputStream(ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() })
val BufferedImage.bytes: ByteArray get() =
ByteArrayOutputStream().let { ImageIO.write(this, "PNG", it); it.toByteArray() }
/**
* Calculate the average color of a texture.
*

View File

@@ -0,0 +1,15 @@
package mods.octarinecore.common
import net.minecraft.client.Minecraft
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.function.Consumer
import java.util.function.Function
fun completedVoid() = CompletableFuture.completedFuture<Void>(null)!!
fun <T, U> CompletionStage<T>.map(func: (T)->U) = thenApply(Function(func)).toCompletableFuture()!!
fun <T, U> CompletionStage<T>.mapAsync(func: (T)->U) = thenApplyAsync(Function(func), Minecraft.getInstance()).toCompletableFuture()!!
fun <T> CompletionStage<T>.sink(func: (T)->Unit) = thenAccept(Consumer(func)).toCompletableFuture()!!
fun <T> CompletionStage<T>.sinkAsync(func: (T)->Unit) = thenAcceptAsync(Consumer(func), Minecraft.getInstance()).toCompletableFuture()!!

View File

@@ -11,7 +11,8 @@
"BlockStateMixin",
"ChunkRenderMixin",
"ClientWorldMixin",
"ModelBakeryMixin"
"ModelBakeryMixin",
"ParticleManagerMixin"
],
"server": [
],