Compare commits

...

20 Commits

Author SHA1 Message Date
octarine-noise
178a014a6b bump version 2021-07-30 15:57:56 +02:00
octarine-noise
0b802663dc fix ignored snowed leaves config 2021-07-26 23:32:02 +02:00
octarine-noise
d2b9326ced mod support: BOP, BYG, Environmental, Desolation 2021-07-26 23:29:58 +02:00
octarine-noise
31eddf682d get leaf particle type from rules 2021-07-26 22:43:42 +02:00
octarine-noise
e689a44687 simpler parser, allow for negated conditions 2021-07-26 22:42:54 +02:00
octarine-noise
b4824b77ae fix leaf block & leaf particle colors 2021-07-26 14:52:26 +02:00
octarine-noise
4c08354d74 Leaf shader wind integration 2021-07-26 12:15:38 +02:00
octarine-noise
0518b01b50 Optifine compatibility for mixin 2021-07-26 12:14:57 +02:00
octarine-noise
563a67f213 fix wrong package name for Refs 2021-07-26 12:13:06 +02:00
octarine-noise
4637e282ce cleaner rule matching implementation based on Either 2021-07-15 00:53:24 +02:00
octarine-noise
8ef84718b5 get tint index directly from model 2021-07-13 20:06:45 +02:00
octarine-noise
1e69081a2f calculate average texture color without atlas 2021-07-13 20:05:15 +02:00
octarine-noise
c418e001b0 change overlay layer to use Maps as storage 2021-07-13 17:07:06 +02:00
octarine-noise
4ce7eda78b fix snowed short grass color 2021-07-13 17:05:57 +02:00
octarine-noise
145e07363c support for Nylium blocks 2021-07-12 21:47:06 +02:00
octarine-noise
54e245bcd4 add rules for crops 2021-07-12 20:49:45 +02:00
octarine-noise
d2485cd323 remove old block config 2021-07-12 20:27:55 +02:00
octarine-noise
257593d231 use new config parser
+BakeWrapperManager no longer singleton
2021-07-12 20:21:26 +02:00
octarine-noise
c8e79c22ff [WIP] Config parser 2021-07-12 19:13:42 +02:00
octarine-noise
29ab544269 Multi-layer rendering support 2021-07-06 00:06:11 +02:00
68 changed files with 1641 additions and 547 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ build/
classes/
temp/
logs
src/main/javacc/mods

View File

@@ -2,6 +2,7 @@ plugins {
kotlin("jvm").version("1.4.20")
id("net.minecraftforge.gradle").version("4.1.12")
id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
id("com.intershop.gradle.javacc").version("4.0.0")
}
repositories {
@@ -20,6 +21,7 @@ dependencies {
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
sourceSets {
get("main").ext["refMap"] = "betterfoliage.refmap.json"
get("main").java.srcDir("src/main/javacc/")
}
minecraft {
@@ -36,6 +38,17 @@ minecraft {
}
}
javacc {
configs {
create("blockconfig") {
staticParam = "false"
inputFile = file("src/main/javacc/BlockConfig.jj")
outputDir = file("src/main/javacc/")
packageName = "mods.betterfoliage.config.match.parser"
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

View File

@@ -4,7 +4,7 @@ org.gradle.daemon=false
group = com.github.octarine-noise
jarName = BetterFoliage-Forge
version = 2.6.5
version = 2.7.0
mcVersion = 1.16.5
forgeVersion = 36.1.17

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliage;
import mods.betterfoliage.BetterFoliageMod;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent;
@@ -38,6 +39,6 @@ abstract public class MixinModelBakery {
IModelTransform transform,
ResourceLocation locationIn
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn);
return BetterFoliage.INSTANCE.getModelManager().onBake(unbaked, bakery, spriteGetter, transform, locationIn);
}
}

View File

@@ -0,0 +1,57 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinOptifineChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockStateMcp = "Lnet/optifine/override/ChunkCacheOF;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
private static final String getBlockStateSrg = "Lnet/optifine/override/ChunkCacheOF;func_180495_p(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, locals = LocalCapture.CAPTURE_FAILHARD, require = 1, at = {
@At(value = "INVOKE", target = getBlockStateMcp),
@At(value = "INVOKE", target = getBlockStateSrg),
})
void onStartBlockRender(
float xIn, float yIn, float zIn,
ChunkRenderDispatcher.CompiledChunk compiledChunkIn, RegionRenderCacheBuilder builderIn,
CallbackInfoReturnable<Set> cir,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set, MatrixStack matrixstack,
@Coerce IBlockDisplayReader chunkrendercache, RenderType[] singleLayer,
boolean shaders, boolean shadersMidBlock, Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var18,
@Coerce BlockPos blockpos2
) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, locals = LocalCapture.PRINT, require = 1, at = {
// @At(value = "INVOKE", target = getBlockStateMcp),
// @At(value = "INVOKE", target = getBlockStateSrg),
// })
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable<BlockState> ci) {
// }
}

View File

@@ -0,0 +1,46 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxBase;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.chunk.ChunkRenderCache;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.util.math.BlockPos;
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.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
@Mixin(targets = "net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask")
public class MixinVanillaChunkRendererDispatcher {
private static final String compile = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$ChunkRender$RebuildTask;compile(FFFLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;Lnet/minecraft/client/renderer/RegionRenderCacheBuilder;)Ljava/util/Set;";
private static final String getBlockState = "Lnet/minecraft/client/renderer/chunk/ChunkRenderCache;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;";
@Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.CAPTURE_FAILHARD)
void onStartBlockRender(
float p_228940_1_, float p_228940_2_, float p_228940_3_,
ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
CallbackInfoReturnable ci,
int i, BlockPos blockpos, BlockPos blockpos1, VisGraph visgraph, Set set,
ChunkRenderCache chunkrendercache, MatrixStack matrixstack,
Random random,
BlockRendererDispatcher blockrendererdispatcher, Iterator var15,
BlockPos blockpos2) {
RenderCtxBase.reset(chunkrendercache, blockrendererdispatcher, blockpos2, random);
}
// @Inject(method = compile, at = @At(value = "INVOKE", target = getBlockState), locals = LocalCapture.PRINT)
// void printLocals(
// float p_228940_1_, float p_228940_2_, float p_228940_3_,
// ChunkRenderDispatcher.CompiledChunk p_228940_4_, RegionRenderCacheBuilder p_228940_5_,
// CallbackInfoReturnable ci) {
// }
}

View File

@@ -0,0 +1,101 @@
PARSER_BEGIN(BlockConfigParser)
package mods.betterfoliage.config.match.parser;
import java.util.List;
import java.util.LinkedList;
import mods.betterfoliage.config.match.*;
public class BlockConfigParser {
public String configFile;
ConfigSource getSource(Token t) {
return new ConfigSource(configFile, t.beginLine, t.beginColumn);
}
}
PARSER_END(BlockConfigParser)
// Whitespace definition
SKIP : { " " | "\n" | "\t" | "\r" }
// Single-line comment
SPECIAL_TOKEN : { <lineComment: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")> }
// Lexical state for string literal in quotes
SPECIAL_TOKEN : { < quoteStart : "\"" > : withinQuotes }
<withinQuotes> SPECIAL_TOKEN : { < quoteEnd : "\"" > : DEFAULT }
<withinQuotes> TOKEN : { < stringLiteral : (["a"-"z"] | ["0"-"9"] | "/" | "." | "_" | "-" | ":" )* > }
// Symbol tokens
TOKEN : {
< parenStart : "(" > |
< parenEnd : ")" > |
< dot : "." > |
< comma : "," > |
< exclamation : "!" >
}
List<Node.MatchAll> matchFile() : {
Token t; Node n; List<Node.MatchAll> rules = new LinkedList<Node.MatchAll>();
} {
(
t = "match"
{ List<Node> nodes = new LinkedList<Node>(); }
(n = match() { nodes.add(n); })*
"end"
{ rules.add(new Node.MatchAll(getSource(t), nodes)); }
)*
{ return rules; }
}
Node match() : {
Token t; Token t2; MatchMethod mm; List<Node.Value> values; Node.Value v; Node n;
} {
<exclamation> n = match() { return new Node.Negate(n); }
|
t = "block.class." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_CLASS, mm, getSource(t), values); }
|
t = "block.name." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.BLOCK_NAME, mm, getSource(t), values); }
|
t = "model." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
{ return new Node.MatchValueList(Node.MatchSource.MODEL_LOCATION, mm, getSource(t), values); }
|
t = "isParam" <parenStart> t2 = <stringLiteral> <comma> values = matchValueList() <parenEnd>
{ return new Node.MatchParam(t2.image, values, getSource(t)); }
|
t = "setParam" <parenStart> t2 = <stringLiteral> <comma> v = matchValue() <parenEnd>
{ return new Node.SetParam(t2.image, v, getSource(t)); }
}
MatchMethod matchMethod() : {} {
"matches" { return MatchMethod.EXACT_MATCH; } |
"extends" { return MatchMethod.EXTENDS; } |
"contains" { return MatchMethod.CONTAINS; }
}
List<Node.Value> matchValueList() : {
List<Node.Value> values = new LinkedList<Node.Value>();
Node.Value v;
} {
v = matchValue() { values.add(v); }
(<comma> v = matchValue() { values.add(v); } )*
{ return values; }
}
Node.Value matchValue() : {
Token t;
} {
t = <stringLiteral>
{ return new Node.Value.Literal(getSource(t), t.image); }
|
"classOf" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.ClassOf(getSource(t), t.image); }
|
"model.texture" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Texture(getSource(t), t.image); }
|
"model.tint" <parenStart> t = <stringLiteral> <parenEnd>
{ return new Node.Value.Tint(getSource(t), t.image); }
}

View File

@@ -15,27 +15,24 @@ import mods.betterfoliage.render.block.vanilla.StandardLeafDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLeafModel
import mods.betterfoliage.render.block.vanilla.StandardLilypadDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLilypadModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumDiscovery
import mods.betterfoliage.render.block.vanilla.StandardMyceliumModel
import mods.betterfoliage.render.block.vanilla.StandardNetherrackDiscovery
import mods.betterfoliage.render.block.vanilla.StandardNetherrackModel
import mods.betterfoliage.render.block.vanilla.StandardRoundLogDiscovery
import mods.betterfoliage.render.block.vanilla.StandardRoundLogModel
import mods.betterfoliage.render.block.vanilla.StandardSandDiscovery
import mods.betterfoliage.render.block.vanilla.StandardSandModel
import mods.betterfoliage.render.lighting.AoSideHelper
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.LeafWindTracker
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
import mods.betterfoliage.resource.discovery.RuleBasedDiscovery
import mods.betterfoliage.resource.generated.GeneratedTexturePack
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.resources.IReloadableResourceManager
import net.minecraftforge.eventbus.api.SubscribeEvent
/**
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
@@ -48,9 +45,26 @@ object BetterFoliage {
/** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache()
val blockConfig = BlockConfig()
val standardModelSupport = RuleBasedDiscovery().apply {
discoverers["cactus"] = StandardCactusDiscovery
discoverers["dirt"] = StandardDirtDiscovery
discoverers["grass"] = StandardGrassDiscovery
discoverers["leaf"] = StandardLeafDiscovery
discoverers["lilypad"] = StandardLilypadDiscovery
discoverers["mycelium"] = StandardMyceliumDiscovery
discoverers["netherrack"] = StandardNetherrackDiscovery
discoverers["round-log"] = StandardRoundLogDiscovery
discoverers["sand"] = StandardSandDiscovery
}
val modelManager = BakeWrapperManager().apply {
discoverers.add(standardModelSupport)
}
fun init() {
// discoverers
BetterFoliageMod.bus.register(BakeWrapperManager)
BetterFoliageMod.bus.register(modelManager)
BetterFoliageMod.bus.register(LeafParticleRegistry)
resourceManager.registerReloadListener(LeafParticleRegistry)
@@ -58,22 +72,14 @@ object BetterFoliage {
listOf(
StandardLeafDiscovery,
StandardGrassDiscovery,
StandardDirtDiscovery,
StandardMyceliumDiscovery,
StandardSandDiscovery,
StandardLilypadDiscovery,
StandardCactusDiscovery,
StandardNetherrackDiscovery,
StandardRoundLogDiscovery
StandardRoundLogDiscovery,
).forEach {
BakeWrapperManager.discoverers.add(it)
}
// init singletons
val singletons = listOf(
AoSideHelper,
BlockConfig,
ChunkOverlayManager,
LeafWindTracker
)

View File

@@ -1,10 +1,9 @@
package mods.betterfoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.MainConfig
import mods.betterfoliage.util.tryDefault
import mods.betterfoliage.config.clothGuiRoot
import mods.betterfoliage.config.forgeSpecRoot
import mods.betterfoliage.util.tryDefault
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screen.Screen
import net.minecraft.util.ResourceLocation
@@ -72,7 +71,6 @@ object BetterFoliageMod {
}
Minecraft.getInstance().resourcePackRepository.addPackFinder(BetterFoliage.generatedPack.finder)
bus.register(BlockConfig)
BetterFoliage.init()
}
}

View File

@@ -1,4 +1,4 @@
package mods.octarinecore
package mods.betterfoliage
import mods.betterfoliage.util.ClassRef
import mods.betterfoliage.util.ClassRef.Companion.float
@@ -10,6 +10,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IUnbakedModel
@@ -21,7 +22,9 @@ import net.minecraft.world.IBlockDisplayReader
import net.minecraft.world.IBlockReader
import net.minecraftforge.client.model.pipeline.BlockInfo
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import net.minecraftforge.registries.IRegistryDelegate
import java.util.Random
import java.util.function.Predicate
typealias Sprite = TextureAtlasSprite
@@ -61,6 +64,10 @@ object ModelBakery : ClassRef<ModelBakery>("net.minecraft.client.renderer.model.
val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
}
object RenderTypeLookup : ClassRef<RenderTypeLookup>("net.minecraft.client.renderer.RenderTypeLookup") {
val blockRenderChecks = FieldRef(this, "blockRenderChecks", mapRefMutable<IRegistryDelegate<Block>, Predicate<RenderType>>())
}
// Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")
val BlockPosM = ClassRef<Any>("net.optifine.BlockPosM")

View File

@@ -22,6 +22,8 @@ interface BlockCtx {
val world: IBlockDisplayReader
val pos: BlockPos
val seed: Long get() = state.getSeed(pos)
fun offset(dir: Direction) = offset(dir.offset)
fun offset(offset: Int3): BlockCtx

View File

@@ -2,7 +2,7 @@ package mods.betterfoliage.chunk
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isInstance
import mods.octarinecore.ChunkCacheOF
import mods.betterfoliage.ChunkCacheOF
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos
@@ -27,92 +27,69 @@ val IBlockDisplayReader.dimType: DimensionType get() = when {
/**
* Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/
interface ChunkOverlayLayer<T> {
fun calculate(ctx: BlockCtx): T
fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
abstract class ChunkOverlayLayer<T> {
val dimData = IdentityHashMap<DimensionType, SparseChunkedMap<T>>()
abstract fun calculate(ctx: BlockCtx): T
abstract fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos)
operator fun get(ctx: BlockCtx): T {
return dimData
.getOrPut(ctx.world.dimType) { SparseChunkedMap() }
.getOrPut(ctx.pos) { calculate(ctx) }
}
fun remove(world: IBlockDisplayReader, pos: BlockPos) {
dimData[world.dimType]?.remove(pos)
}
}
/**
* Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data.
* Event forwarder for multiple layers of chunk overlay data.
*/
object ChunkOverlayManager {
var tempCounter = 0
init {
MinecraftForge.EVENT_BUS.register(this)
}
val chunkData = IdentityHashMap<DimensionType, MutableMap<ChunkPos, ChunkOverlayData>>()
init { MinecraftForge.EVENT_BUS.register(this) }
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* Get the overlay data for a given layer and position
*
* @param layer Overlay layer to query
* @param reader World to use if calculation of overlay value is necessary
* @param pos Block position
*/
fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
data.get(layer, ctx.pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(ctx)
data.set(layer, ctx.pos, newValue)
return newValue
}
}
/**
* Clear the overlay data for a given layer and position
*
* @param layer Overlay layer to clear
* @param pos Block position
*/
fun <T> clear(dimension: DimensionType, layer: ChunkOverlayLayer<T>, pos: BlockPos) {
chunkData[dimension]?.get(ChunkPos(pos))?.clear(layer, pos)
}
fun onBlockChange(world: ClientWorld, pos: BlockPos) {
if (chunkData[world.dimType]?.containsKey(ChunkPos(pos)) == true) {
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
}
@SubscribeEvent
fun handleLoadWorld(event: WorldEvent.Load) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType] = mutableMapOf()
layers.forEach { layer -> layer.onBlockUpdate(world, pos) }
}
@SubscribeEvent
fun handleUnloadWorld(event: WorldEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData.remove(world.dimType)
}
@SubscribeEvent
fun handleLoadChunk(event: ChunkEvent.Load) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.let { chunks ->
// check for existence first because Optifine fires a TON of these
if (event.chunk.pos !in chunks.keys) chunks[event.chunk.pos] = ChunkOverlayData(layers)
}
layers.forEach { layer -> layer.dimData.remove(world.dimType) }
}
@SubscribeEvent
fun handleUnloadChunk(event: ChunkEvent.Unload) = (event.world as? ClientWorld)?.let { world ->
chunkData[world.dimType]?.remove(event.chunk.pos)
layers.forEach { layer -> layer.dimData[world.dimType]?.removeChunk(event.chunk.pos) }
}
}
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val BlockPos.isValid: Boolean get() = y in validYRange
val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
interface DoubleMap<K1, K2, V> {
val map1: MutableMap<K1, MutableMap<K2, V>>
fun createMap2(): MutableMap<K2, V>
companion object {
val UNCALCULATED = object {}
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
val validYRange = 0 until 256
fun remove(key1: K1) {
map1.remove(key1)
}
fun remove(key1: K1, key2: K2) {
map1[key1]?.remove(key2)
}
fun contains(key1: K1) = map1.contains(key1)
fun getOrSet(key1: K1, key2: K2, factory: () -> V) =
(map1[key1] ?: createMap2().apply { map1[key1] = this }).let { subMap ->
subMap[key2] ?: factory().apply { subMap[key2] = this }
}
}
class SparseChunkedMap<V> {
val map = object : DoubleMap<ChunkPos, BlockPos, V> {
override val map1 = mutableMapOf<ChunkPos, MutableMap<BlockPos, V>>()
override fun createMap2() = mutableMapOf<BlockPos, V>()
}
fun getOrPut(pos: BlockPos, factory: () -> V) = map.getOrSet(ChunkPos(pos), pos, factory)
fun remove(pos: BlockPos) = map.remove(ChunkPos(pos), pos)
fun removeChunk(pos: ChunkPos) = map.map1.remove(pos)
}

View File

@@ -0,0 +1,40 @@
package mods.betterfoliage.config
import mods.betterfoliage.config.match.Node
import mods.betterfoliage.config.match.parser.BlockConfigParser
import mods.betterfoliage.config.match.parser.ParseException
import mods.betterfoliage.config.match.parser.TokenMgrError
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.stripStart
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.ERROR
class BlockConfig : HasLogger() {
lateinit var rules: List<Node.MatchAll>
fun readConfig(manager: IResourceManager) {
val configs = manager.listResources("config/betterfoliage") { it.endsWith(".rules") }
rules = configs.flatMap { configLocation ->
val resource = manager.getResource(configLocation)
val parser = BlockConfigParser(resource.inputStream)
.apply { configFile = configLocation.stripStart("config/betterfoliage/").toString() }
try {
parser.matchFile()
} catch (e: ParseException) {
parseError(e, configLocation)
} catch (e: TokenMgrError) {
parseError(e, configLocation)
}
}
}
fun parseError(e: Throwable, location: ResourceLocation): List<Node.MatchAll> {
"Error parsing block config $location: ${e.message}".let {
logger.log(ERROR, it)
detailLogger.log(ERROR, it)
}
return emptyList()
}
}

View File

@@ -1,35 +0,0 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ModelTextureListConfiguration
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
object BlockConfig {
private val list = mutableListOf<Any>()
val leafBlocks = blocks("leaves_blocks_default.cfg")
val leafModels = models("leaves_models_default.cfg")
val grassBlocks = blocks("grass_blocks_default.cfg")
val grassModels = models("grass_models_default.cfg")
val mycelium = blocks("mycelium_blocks_default.cfg")
// val dirt = blocks("dirt_default.cfg")
val crops = blocks("crop_default.cfg")
val logBlocks = blocks("log_blocks_default.cfg")
val logModels = models("log_models_default.cfg")
val lilypad = blocks("lilypad_default.cfg")
init { BetterFoliageMod.bus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(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

@@ -5,13 +5,6 @@ import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.world.biome.Biome
val CACTUS_BLOCKS = listOf(Blocks.CACTUS)
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
val SNOW_MATERIALS = listOf(Material.TOP_SNOW, Material.SNOW)

View File

@@ -0,0 +1,162 @@
package mods.betterfoliage.config.match
enum class MatchMethod {
EXACT_MATCH, EXTENDS, CONTAINS;
fun description(isSuccess: Boolean) = when (this) {
EXACT_MATCH -> if (isSuccess) "matches" else "does not match"
EXTENDS -> if (isSuccess) "extends" else "does not extend"
CONTAINS -> if (isSuccess) "contains" else "does not contain"
}
}
/**
* Basic Either monad implementation
*/
sealed class Either<out L, out R> {
class Left<L>(val left: L) : Either<L, Nothing>()
class Right<R>(val right: R) : Either<Nothing, R>()
fun leftOrNull() = if (this is Left) left else null
fun rightOrNull() = if (this is Right) right else null
fun <R2> map(func: (R) -> R2): Either<L, R2> = when (this) {
is Left<L> -> this
is Right<R> -> Right(func(right))
}
fun <L2> mapLeft(func: (L) -> L2): Either<L2, R> = when (this) {
is Left<L> -> Left(func(left))
is Right<R> -> this
}
fun ifRight(action: (R) -> Unit) {
if (this is Right) action(right)
}
companion object {
fun <L> ofLeft(left: L) = Left(left)
fun <R> ofRight(right: R) = Right(right)
}
}
// this cannot be inside the class for variance reasons
fun <L, R, R2> Either<L, R>.flatMap(func: (R) -> Either<L, R2>) = when (this) {
is Either.Left<L> -> this
is Either.Right<R> -> func(right)
}
fun <L, R, L2> Either<L, R>.flatMapLeft(func: (L) -> Either<L2, R>) = when (this) {
is Either.Left<L> -> func(left)
is Either.Right<R> -> this
}
fun <T> Either<T, T>.flatten() = when (this) {
is Either.Left -> left
is Either.Right -> right
}
interface MAnything<out T> {
val value: T
val immutable: Boolean
}
class MListAll(val list: List<MAnything<Boolean>>) : MAnything<Boolean> {
override val value get() = list.all { it.value }
override val immutable get() = list.all { it.immutable }
}
class MListAny(val list: List<MValue<Boolean>>) : MAnything<Boolean> {
override val value get() = list.any { it.value }
override val immutable get() = list.all { it.immutable }
}
class MNegated(val inner: MAnything<Boolean>) : MAnything<Boolean> {
override val value get() = !inner.value
override val immutable get() = inner.immutable
}
/**
* Value with metadata related to rule matching applied.
*
* @param value the wrapped value
* @param description human-readable description of what the value represents
* @param configSource identifies where the value is described in the config
* @param immutable true if the value never changes
* (another [MValue] constructed in the same way will have the same value)
*
*/
class MValue<out T>(
override val value: T,
val description: String,
val configSource: ConfigSource,
override val immutable: Boolean,
) : MAnything<T> {
companion object {
fun <T> right(value: T, description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofRight(MValue(value, description, configSource, immutable))
fun left(description: String, configSource: ConfigSource, immutable: Boolean = true) =
Either.ofLeft(MValue(false, description, configSource, immutable))
}
}
typealias MEither<T> = Either<MValue<Boolean>, MValue<T>>
val Node.Value.asEither get() = MValue.right(value, value, configSource, true)
fun Node.Value.left(description: String) = MValue.left(description, configSource)
fun Node.invalidTypeFor(type: String) = MValue.left("invalid type for $type: [${this::class.java.name}]", configSource)
fun Node.error(description: String) = MValue.left(description, configSource)
fun <T, R> MEither<T>.mapValue(func: (T) -> R) = map {
MValue(func(it.value), it.description, it.configSource, it.immutable)
}
fun <T> MEither<T>.mapDescription(func: (MValue<T>) -> String) = map {
MValue(it.value, func(it), it.configSource, it.immutable)
}
fun <T, R> MEither<T>.map(
func: (T) -> R,
description: (MValue<T>, R) -> String
) = map { t -> func(t.value).let { r -> MValue(r, description(t, r), t.configSource, t.immutable) } }
fun <T, R> MEither<T>.mapNotNull(
func: (T) -> R?,
dLeft: (MValue<T>) -> String = { it.description },
dRight: (MValue<T>, R) -> String = { m, _ -> m.description }
) = flatMap { t ->
func(t.value)?.let { r ->
MValue.right(r, dRight(t, r), t.configSource, t.immutable)
} ?: MValue.left(dLeft(t), t.configSource, t.immutable)
}
fun <T> MEither<T>.toRight(value: T) =
flatMapLeft { MValue.right(value, it.description, it.configSource, it.immutable) }
data class MComparison<T1, T2>(
private val opTrue: String,
private val opFalse: String,
val testFunc: (T1, T2) -> Boolean
) {
fun compare(value1: MEither<T1>, value2: MEither<T2>) = when {
value1 is Either.Left -> value1
value2 is Either.Left -> value2
else -> {
val isSuccess = testFunc((value1 as Either.Right).right.value, (value2 as Either.Right).right.value)
MValue.right(
isSuccess,
"${value1.right.description} ${if (isSuccess) opTrue else opFalse} ${value2.right.description}",
value2.right.configSource,
value1.right.immutable && value2.right.immutable
)
}
}.flatten()
companion object {
fun <T1, T2> of(matchMethod: MatchMethod, testFunc: (T1, T2) -> Boolean) =
MComparison(matchMethod.description(true), matchMethod.description(false), testFunc)
val equals = of(MatchMethod.EXACT_MATCH) { t1: Any, t2: Any -> t1 == t2 }
}
}

View File

@@ -0,0 +1,42 @@
package mods.betterfoliage.config.match
data class ConfigSource(
val configFile: String,
val line: Int,
val column: Int
) {
override fun toString() = "$configFile @ R$line,C$column"
}
sealed class Node {
enum class MatchSource { BLOCK_CLASS, BLOCK_NAME, MODEL_LOCATION }
abstract val configSource: ConfigSource
class MatchValueList(
val matchSource: MatchSource,
val matchMethod: MatchMethod,
override val configSource: ConfigSource,
val values: List<Value>
) : Node()
class MatchParam(
val name: String,
val values: List<Value>,
override val configSource: ConfigSource,
) : Node()
class Negate(val node: Node) : Node() {
override val configSource get() = node.configSource
}
class SetParam(val name: String, val value: Value, override val configSource: ConfigSource) : Node()
class MatchAll(override val configSource: ConfigSource, val list: List<Node>) : Node()
abstract class Value(override val configSource: ConfigSource, val value: String) : Node() {
class Literal(configSource: ConfigSource, value: String) : Value(configSource, value)
class ClassOf(configSource: ConfigSource, value: String) : Value(configSource, value)
class Texture(configSource: ConfigSource, value: String) : Value(configSource, value)
class Tint(configSource: ConfigSource, value: String) : Value(configSource, value)
}
}

View File

@@ -0,0 +1,172 @@
package mods.betterfoliage.config.match
import mods.betterfoliage.config.match.MatchMethod.CONTAINS
import mods.betterfoliage.config.match.MatchMethod.EXACT_MATCH
import mods.betterfoliage.config.match.MatchMethod.EXTENDS
import mods.betterfoliage.resource.discovery.RuleProcessingContext
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.quoted
import mods.betterfoliage.util.tryDefault
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.ResourceLocation
import net.minecraftforge.registries.ForgeRegistries
typealias PartialLocation = Pair<String?, String>
object MatchRules {
fun visitRoot(ctx: RuleProcessingContext, node: Node.MatchAll): MListAll {
val results = mutableListOf<MAnything<Boolean>>()
for (rule in node.list) {
val result = mNode(ctx, rule)
results.add(result)
if (!result.value) break
}
return MListAll(results)
}
fun mNode(ctx: RuleProcessingContext, node: Node): MAnything<Boolean> = when(node) {
is Node.MatchValueList -> mMatchList(ctx, node)
is Node.MatchParam -> mParam(ctx, node)
is Node.SetParam -> mParamSet(ctx, node)
is Node.Negate -> mNegate(ctx, node)
else -> node.error("match type not implemented: ${node::class.java.name.quoted}").left
}
fun mNegate(ctx: RuleProcessingContext, node: Node.Negate) = MNegated(mNode(ctx, node.node))
fun mMatchList(ctx: RuleProcessingContext, node: Node.MatchValueList) = node.values.map { value ->
when (node.matchSource) {
Node.MatchSource.BLOCK_CLASS -> mBlockClass(ctx, node, value)
Node.MatchSource.BLOCK_NAME -> mBlockName(ctx, node, value)
Node.MatchSource.MODEL_LOCATION -> mModel(ctx, node, value)
}
}.let { MListAny(it) }
fun mBlockClass(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockClass = ctx.discovery.blockState.block::class.java.let {
MValue.right(it, "block class ${it.name.quoted}", node.configSource)
}
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapNotNull(
func = { tryDefault(null) { Class.forName(it) }},
dLeft = { "missing class ${it.value}" },
dRight = { m, _ -> " class ${m.value}" }
)
is Node.Value.ClassOf -> value.asEither.mapValue(::ResourceLocation).mapNotNull(
func = { loc -> ForgeRegistries.BLOCKS.getValue(loc)?.let { it::class.java } },
dLeft = { "missing block ${it.value.toString().quoted}" },
dRight = { m, r -> "class ${r.name.quoted} of block ${m.value}" }
)
else -> value.invalidTypeFor("block class")
}
return when(node.matchMethod) {
EXACT_MATCH -> MComparison.equals.compare(blockClass, target)
EXTENDS -> classExtends.compare(blockClass, target)
CONTAINS -> node.error("invalid match type for block class: contains").left
}
}
fun mBlockName(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val blockName = MValue.right(ctx.discovery.blockState.block, "", node.configSource).mapNotNull(
func = { it.registryName }, dLeft = { "missing block name" }, dRight = { _, r -> "block name ${r.toString().quoted}" }
)
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("block name")
}
return when(node.matchMethod) {
EXACT_MATCH -> blockNameExact.compare(blockName, target)
CONTAINS -> blockNameContains.compare(blockName, target)
EXTENDS -> node.error("invalid match type for block name: extends").left
}
}
fun mModel(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MValue<Boolean> {
val model = (ctx.discovery.getUnbaked() as? BlockModel)?.let {
MValue.right(it, "model ${it.name.quoted}", node.configSource)
} ?: node.error("unsupported model type: ${ctx.discovery.getUnbaked()::class.java.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.map(::splitLocation, ::quoteString)
else -> value.invalidTypeFor("model")
}
val models = when(node.matchMethod) {
EXTENDS -> model.mapValue { ctx.discovery.loadHierarchy(it).ancestors() }
else -> model.mapValue { listOf(it) }
}
return when(node.matchMethod) {
EXACT_MATCH, EXTENDS -> anyModel(node.matchMethod, ::locationMatches)
CONTAINS -> anyModel(CONTAINS, ::locationContains)
}.compare(models, target)
}
fun mParam(ctx: RuleProcessingContext, node: Node.MatchParam) = node.values.map { value ->
val paramValue = ctx.params[node.name] ?.let {
MValue.right(it, "parameter ${node.name.quoted}", node.configSource, immutable = false)
} ?: node.error("missing parameter ${node.name.quoted}")
val target = when(value) {
is Node.Value.Literal -> value.asEither.mapDescription { it.description.quoted }
else -> value.invalidTypeFor("parameter")
}
MComparison.equals.compare(paramValue, target)
}.let { MListAny(it) }
fun mParamSet(ctx: RuleProcessingContext, node: Node.SetParam): MValue<Boolean> {
val target = when(node.value) {
is Node.Value.Literal -> node.value.asEither
is Node.Value.Texture -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.map(
func = { model.getMaterial(it).texture().toString() },
description = { m, r -> "texture \"${m.value}\" = \"$r\" of model ${model.name}"}
)
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
is Node.Value.Tint -> when(val model = ctx.discovery.getUnbaked()) {
is BlockModel -> node.value.asEither.mapNotNull(
func = { model.tintOf(it)?.toString() },
dRight = { m, r -> "tint index $r for sprite ${m.value}" },
dLeft = { m -> "tint index -1 for unused sprite ${m.value}"}
).toRight("-1")
else -> node.error("unsupported model type: ${model::class.java.name.quoted}")
}
else -> node.value.invalidTypeFor("prameter")
}
target.ifRight { ctx.params[node.name] = it.value }
return target.mapDescription { m -> "parameter ${node.name} set to ${m.value}" }.mapValue { true }.flatten()
}
private val classExtends = MComparison.of<Class<*>, Class<*>>(EXTENDS) { c1, c2 -> c2.isAssignableFrom(c1) }
private val blockNameExact = MComparison.of<ResourceLocation, PartialLocation>(EXACT_MATCH) { block, partial ->
locationMatches(block, partial)
}
private val blockNameContains = MComparison.of<ResourceLocation, Pair<String?, String>>(CONTAINS) { block, partial ->
locationContains(block, partial)
}
private fun anyModel(matchMethod: MatchMethod, func: (ResourceLocation, PartialLocation)->Boolean) =
MComparison.of<List<BlockModel>, PartialLocation>(matchMethod) { models, partial ->
models.any { func(ResourceLocation(it.name), partial) }
}
fun locationMatches(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace == partial.first) && loc.path == partial.second
fun locationContains(loc: ResourceLocation, partial: PartialLocation) =
(partial.first == null || loc.namespace.contains(partial.first!!)) && loc.path.contains(partial.second)
fun splitLocation(str: String): PartialLocation =
if (str.contains(":")) ResourceLocation(str).let { it.namespace to it.path } else null to str
fun <T, R> quoteString(mValue: MValue<T>, newValue: R) = mValue.description.quoted
fun BlockModel.ancestors(): List<BlockModel> = if (parent == null) listOf(this) else parent!!.ancestors() + this
fun BlockModel.tintOf(spriteName: String) =
elements.findFirst { element ->
element.faces.entries.findFirst { (_, face) ->
if (face.texture == "#$spriteName") face.tintIndex else null
}
}
}

View File

@@ -4,10 +4,10 @@ import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.reflectField
import mods.octarinecore.BlockPos
import mods.octarinecore.BlockState
import mods.octarinecore.CustomColors
import mods.octarinecore.RenderEnv
import mods.betterfoliage.BlockPos
import mods.betterfoliage.BlockState
import mods.betterfoliage.CustomColors
import mods.betterfoliage.RenderEnv
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.BakedQuad

View File

@@ -1,13 +1,16 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BlockAliases
import mods.betterfoliage.BufferBuilder_sVertexBuilder
import mods.betterfoliage.SVertexBuilder
import mods.betterfoliage.Shaders
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.octarinecore.*
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
@@ -41,7 +44,7 @@ object ShadersModIntegration : HasLogger() {
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockStateOverride(state: BlockState, world: IBlockDisplayReader, pos: BlockPos): BlockState {
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
if (state in BetterFoliage.blockTypes.leaf) return defaultLeaves
// if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
return state
}
@@ -50,8 +53,8 @@ object ShadersModIntegration : HasLogger() {
logger.log(INFO, "ShadersMod diffuse shading integration is ${if (isDiffuseAvailable) "enabled" else "disabled" }")
logger.log(INFO, "ShadersMod vertex shader integration is ${if (isEffectsAvailable) "enabled" else "disabled" }")
// Recalculate the diffsuse shading values used when resources are reloaded
if (isDiffuseAvailable) BakeWrapperManager.onInvalidate {
// Recalculate the diffuse shading values used when resources are reloaded
if (isDiffuseAvailable) BetterFoliage.modelManager.onInvalidate {
if (Shaders.shaderPackLoaded.getStatic()) {
diffuseShades = Direction.values().mapArray { face ->
when(face) {

View File

@@ -1,12 +1,19 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.WrappedLayerPredicate
import mods.betterfoliage.render.pipeline.layerPredicate
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.directionsAndNull
import mods.betterfoliage.util.mapArray
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.SimpleBakedModel
@@ -27,15 +34,23 @@ data class HalfBakedQuad(
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, SpecialRenderModel {
val baseQuads = baseModel.unbakeQuads()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.renderQuads(baseQuads)
override fun prepare(ctx: BlockCtx, random: Random) = Unit
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
// if the passed data is a BlockState, render on the same layer(s) as that block
val testState = (data as? BlockState) ?: ctx.state
// this could get called for more layers than the underlying model is on
// ignore extra decoration layers
val shouldRender = when(val predicate = testState.block.layerPredicate) {
is WrappedLayerPredicate -> predicate.original.test(layer)
else -> RenderTypeLookup.canRenderInLayer(testState, layer)
}
if (shouldRender) ctx.renderQuads(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): IBakedModel by baseModel, SpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
baseModel.render(ctx, noDecorations)
}
open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): SpecialRenderModel by baseModel {
}
abstract class HalfBakedWrapperKey : ModelBakingKey, HasLogger() {

View File

@@ -70,6 +70,8 @@ data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
val asHSB get() = HSB.fromColor(this)
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
@@ -89,8 +91,17 @@ data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
val hsbVals = java.awt.Color.RGBtoHSB(color and 255, (color shr 8) and 255, (color shr 16) and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
fun fromColorBGRA(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
fun fromColor(color: Color): HSB {
val hsbVals = java.awt.Color.RGBtoHSB(color.red, color.green, color.blue, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
val asInt: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
val asColor: Color get() = Color(asInt)
}
/**

View File

@@ -1,7 +1,10 @@
package mods.betterfoliage.model
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.WeightedBakedModel
import net.minecraft.util.WeightedRandom
import java.util.Random
@@ -9,18 +12,46 @@ import java.util.Random
* Model that makes use of advanced rendering features.
*/
interface SpecialRenderModel : IBakedModel {
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
/**
* Create custom renderdata object. Called once per block. Result is passed to renderLayer().
*/
fun prepare(ctx: BlockCtx, random: Random): Any
fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType)
/**
* Get the actual model that will be rendered. Useful for container models (like [WeightedBakedModel]).
*/
fun resolve(random: Random) = this
}
interface SpecialRenderData {
fun canRenderInLayer(layer: RenderType) = false
}
class WeightedModelWrapper(
val models: List<WeightedModel>, baseModel: SpecialRenderModel
): IBakedModel by baseModel, SpecialRenderModel {
) : IBakedModel by baseModel, SpecialRenderModel {
class WeightedModel(val model: SpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
val totalWeight = models.sumBy { it.weight }
fun getModel(random: Random) = WeightedRandom.getWeightedItem(models, random.nextInt(totalWeight))
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
getModel(ctx.random).model.render(ctx, noDecorations)
override fun resolve(random: Random) = getModel(random).model.resolve(random)
override fun prepare(ctx: BlockCtx, random: Random) = getModel(random).model.let { actual ->
WeightedRenderData(actual, actual.prepare(ctx, random))
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) = when (data) {
is WeightedRenderData -> data.model.renderLayer(ctx, data.modelRenderData, layer)
else -> getModel(ctx.random).model.renderLayer(ctx, data, layer)
}
}
data class WeightedRenderData(
val model: SpecialRenderModel,
val modelRenderData: Any
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = (modelRenderData as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}

View File

@@ -18,7 +18,6 @@ import kotlin.math.sin
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx.toDouble() / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
data class TuftShapeKey(
val size: Double,
val height: Double,
@@ -50,13 +49,13 @@ fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, tintIndex: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
fun tuftModelSet(shapes: Array<TuftShapeKey>, color: Color, tint: Int, spriteGetter: (Int) -> TextureAtlasSprite) =
shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorIndex(tintIndex) }
.map { it.color(color).colorIndex(tint) }
.map { it.sprite(spriteGetter(idx)) }
}
@@ -85,20 +84,20 @@ fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Li
}
}
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, tintIndex: Int,scrambleUV: Boolean) =
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, color: Color, tint: Int, scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorIndex(tintIndex) }
.map { it.color(color).colorIndex(tint) }
.mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites()
.bake(false)
fun crossModelsTextured(
leafBase: Iterable<List<Quad>>,
tintIndex: Int,
color: Color, tint: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], color, tint, scrambleUV)
}.toTypedArray()
fun Iterable<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }

View File

@@ -1,9 +1,10 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.CACTUS_BLOCKS
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
@@ -17,31 +18,25 @@ import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardCactusDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is BlockModel && ctx.blockState.block in CACTUS_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
}
super.processModel(ctx)
object StandardCactusDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardCactusKey)
ctx.sprites.add(StandardCactusModel.cactusCrossSprite)
}
}
@@ -49,22 +44,30 @@ object StandardCactusKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
}
class CactusRenderData(val armSide: Int, val armIdx: Int, val crossIdx: Int)
class StandardCactusModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun prepare(ctx: BlockCtx, random: Random): Any = when {
!Config.enabled || !Config.cactus.enabled -> Unit
else -> CactusRenderData(
armSide = random.nextInt() and 3,
armIdx = random.idx(cactusArmModels),
crossIdx = random.idx(cactusCrossModels)
)
}
val armLighting = horizontalDirections.map { LightingPreferredFace(it) }.toTypedArray()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.checkSides = false
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.cactus.enabled) return
val armSide = ctx.random.nextInt() and 3
ctx.vertexLighter = armLighting[armSide]
ctx.renderQuads(cactusArmModels[armSide][ctx.random])
ctx.vertexLighter = RoundLeafLighting
ctx.renderQuads(cactusCrossModels[ctx.random])
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is CactusRenderData) {
ctx.vertexLighter = armLighting[data.armSide]
ctx.renderQuads(cactusArmModels[data.armSide][data.armIdx])
ctx.vertexLighter = RoundLeafLighting
ctx.renderQuads(cactusCrossModels[data.crossIdx])
}
}
companion object {
@@ -72,19 +75,19 @@ class StandardCactusModel(
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
}
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val cactusArmModels by BetterFoliage.modelManager.lazy {
val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, -1) { cactusArmSprites[randomI()] }
val models = tuftModelSet(shapes, Color.white, -1) { cactusArmSprites[randomI()] }
horizontalDirections.map { side ->
models.transform { move(0.0625 to DOWN).rotate(Rotation.fromUp[side.ordinal]) }.buildTufts()
}.toTypedArray()
}
val cactusCrossModels by LazyInvalidatable(BakeWrapperManager) {
val cactusCrossModels by BetterFoliage.modelManager.lazy {
val models = Config.cactus.let { config ->
crossModelsRaw(64, config.size, 0.0, 0.0)
.transform { rotateZ(randomD(-config.sizeVariation, config.sizeVariation)) }
}
crossModelsTextured(models, -1, true) { cactusCrossSprite }
crossModelsTextured(models, Color.white, -1, true) { cactusCrossSprite }
}
}
}

View File

@@ -2,52 +2,46 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.DIRT_BLOCKS
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getBlockModel
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.randomI
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardDirtDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.solid()
!Config.connectedGrass.enabled && !Config.algae.enabled && !Config.reed.enabled -> layer == RenderType.solid()
else -> layer == RenderType.cutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in DIRT_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
object StandardDirtDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
ctx.blockState.block.extendLayers()
}
}
@@ -55,48 +49,77 @@ object StandardDirtKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
}
class DirtRenderData(
val connectedGrassModel: SpecialRenderModel?,
val algaeIdx: Int?,
val reedIdx: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassModel != null && layer == Layers.connectedDirt -> true
(algaeIdx != null || reedIdx != null) && layer == Layers.tufts -> true
else -> false
}
}
class StandardDirtModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val vanillaTuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
val stateUp = ctx.state(UP)
val state2Up = ctx.state(Int3(0, 2, 0))
val isConnectedGrass = Config.connectedGrass.enabled &&
stateUp in BetterFoliage.blockTypes.grass &&
(Config.connectedGrass.snowEnabled || !state2Up.isSnow)
if (isConnectedGrass) {
(ctx.blockModelShapes.getBlockModel(stateUp) as? SpecialRenderModel)?.let { grassModel ->
ctx.renderMasquerade(UP.offset) {
grassModel.render(ctx, true)
}
return
}
return super.render(ctx, false)
}
super.render(ctx, false)
val isWater = stateUp.material == Material.WATER
val isDeepWater = isWater && state2Up.material == Material.WATER
val isShallowWater = isWater && state2Up.isAir
val isSaltWater = isWater && ctx.biome?.biomeCategory in SALTWATER_BIOMES
if (Config.algae.enabled(ctx.random) && isDeepWater) {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.renderQuads(algaeModels[ctx.random])
}
} else if (Config.reed.enabled(ctx.random) && isShallowWater && !isSaltWater) {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.renderQuads(reedModels[ctx.random])
// get the actual grass model to use for connected grass rendering
// return null if the grass specifically does not want to connect
val connectedGrassModel = if (!isConnectedGrass) null else getBlockModel(stateUp).let { model ->
(model as? SpecialRenderModel)?.resolve(random)?.let { grassModel ->
if ((grassModel as? StandardGrassModel)?.key?.noConnect == true) null else grassModel
}
}
return DirtRenderData(
connectedGrassModel = connectedGrassModel,
algaeIdx = random.idxOrNull(algaeModels) { Config.algae.enabled(random) && isDeepWater && isSaltWater },
reedIdx = random.idxOrNull(reedModels) { Config.reed.enabled(random) && isShallowWater && !isSaltWater }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (data is DirtRenderData) {
if (data.connectedGrassModel != null) {
ctx.renderMasquerade(UP.offset) {
data.connectedGrassModel.renderLayer(ctx, ctx.state(UP), layer)
}
} else {
// render non-connected grass
super.renderLayer(ctx, data, layer)
}
if (layer == Layers.tufts) {
data.algaeIdx?.let {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.renderQuads(algaeModels[it])
}
}
data.reedIdx?.let {
ctx.vertexLighter = vanillaTuftLighting
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.renderQuads(reedModels[it])
}
}
}
} else super.renderLayer(ctx, data, layer)
}
companion object {
@@ -108,13 +131,13 @@ class StandardDirtModel(
idFunc = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(BetterFoliage.generatedPack) }
)
val algaeModels by LazyInvalidatable(BakeWrapperManager) {
val algaeModels by BetterFoliage.modelManager.lazy {
val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { algaeSprites[randomI()] }.buildTufts()
tuftModelSet(shapes, Color.white, -1) { algaeSprites[randomI()] }.buildTufts()
}
val reedModels by LazyInvalidatable(BakeWrapperManager) {
val reedModels by BetterFoliage.modelManager.lazy {
val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { reedSprites[randomI()] }.buildTufts()
tuftModelSet(shapes, Color.white, -1) { reedSprites[randomI()] }.buildTufts()
}
}
}

View File

@@ -2,13 +2,14 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
@@ -16,53 +17,68 @@ import mods.betterfoliage.model.fullCubeTextured
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.get
import mods.betterfoliage.util.logColorOverride
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.logTextureColor
import mods.betterfoliage.util.randomI
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
import java.util.Random
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
object StandardGrassDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.let {
detailLogger.logTextureColor(INFO, "grass texture \"$texture\"", it)
it.brighten().asColor
}
val noConnect = params["no-connect"] == "true"
ctx.addReplacement(StandardGrassKey(texture, tint, color, noConnect))
BetterFoliage.blockTypes.grass.add(ctx.blockState)
ctx.blockState.block.extendLayers()
}
}
data class StandardGrassKey(
val grassLocation: ResourceLocation,
val overrideColor: Color?
val sprite: ResourceLocation,
val tintIndex: Int,
val avgColor: Color,
val noConnect: Boolean
) : HalfBakedWrapperKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val grassSpriteColor = Atlas.BLOCKS[grassLocation].averageColor.let { hsb ->
logColorOverride(BetterFoliageMod.detailLogger(this), Config.shortGrass.saturationThreshold, hsb)
hsb.colorOverride(Config.shortGrass.saturationThreshold)
}
return StandardGrassModel(wrapped, this.copy(overrideColor = grassSpriteColor))
return StandardGrassModel(wrapped, this)
}
}
class GrassRenderData(
val isSnowed: Boolean,
val connectedGrassIdx: Int?,
val tuftIdx: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
connectedGrassIdx != null && layer == Layers.connectedGrass -> true
tuftIdx != null && layer == Layers.tufts -> true
else -> false
}
}
class StandardGrassModel(
wrapped: SpecialRenderModel,
key: StandardGrassKey
val key: StandardGrassKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key)
@@ -70,48 +86,62 @@ class StandardGrassModel(
val fullBlock by grassFullBlockMeshes.delegate(key)
val tuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isAir = ctx.isAir(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
val connected = !key.noConnect && Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
BetterFoliage.blockTypes.run { stateBelow in grass || stateBelow in dirt }
if (connected) {
ctx.renderQuads(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
} else {
super.render(ctx, noDecorations)
}
if (Config.shortGrass.enabled(ctx.random) && Config.shortGrass.grassEnabled && (isAir || isSnowed)) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
return GrassRenderData(
isSnowed = isSnowed,
connectedGrassIdx = random.idxOrNull(if (isSnowed) snowFullBlockMeshes else fullBlock) { connected },
tuftIdx = random.idxOrNull(if (isSnowed) tuftSnowed else tuftNormal) {
Config.shortGrass.enabled(random) &&
Config.shortGrass.grassEnabled &&
(isAir || isSnowed)
}
}
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (data is GrassRenderData) {
if (data.connectedGrassIdx != null) {
if (layer == Layers.connectedGrass)
ctx.renderQuads((if (data.isSnowed) snowFullBlockMeshes else fullBlock)[data.connectedGrassIdx])
} else {
super.renderLayer(ctx, data, layer)
}
if (data.tuftIdx != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads((if (data.isSnowed) tuftSnowed else tuftNormal)[data.tuftIdx])
}
}
} else super.renderLayer(ctx, data, layer)
}
companion object {
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
val grassTuftShapes by BetterFoliage.modelManager.lazy {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
val grassTuftMeshesNormal = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, key.avgColor, key.tintIndex) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
val grassTuftMeshesSnowed = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, Color.white, -1) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.grassLocation, key.tintIndex) }
val grassFullBlockMeshes = BetterFoliage.modelManager.lazyMap { key: StandardGrassKey ->
Array(64) { fullCubeTextured(key.sprite, key.tintIndex) }
}
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
val snowFullBlockMeshes by BetterFoliage.modelManager.lazy {
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), -1) }
}
}

View File

@@ -2,7 +2,6 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.isSnow
import mods.betterfoliage.integration.ShadersModIntegration
@@ -18,50 +17,49 @@ import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.particle.LeafParticleKey
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.resource.generated.GeneratedLeafSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.colorOverride
import mods.betterfoliage.util.logColorOverride
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.logTextureColor
import mods.betterfoliage.util.saturate
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeafSprite(textureMatch[0], leafType)
object StandardLeafDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.let {
detailLogger.logTextureColor(INFO, "leaf texture \"$texture\"", it)
it.brighten().asColor
}
val leafType = params["particle"] ?: "default"
val generated = GeneratedLeafSprite(texture, leafType)
.register(BetterFoliage.generatedPack)
.apply { ctx.sprites.add(this) }
detailLogger.log(INFO, " particle $leafType")
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
ctx.addReplacement(StandardLeafKey(generated, leafType, tint, color))
BetterFoliage.blockTypes.leaf.add(ctx.blockState)
}
}
data class StandardLeafKey(
val roundLeafTexture: ResourceLocation,
override val leafType: String,
override val overrideColor: Color?
override val tintIndex: Int,
override val avgColor: Color
) : HalfBakedWrapperKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
val leafSpriteColor = Atlas.BLOCKS[roundLeafTexture].averageColor.let { hsb ->
logColorOverride(BetterFoliageMod.detailLogger(this), Config.leaves.saturationThreshold, hsb)
hsb.colorOverride(Config.leaves.saturationThreshold)
}
return StandardLeafModel(wrapped, this.copy(overrideColor = leafSpriteColor))
return StandardLeafModel(wrapped, this)
}
}
@@ -72,15 +70,15 @@ class StandardLeafModel(
val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
ShadersModIntegration.leaves(ctx, true) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
super.renderLayer(ctx, data, layer)
if (!Config.enabled || !Config.leaves.enabled) return
ctx.vertexLighter = RoundLeafLightingPreferUp
val leafIdx = ctx.random.nextInt(64)
ctx.renderQuads(leafNormal[leafIdx])
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
if (Config.leaves.snowEnabled && ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
}
}
@@ -88,14 +86,16 @@ class StandardLeafModel(
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
}
val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
val leafModelsBase by BetterFoliage.modelManager.lazy {
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
}
val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], key.tintIndex, true) { key.roundLeafTexture }
val leafModelsNormal = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey ->
// generated leaf textures naturally carry the color of their source textures
// no need to color the quad a second time
crossModelsTextured(leafModelsBase, Color.white, key.tintIndex, true) { key.roundLeafTexture }
}
val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], -1, false) { leafSpritesSnowed[it].name }
val leafModelsSnowed = BetterFoliage.modelManager.lazyMap { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase, Color.white, -1, false) { leafSpritesSnowed[it].name }
}
}
}

View File

@@ -1,9 +1,11 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.LILYPAD_BLOCKS
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
@@ -13,27 +15,21 @@ import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.ModelBakery
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardLilypadDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey)
}
super.processModel(ctx)
object StandardLilypadDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardLilypadKey)
}
}
@@ -41,18 +37,32 @@ object StandardLilypadKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
}
class LilypadRenderData(
val rootIdx: Int,
val flowerIdx: Int?
)
class StandardLilypadModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.checkSides = false
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.lilypad.enabled) return
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ctx.renderQuads(lilypadRootModels[ctx.random])
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return LilypadRenderData(
rootIdx = random.idx(lilypadRootModels),
flowerIdx = random.idxOrNull(lilypadFlowerModels) { Config.lilypad.enabled(random) }
)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
ctx.checkSides = false
super.renderLayer(ctx, data, layer)
if (data is LilypadRenderData) {
data.flowerIdx?.let { ctx.renderQuads(lilypadFlowerModels[it]) }
ShadersModIntegration.grass(ctx, Config.lilypad.shaderWind) {
ctx.renderQuads(lilypadRootModels[data.rootIdx])
}
}
if (Config.lilypad.enabled(ctx.random)) ctx.renderQuads(lilypadFlowerModels[ctx.random])
}
companion object {
@@ -62,15 +72,15 @@ class StandardLilypadModel(
val lilypadFlowerSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx")
}
val lilypadRootModels by LazyInvalidatable(BakeWrapperManager) {
val lilypadRootModels by BetterFoliage.modelManager.lazy {
val shapes = tuftShapeSet(1.0, 1.0, 1.0, Config.lilypad.hOffset)
tuftModelSet(shapes, -1) { lilypadRootSprites[it] }
tuftModelSet(shapes, Color.white, -1) { lilypadRootSprites[it] }
.transform { move(2.0 to DOWN) }
.buildTufts()
}
val lilypadFlowerModels by LazyInvalidatable(BakeWrapperManager) {
val lilypadFlowerModels by BetterFoliage.modelManager.lazy {
val shapes = tuftShapeSet(0.5, 0.5, 0.5, Config.lilypad.hOffset)
tuftModelSet(shapes, -1) { lilypadFlowerSprites[it] }
tuftModelSet(shapes, Color.white, -1) { lilypadFlowerSprites[it] }
.transform { move(1.0 to DOWN) }
.buildTufts()
}

View File

@@ -1,61 +1,87 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.MYCELIUM_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.averageHSB
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.brighten
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in MYCELIUM_BLOCKS) {
ctx.addReplacement(StandardMyceliumKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutout())
}
super.processModel(ctx)
object StandardMyceliumDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val texture = params.location("texture") ?: return
val tint = params.int("tint") ?: -1
val color = Atlas.BLOCKS.file(texture).averageHSB.brighten(multiplier = 1.5f).asColor
ctx.addReplacement(StandardMyceliumKey(texture, tint, color))
ctx.blockState.block.extendLayers()
}
}
object StandardMyceliumKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
data class StandardMyceliumKey(
val sprite: ResourceLocation,
val tintIndex: Int,
val avgColor: Color,
) : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel {
return StandardMyceliumModel(wrapped, this)
}
}
class MyceliumRenderData(
val tuftIndex: Int?
) : SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardMyceliumModel(
wrapped: SpecialRenderModel
wrapped: SpecialRenderModel,
key: StandardMyceliumKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftModels by myceliumTuftModels.delegate(key)
val tuftLighting = LightingPreferredFace(Direction.UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return MyceliumRenderData(
random.idxOrNull(tuftModels) {
Config.shortGrass.enabled(random) &&
Config.shortGrass.myceliumEnabled &&
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
}
)
}
if (Config.shortGrass.enabled(ctx.random) &&
Config.shortGrass.myceliumEnabled &&
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
) {
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is MyceliumRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(myceliumTuftModels[ctx.random])
ctx.renderQuads(tuftModels[data.tuftIndex])
}
}
@@ -63,9 +89,11 @@ class StandardMyceliumModel(
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx")
}
val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
val myceliumTuftShapes by BetterFoliage.modelManager.lazy {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val myceliumTuftModels = BetterFoliage.modelManager.lazyMap { key: StandardMyceliumKey ->
tuftModelSet(myceliumTuftShapes, key.avgColor, key.tintIndex) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
}
}
}

View File

@@ -1,11 +1,13 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.NETHERRACK_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
@@ -13,37 +15,26 @@ import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
import mods.betterfoliage.util.idxOrNull
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardNetherrackDiscovery : AbstractModelDiscovery() {
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.solid()
!Config.netherrack.enabled -> layer == RenderType.solid()
else -> layer == RenderType.cutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
object StandardNetherrackDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardNetherrackKey)
ctx.blockState.block.extendLayers()
}
}
@@ -51,19 +42,33 @@ object StandardNetherrackKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped)
}
class NetherrackRenderData(
val tuftIndex: Int?
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = tuftIndex != null && layer == Layers.tufts
}
class StandardNetherrackModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val tuftLighting = LightingPreferredFace(DOWN)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.netherrack.enabled) return
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
return NetherrackRenderData(
random.idxOrNull(netherrackTuftModels) {
Config.netherrack.enabled &&
ctx.isAir(DOWN)
}
)
}
if (ctx.isAir(DOWN)) {
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is NetherrackRenderData && data.tuftIndex != null && layer == Layers.tufts) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(netherrackTuftModels[ctx.random])
ctx.renderQuads(netherrackTuftModels[data.tuftIndex])
}
}
@@ -71,9 +76,9 @@ class StandardNetherrackModel(
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx")
}
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val netherrackTuftModels by BetterFoliage.modelManager.lazy {
val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { netherrackTuftSprites[randomI()] }
tuftModelSet(shapes, Color.white, -1) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.buildTufts()
}

View File

@@ -2,7 +2,6 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.ACCEPTED_ROUND_LOG_MATERIALS
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
@@ -10,15 +9,12 @@ import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.render.column.ColumnMeshSet
import mods.betterfoliage.render.column.ColumnModelBase
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.lazyMap
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.RotatedPillarBlock
@@ -38,16 +34,15 @@ object RoundLogOverlayLayer : ColumnRenderLayer() {
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
object StandardRoundLogDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
object StandardRoundLogDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
val barkSprite = params.location("texture-side") ?: return
val endSprite = params.location("texture-end") ?: return
val axis = getAxis(ctx.blockState)
detailLogger.log(INFO, " axis $axis, material ${ctx.blockState.material}")
if (!Config.roundLogs.plantsOnly || ctx.blockState.material in ACCEPTED_ROUND_LOG_MATERIALS)
ctx.addReplacement(StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]))
ctx.addReplacement(StandardRoundLogKey(axis, barkSprite, endSprite))
}
fun getAxis(state: BlockState): Axis? {
@@ -82,7 +77,7 @@ class StandardRoundLogModel(
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
companion object {
val modelSets = LazyMapInvalidatable(BakeWrapperManager) { key: StandardRoundLogKey ->
val modelSets = BetterFoliage.modelManager.lazyMap { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS[key.barkSprite]
val endSprite = Atlas.BLOCKS[key.endSprite]
Config.roundLogs.let { config ->

View File

@@ -2,12 +2,14 @@ package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.SALTWATER_BIOMES
import mods.betterfoliage.config.SAND_BLOCKS
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.bake
@@ -16,36 +18,33 @@ import mods.betterfoliage.model.transform
import mods.betterfoliage.model.tuftModelSet
import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.Layers
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.render.pipeline.extendLayers
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ParametrizedModelDiscovery
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.get
import mods.betterfoliage.util.idx
import mods.betterfoliage.util.lazy
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import java.util.Random
object StandardSandDiscovery : AbstractModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in SAND_BLOCKS) {
BetterFoliage.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.cutoutMipped())
}
super.processModel(ctx)
object StandardSandDiscovery : ParametrizedModelDiscovery() {
override fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>) {
ctx.addReplacement(StandardSandKey)
ctx.blockState.block.extendLayers()
}
}
@@ -53,23 +52,46 @@ object StandardSandKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
}
class SandRenderData(
val crustIdx: Array<Int?>,
val tuftIdx: Array<Int?>
): SpecialRenderData {
override fun canRenderInLayer(layer: RenderType) = when {
(crustIdx.any { it != null } || tuftIdx.any { it != null }) && layer == Layers.coral -> true
else -> false
}
}
class StandardSandModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (noDecorations || !Config.enabled || !Config.coral.enabled(ctx.random)) return
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return
override fun prepare(ctx: BlockCtx, random: Random): Any {
if (!Config.enabled) return Unit
if (!Config.coral.enabled(random)) return Unit
if (ctx.biome?.biomeCategory !in SALTWATER_BIOMES) return Unit
allDirections.filter { ctx.random.nextInt(64) < Config.coral.chance }.forEach { face ->
val crustIdx = Array<Int?>(6) { null }
val tuftIdx = Array<Int?>(6) { null }
allDirections.filter { random.nextInt(64) < Config.coral.chance }.forEach { face ->
val isWater = ctx.state(face).material == Material.WATER
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
if (isDeepWater) {
crustIdx[face.ordinal] = random.idx(coralCrustModels)
tuftIdx[face.ordinal] = random.idx(coralTuftModels)
}
}
return SandRenderData(crustIdx, tuftIdx)
}
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
super.renderLayer(ctx, data, layer)
if (data is SandRenderData && layer == Layers.coral) {
for (face in 0 until 6) {
ctx.vertexLighter = coralLighting[face]
ctx.renderQuads(coralCrustModels[face][ctx.random])
ctx.renderQuads(coralTuftModels[face][ctx.random])
data.crustIdx[face]?.let { ctx.renderQuads(coralCrustModels[face][it]) }
data.tuftIdx[face]?.let { ctx.renderQuads(coralTuftModels[face][it]) }
}
}
}
@@ -81,15 +103,15 @@ class StandardSandModel(
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx")
}
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
val coralTuftModels by BetterFoliage.modelManager.lazy {
val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
allDirections.mapArray { face ->
tuftModelSet(shapes, -1) { coralTuftSprites[randomI()] }
tuftModelSet(shapes, Color.white, -1) { coralTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[face]) }
.buildTufts()
}
}
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
val coralCrustModels by BetterFoliage.modelManager.lazy {
allDirections.map { face ->
Array(64) { idx ->
listOf(

View File

@@ -13,6 +13,7 @@ import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantTy
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.render.lighting.ColumnLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction.Axis
abstract class ColumnModelBase(
@@ -24,21 +25,21 @@ abstract class ColumnModelBase(
abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!enabled) return super.render(ctx, noDecorations)
override fun renderLayer(ctx: RenderCtxBase, data: Any, layer: RenderType) {
if (!enabled) return super.renderLayer(ctx, data, layer)
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
val roundLog = overlayLayer[ctx]
when(roundLog) {
ColumnLayerData.SkipRender -> return
NormalRender -> return super.render(ctx, noDecorations)
NormalRender -> return super.renderLayer(ctx, data, layer)
ColumnLayerData.ResolveError, null -> {
return super.render(ctx, noDecorations)
return super.renderLayer(ctx, data, layer)
}
}
// if log axis is not defined and "Default to vertical" config option is not set, render normally
if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) {
return super.render(ctx, noDecorations)
return super.renderLayer(ctx, data, layer)
}
ctx.vertexLighter = ColumnLighting

View File

@@ -73,7 +73,7 @@ sealed class ColumnLayerData {
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData>() {
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
@@ -84,7 +84,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: IBlockDisplayReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
allNeighborOffsets.forEach { offset -> remove(world, pos + offset) }
}
override fun calculate(ctx: BlockCtx): ColumnLayerData {

View File

@@ -1,6 +1,8 @@
package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import mods.betterfoliage.util.Double3
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.SpriteTexturedParticle
@@ -92,10 +94,16 @@ abstract class AbstractParticle(world: ClientWorld, x: Double, y: Double, z: Dou
renderVertex(coords[3], sprite.u0, sprite.v1)
}
fun setColor(color: Int) {
bCol = (color and 255) / 256.0f
gCol = ((color shr 8) and 255) / 256.0f
rCol = ((color shr 16) and 255) / 256.0f
fun setColor(color: Color) {
rCol = color.red / 256.0f
gCol = color.green / 256.0f
bCol = color.blue / 256.0f
}
/**
* Set particle color to the "stronger" of the given colors, determined by higher color saturation
*/
fun setColor(color1: Color, color2: Color) =
setColor(if (color1.asHSB.saturation > color2.asHSB.saturation) color1 else color2)
}

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Color
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax
@@ -44,7 +45,7 @@ class FallingLeafParticle(
yd = -Config.fallingLeaves.speed
quadSize = Config.fallingLeaves.size.toFloat() * 0.1f
setColor(leaf.overrideColor?.asInt ?: blockColor)
if (leaf.tintIndex == -1) setColor(leaf.avgColor) else setColor(leaf.avgColor, Color(blockColor))
sprite = LeafParticleRegistry[leaf.leafType][randomI(max = 1024)]
}

View File

@@ -10,8 +10,10 @@ import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.stripEnd
import mods.betterfoliage.util.stripStart
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
@@ -23,20 +25,23 @@ interface LeafBlockModel {
interface LeafParticleKey {
val leafType: String
val overrideColor: Color?
val tintIndex: Int
val avgColor: Color
}
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
val typeMappings = TextureMatcher()
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
val allTypes = mutableSetOf<String>()
val particles = hashMapOf<String, SpriteSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
override fun onReloadStarted() {
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
detailLogger.log(INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
override fun onReloadStarted(resourceManager: IResourceManager) {
allTypes.clear()
resourceManager.listResources("textures/particle") { it.startsWith("falling_leaf_") }
.filter { it.namespace == BetterFoliageMod.MOD_ID }
.map { it.stripStart("textures/particle/falling_leaf_").stripEnd(".png") }
.map { it.path.substringBefore("_", "") }
.forEach { leafType -> if (!leafType.isEmpty()) allTypes.add(leafType) }
}
@SubscribeEvent
@@ -56,7 +61,7 @@ object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
@SubscribeEvent
fun handlePostStitch(event: TextureStitchEvent.Post) {
if (event.map.location() == Atlas.PARTICLES.resourceId) {
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
allTypes.forEach { leafType ->
val sprites = (0 until 16).map { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
}

View File

@@ -0,0 +1,42 @@
package mods.betterfoliage.render.pipeline
import mods.betterfoliage.model.SpecialRenderData
import mods.betterfoliage.RenderTypeLookup
import net.minecraft.block.Block
import net.minecraft.client.renderer.RenderType
import java.util.function.Predicate
object Layers {
val tufts = RenderType.cutout()
val connectedGrass = RenderType.solid()
val connectedDirt = RenderType.cutoutMipped()
val coral = RenderType.cutoutMipped()
}
val defaultLayerBehaviour = Predicate<RenderType> { layer -> layer == RenderType.solid() }
class WrappedLayerPredicate(val original: Predicate<RenderType>, val func: (RenderType, Predicate<RenderType>) -> Boolean) : Predicate<RenderType> {
override fun test(layer: RenderType) = func(layer, original)
}
/**
* Extension method to access the canRenderInLayer() predicate in [RenderTypeLookup]
*/
var Block.layerPredicate : Predicate<RenderType>?
get() = RenderTypeLookup.blockRenderChecks.getStatic()[delegate]
set(value) {
RenderTypeLookup.blockRenderChecks.getStatic()[delegate] = value!!
}
/**
* Add a wrapper to the block's canRenderInLayer() predicate to enable dynamic multi-layer rendering.
* If the render data for the block implements [SpecialRenderData], the layers it enables will be
* rendered _in addition to_ the block's normal layers.
*/
fun Block.extendLayers() {
val original = layerPredicate ?: defaultLayerBehaviour
if (original !is WrappedLayerPredicate) layerPredicate = WrappedLayerPredicate(original) { layer, original ->
original.test(layer) ||
(RenderCtxBase.specialRenderData.get() as? SpecialRenderData)?.canRenderInLayer(layer) ?: false
}
}

View File

@@ -12,6 +12,8 @@ import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.plus
import net.minecraft.block.Block
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
@@ -25,17 +27,19 @@ import java.util.Random
* push-based partial rendering pipeline for [SpecialRenderModel] instances.
*/
abstract class RenderCtxBase(
world: IBlockDisplayReader,
pos: BlockPos,
blockCtx: BlockCtx,
val matrixStack: MatrixStack,
var checkSides: Boolean,
val random: Random,
val modelData: IModelData
) : BlockCtx by BasicBlockCtx(world, pos) {
abstract fun renderQuad(quad: HalfBakedQuad)
val modelData: IModelData,
) : BlockCtx by blockCtx {
var hasRendered = false
var modelRenderData: Any? = null
inline fun <reified T> withRenderData(renderFunc: (T)->Boolean) = (modelRenderData as? T?).let {
if (it == null) false else renderFunc(it)
}
val blockModelShapes = Minecraft.getInstance().blockRenderer.blockModelShaper
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
protected val lightingData = RenderCtxBase.lightingData.get().apply {
@@ -43,6 +47,8 @@ abstract class RenderCtxBase(
blockColors = Minecraft.getInstance().blockColors
}
abstract fun renderQuad(quad: HalfBakedQuad)
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldRenderFace(state, world, pos, this)
fun renderQuads(quads: Iterable<HalfBakedQuad>) {
@@ -61,6 +67,17 @@ abstract class RenderCtxBase(
}
companion object {
@JvmStatic
fun reset(chunkRenderCache: IBlockDisplayReader, blockRendererDispatcher: BlockRendererDispatcher, pos: BlockPos, random: Random) {
// prepare render data
val blockCtx = BasicBlockCtx(chunkRenderCache, pos)
val model = blockRendererDispatcher.getBlockModel(blockCtx.state)
random.setSeed(blockCtx.seed)
val data = if (model is SpecialRenderModel) model.prepare(blockCtx, random) else Unit
specialRenderData.set(data)
}
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
val specialRenderData = ThreadLocal<Any?>()
}
}

View File

@@ -1,27 +1,31 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.lighting.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import mods.betterfoliage.util.getWithDefault
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.LightTexture
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.ForgeHooksClient
import net.minecraftforge.client.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
world: IBlockDisplayReader,
pos: BlockPos,
blockCtx: BlockCtx,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
modelData: IModelData,
) : RenderCtxBase(blockCtx, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
override fun renderQuad(quad: HalfBakedQuad) {
// set Forge lighter AO calculator to us
@@ -38,7 +42,15 @@ class RenderCtxForge(
}
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
override fun updateVertexColor(
normal: FloatArray,
color: FloatArray,
x: Float,
y: Float,
z: Float,
tint: Float,
multiplier: Int
) {
color[0] = lightingData.tint[0] * lightingData.colorMultiplier[vIdx]
color[1] = lightingData.tint[1] * lightingData.colorMultiplier[vIdx]
color[2] = lightingData.tint[2] * lightingData.colorMultiplier[vIdx]
@@ -55,17 +67,21 @@ class RenderCtxForge(
pos: BlockPos,
matrixStack: MatrixStack,
checkSides: Boolean,
rand: Random, seed: Long,
random: Random, seed: Long,
modelData: IModelData
): Boolean {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
rand.setSeed(seed)
lighter.updateBlockInfo()
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
val blockCtx = BasicBlockCtx(world, pos)
val ctx = RenderCtxForge(blockCtx, lighter, matrixStack, checkSides, random, modelData).apply {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
lighter.updateBlockInfo()
}
// render layer
return ctx.let {
(lighter as ForgeVertexLighterAccess).vertexLighter = it
model.render(it, false)
model.renderLayer(it, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
lighter.resetBlockInfo()
it.hasRendered
}

View File

@@ -2,28 +2,31 @@ package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.util.getWithDefault
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockDisplayReader
import net.minecraftforge.client.MinecraftForgeClient
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
world: IBlockDisplayReader,
pos: BlockPos,
blockCtx: BlockCtx,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val seed: Long,
val randomSeed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
): RenderCtxBase(blockCtx, matrixStack, checkSides, random, modelData) {
override fun renderQuad(quad: HalfBakedQuad) {
vertexLighter.updateLightmapAndColor(quad, lightingData)
@@ -47,20 +50,16 @@ class RenderCtxVanilla(
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
rand: Long,
seed: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
random.setSeed(rand)
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
lightingData.apply {
}
model.render(ctx, false)
val blockCtx = BasicBlockCtx(world, pos)
// init context if missing (this is the first render layer)
val ctx = RenderCtxVanilla(renderer, blockCtx, buffer, combinedOverlay, matrixStack, checkSides, random, seed, modelData, smooth)
model.renderLayer(ctx, specialRenderData.get()!!, MinecraftForgeClient.getRenderLayer())
return ctx.hasRendered
}
}
}

View File

@@ -19,9 +19,9 @@ interface VeryEarlyReloadListener : IFutureReloadListener {
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted()
onReloadStarted(resourceManager)
return stage.wait(null)
}
fun onReloadStarted() {}
fun onReloadStarted(resourceManager: IResourceManager) {}
}

View File

@@ -1,9 +1,11 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
@@ -55,6 +57,9 @@ data class ModelDiscoveryContext(
if (addToStateKeys) BetterFoliage.blockTypes.stateKeys[blockState] = key
logger.log(INFO, "Adding model replacement $modelLocation -> $key")
}
fun <T: IUnbakedModel> loadHierarchy(model: T) = model.apply {
getMaterials(this@ModelDiscoveryContext::getUnbaked, mutableSetOf())
}
}
data class ModelBakingContext(
@@ -68,7 +73,7 @@ data class ModelBakingContext(
fun getBaked() = bakery.getBakedModel(location, transform, spriteGetter)
}
object BakeWrapperManager : Invalidator, HasLogger() {
class BakeWrapperManager : Invalidator, HasLogger() {
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
@@ -79,6 +84,7 @@ object BakeWrapperManager : Invalidator, HasLogger() {
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
val startTime = System.currentTimeMillis()
invalidate()
BetterFoliage.blockConfig.readConfig(resourceManager)
BetterFoliage.blockTypes = BlockTypeCache()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")

View File

@@ -31,6 +31,10 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
}
open fun processModel(ctx: ModelDiscoveryContext) {
processContainerModel(ctx)
}
fun processContainerModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
// built-in support for container models

View File

@@ -0,0 +1,97 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.match.MAnything
import mods.betterfoliage.config.match.MListAll
import mods.betterfoliage.config.match.MListAny
import mods.betterfoliage.config.match.MNegated
import mods.betterfoliage.config.match.MValue
import mods.betterfoliage.config.match.MatchRules
import mods.betterfoliage.util.HasLogger
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
abstract class ParametrizedModelDiscovery : HasLogger() {
abstract fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>)
fun Map<String, String>.location(key: String): ResourceLocation? {
val result = get(key)?.let { ResourceLocation(it) }
if (result == null) detailLogger.log(Level.WARN, "Cannot find texture parameter \"$key\"")
return result
}
fun Map<String, String>.int(key: String): Int? {
val result = get(key)?.toInt()
if (result == null) detailLogger.log(Level.WARN, "Cannot find integer parameter \"$key\"")
return result
}
}
class RuleProcessingContext(
val discovery: ModelDiscoveryContext
) {
val params = mutableMapOf("type" to "none")
}
class RuleBasedDiscovery : AbstractModelDiscovery() {
val discoverers = mutableMapOf<String, ParametrizedModelDiscovery>()
override fun processModel(ctx: ModelDiscoveryContext) = when(ctx.getUnbaked()) {
is VariantList -> processContainerModel(ctx)
is BlockModel -> processBlockModel(ctx)
else -> Unit
}
fun processBlockModel(ctx: ModelDiscoveryContext) {
val ruleCtx = RuleProcessingContext(ctx)
val rulesToCheck = BetterFoliage.blockConfig.rules.toMutableList()
val ruleResults = mutableListOf<MListAll>()
var previousSize = 0
// stop processing if nothing changes anymore
while (rulesToCheck.size != previousSize) {
previousSize = rulesToCheck.size
val iterator = rulesToCheck.listIterator()
while (iterator.hasNext()) iterator.next().let { rule ->
// process single rule
MatchRules.visitRoot(ruleCtx, rule).let { result ->
ruleResults.add(result)
// remove rule from active list if:
// - rule succeeded (all directives returned success)
// - rule is immutable (result will always be the same)
if (result.value || result.immutable) iterator.remove()
}
}
}
// log result of rule processing
if (ruleResults.any { it.value }) {
detailLogger.log(Level.INFO, "================================")
detailLogger.log(Level.INFO, "block state: ${ctx.blockState}")
detailLogger.log(Level.INFO, "block class: ${ctx.blockState.block::class.java.name}")
detailLogger.log(Level.INFO, "model : ${ctx.modelLocation}")
detailLogger.log(Level.INFO, "--------------------------------")
ruleResults.forEach { logResult(it) }
}
discoverers[ruleCtx.params["type"]]?.processModel(ctx, ruleCtx.params)
}
fun logResult(match: MAnything<Boolean>) {
when(match) {
is MListAll -> if (match.list.any { it.value }) {
var seenInvariantSuccess = false
match.list.forEach { item ->
if (item.immutable && item.value) seenInvariantSuccess = true
if (seenInvariantSuccess) logResult(item)
}
}
is MListAny -> if (match.value) match.list.first { it.value }.let { logResult(it) }
else match.list.forEach { logResult(it) }
is MNegated -> logResult(match.inner)
is MValue<Boolean> -> detailLogger.log(Level.INFO, "[${match.configSource}] ${match.description}")
}
}
}

View File

@@ -62,3 +62,6 @@ open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFac
}
}
}
fun <V> Invalidator.lazy(valueFactory: ()->V) = LazyInvalidatable(this, valueFactory)
fun <K, V> Invalidator.lazyMap(valueFactory: (K)->V) = LazyMapInvalidatable(this, valueFactory)

View File

@@ -65,6 +65,9 @@ inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
/** Return a random element from the array using the provided random generator */
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)
inline fun Random.idx(array: Array<*>) = nextInt(Int.MAX_VALUE) % array.size
inline fun Random.idxOrNull(array: Array<*>, predicate: ()->Boolean) = if (predicate()) idx(array) else null
fun <T> Iterable<T>.toImmutableList() = ImmutableList.builder<T>().let { builder ->
forEach { builder.add(it) }
builder.build()

View File

@@ -2,6 +2,9 @@
package mods.betterfoliage.util
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.HSB
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
@@ -20,6 +23,8 @@ inline fun String.stripEnd(str: String, ignoreCase: Boolean = true) = if (endsWi
inline fun ResourceLocation.stripStart(str: String) = ResourceLocation(namespace, path.stripStart(str))
inline fun ResourceLocation.stripEnd(str: String) = ResourceLocation(namespace, path.stripEnd(str))
val String.quoted: String get() = "\"$this\""
/**
* Property-level delegate backed by a [ThreadLocal].
*
@@ -31,6 +36,11 @@ class ThreadLocalDelegate<T>(init: () -> T) {
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { tlVal.set(value) }
}
fun <T> ThreadLocal<T?>.getWithDefault(factory: ()->T): T {
get()?.let { return it }
return factory().apply { set(this) }
}
/** Call the supplied lambda and return its result, or the given default value if an exception is thrown. */
fun <T> tryDefault(default: T, work: ()->T) = try { work() } catch (e: Throwable) { default }
@@ -56,6 +66,12 @@ abstract class HasLogger {
val detailLogger = BetterFoliageMod.detailLogger(this)
}
fun Logger.logTextureColor(level: Level, description: String, avgColor: HSB) {
val rgb = avgColor.asColor
log(level, "$description average color RGB[${rgb.red},${rgb.green},${rgb.blue}], HSB[${avgColor.hue},${avgColor.saturation},${avgColor.brightness}]")
}
fun getBlockModel(state: BlockState) = Minecraft.getInstance().blockRenderer.blockModelShaper.getBlockModel(state)
/**
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.

View File

@@ -16,6 +16,8 @@ import java.io.IOException
import javax.imageio.ImageIO
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin
enum class Atlas(val resourceId: ResourceLocation) {
@@ -82,6 +84,36 @@ val TextureAtlasSprite.averageColor: HSB get() {
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
}
val ResourceLocation.averageHSB: HSB get() = resourceManager.loadSprite(this).let { image ->
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0 until image.width)
for (y in 0 until image.width) {
val pixel = image[x, y]
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColorBGRA(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
}
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
}
fun HSB.brighten(multiplier: Float = 1.5f, floor: Float = 0.1f, ceiling: Float = 0.9f) =
copy(brightness = (brightness * multiplier).coerceAtMost(max(ceiling, brightness)).coerceAtLeast(min(floor, brightness)))
fun HSB.saturate(multiplier: Float = 1.5f, floor: Float = 0.1f, ceiling: Float = 0.9f) =
copy(saturation = (saturation * multiplier).coerceAtMost(max(ceiling, saturation)).coerceAtLeast(min(floor, saturation)))
/** Weighted blend of 2 packed RGB colors */
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)
@@ -102,4 +134,4 @@ fun logColorOverride(logger: Logger, threshold: Double, hsb: HSB) {
}
fun HSB.colorOverride(threshold: Double) =
if (saturation < threshold) null else copy(brightness = (brightness * 2.0f).coerceAtMost(0.9f)).asColor.let { Color(it) }
if (saturation < threshold) null else copy(brightness = (brightness * 2.0f).coerceAtMost(0.9f)).asInt.let { Color(it) }

View File

@@ -0,0 +1,11 @@
match isParam("type", "leaf")
model.extends("biomesoplenty:block/leaves_overlay")
setParam("texture", model.texture("leaves"))
setParam("tint", model.tint("leaves"))
end
match isParam("type", "grass")
model.extends("biomesoplenty:block/origin_grass_block")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end

View File

@@ -0,0 +1,48 @@
// A lot of BYG leaf models are very sloppily made, extending "block/cube" or even "block/block"
// These rules are meh, but there's no better way to do it, there's no method to the madness here
// snowy leaves
match isParam("type", "leaf") block.name.contains("byg:leaves") model.contains("snowy")
setParam("texture", model.texture("up")) setParam("tint", model.tint("up")) end
// list of leaves where texture is "up"
match model.matches(
"byg:block/aspen_leaves",
"byg:block/baobab_leaves",
"byg:block/blue_enchanted_leaves"
) setParam("texture", model.texture("up")) setParam("tint", model.tint("up"))
end
// list of leaves where texture is "top"
match model.matches(
"byg:block/flowering_orchard_leaves",
"byg:block/joshua_leaves",
"byg:block/mahogany_leaves",
"byg:block/maple_leaves",
"byg:block/orchard_leaves",
"byg:block/rainbow_eucalyptus_leaves",
"byg:block/willow_leaves"
) setParam("texture", model.texture("top")) setParam("tint", model.tint("top"))
end
// ripe leaves (tint comes from overlay)
match model.matches(
"byg:block/ripe_joshua_leaves",
"byg:block/ripe_orchard_leaves"
) setParam("texture", model.texture("top")) setParam("tint", model.tint("overlay"))
end
//
// other blocks
//
match block.name.matches("byg:meadow_dirt") setParam("type", "dirt") end
match block.name.matches("byg:overgrown_crimson_blackstone") setParam("type", "mycelium") end
match model.matches("byg:block/meadow_grass_block", "byg:block/overgrown_stone", "byg:block/overgrown_dacite", "byg:block/overgrown_netherrack")
setParam("type", "grass")
setParam("texture", model.texture("top")) setParam("tint", model.tint("top"))
end
match block.name.matches("byg:overgrown_stone", "byg:overgrown_dacite", "byg:overgrown_netherrack", "byg:podzol_dacite")
setParam("no-connect", "true")
end

View File

@@ -0,0 +1 @@
match block.class.extends(classOf("desolation:charred_branches")) setParam("type", "leaf") end

View File

@@ -0,0 +1 @@
match block.class.extends(classOf("environmental:blue_wisteria_leaves")) setParam("type", "leaf") end

View File

@@ -0,0 +1,9 @@
match isParam("type", "leaf")
block.name.contains("spruce", "fir")
setParam("particle", "spruce")
end
match isParam("type", "leaf")
block.name.contains("jungle", "palm")
setParam("particle", "jungle")
end

View File

@@ -0,0 +1,58 @@
// Leaves
match block.class.extends(classOf("minecraft:oak_leaves")) setParam("type", "leaf") end
match isParam("type", "leaf")
model.extends("minecraft:block/leaves", "minecraft:block/cube_all")
setParam("texture", model.texture("all"))
setParam("tint", model.tint("all"))
end
// Podzol
match block.name.matches("minecraft:podzol") setParam("type", "grass") end
// Grass
match block.class.extends(classOf("minecraft:grass_block")) setParam("type", "grass") end
match isParam("type", "grass")
model.extends("minecraft:block/grass_block", "minecraft:block/cube_bottom_top")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end
// Mycelium & Nylium
match block.name.matches("minecraft:mycelium", "minecraft:crimson_nylium", "minecraft:warped_nylium") setParam("type", "mycelium") end
match isParam("type", "mycelium")
model.extends("minecraft:block/cube_bottom_top")
setParam("texture", model.texture("top"))
setParam("tint", model.tint("top"))
end
// Dirt
match block.name.matches("minecraft:dirt") setParam("type", "dirt") end
// Wood Log
match block.class.extends(classOf("minecraft:oak_log")) setParam("type", "round-log") end
match isParam("type", "round-log")
model.extends("minecraft:block/cube_column", "minecraft:block/cube_column_horizontal")
setParam("texture-side", model.texture("side"))
setParam("texture-end", model.texture("end"))
end
match isParam("type", "round-log")
model.extends("minecraft:block/cube_all")
setParam("texture-side", model.texture("all"))
setParam("texture-end", model.texture("all"))
end
// Sand & Dirt
match block.name.matches("minecraft:sand", "minecraft:red_sand") setParam("type", "sand") end
// Cactus, Lilypad, Netherrack
match block.name.matches("minecraft:cactus") setParam("type", "cactus") end
match block.name.matches("minecraft:lilypad") setParam("type", "lilypad") end
match block.name.matches("minecraft:netherrack") setParam("type", "netherrack") end
// Crops
match block.class.extends(classOf("minecraft:wheat")) setParam("type", "crop") end

View File

@@ -1,15 +0,0 @@
// Vanilla
net.minecraft.block.TallGrassBlock
net.minecraft.block.CropsBlock
-net.minecraft.block.ReedBlock
-net.minecraft.block.DoublePlantBlock
-net.minecraft.block.CarrotBlock
-net.minecraft.block.PotatoBlock
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPFlower
biomesoplenty.common.block.BlockBOPTurnip
biomesoplenty.common.block.BlockBOPPlant
// Tinkers' Construct
tconstruct.blocks.slime.SlimeTallGrass

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.DirtBlock

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.GrassBlock

View File

@@ -1,3 +0,0 @@
// Vanilla
block/grass_block,top
block/cube_bottom_top,top

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.LeavesBlock

View File

@@ -1,4 +0,0 @@
minecraft:block/leaves,all
minecraft:block/cube_all,all
biomesoplenty:block/leaves_overlay,leaves

View File

@@ -1,8 +0,0 @@
// Vanilla
net.minecraft.block.BlockLilyPad
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLilypad
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Vanilla.BlockCustomLilyPad

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.RotatedPillarBlock

View File

@@ -1,4 +0,0 @@
// Vanilla
block/cube_column,side,end
block/cube_column_horizontal,side,end
block/cube_all,all,all

View File

@@ -1,2 +0,0 @@
// Vanilla
net.minecraft.block.MyceliumBlock

View File

@@ -7,7 +7,8 @@
"mixins": [
],
"client": [
"MixinOptifineBlockUtils"
"MixinOptifineBlockUtils",
"MixinOptifineChunkRendererDispatcher"
],
"server": [
],

View File

@@ -7,6 +7,7 @@
"mixins": [
],
"client": [
"MixinVanillaChunkRendererDispatcher"
],
"server": [
],