[WIP] Cactus, netherrack, round logs work

+ lots more cleanup
+ Optifine x-ray fix
This commit is contained in:
octarine-noise
2021-05-13 00:44:45 +02:00
parent dbc421c18e
commit 9899816029
40 changed files with 1059 additions and 401 deletions

View File

@@ -9,7 +9,9 @@ import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Mixin overriding the {@link VoxelShape} used for the neighbor block in {@link Block}.shouldSideBeRendered().
@@ -22,6 +24,7 @@ public class MixinBlock {
private static final String shouldSideBeRendered = "Lnet/minecraft/block/Block;shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Z";
private static final String getVoxelShape = "Lnet/minecraft/block/BlockState;func_215702_a(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
private static final String isOpaqueCube = "Lnet/minecraft/block/Block;isOpaqueCube(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;)Z";
@Redirect(method = shouldSideBeRendered, at = @At(value = "INVOKE", target = getFaceOcclusionShape, ordinal = 1))
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {

View File

@@ -9,11 +9,16 @@ import net.minecraft.world.IBlockReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Pseudo
@Mixin(targets = "net.optifine.util.BlockUtils")
public class MixinOptifineBlockUtils {
private static final String shouldSideBeRendered = "shouldSideBeRendered(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;)Z";
private static final String shouldSideBeRenderedCached = "shouldSideBeRenderedCached(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;Lnet/optifine/render/RenderEnv;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;)Z";
private static final String getFaceOcclusionShape = "Lnet/minecraft/block/BlockState;getFaceOcclusionShape(Lnet/minecraft/world/IBlockReader;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/Direction;)Lnet/minecraft/util/math/shapes/VoxelShape;";
@@ -22,4 +27,12 @@ public class MixinOptifineBlockUtils {
private static VoxelShape getVoxelShapeOverride(BlockState state, IBlockReader reader, BlockPos pos, Direction dir) {
return Hooks.getVoxelShapeOverride(state, reader, pos, dir);
}
@SuppressWarnings("UnresolvedMixinReference")
@Inject(method = shouldSideBeRendered, at = @At(value = "HEAD"), cancellable = true)
private static void shouldForceSideRender(BlockState state, IBlockReader reader, BlockPos pos, Direction face, @Coerce Object renderEnv, CallbackInfoReturnable<Boolean> cir) {
if (Hooks.shouldForceSideRenderOF(state, reader, pos, face)) {
cir.setReturnValue(true);
}
}
}

View File

@@ -1,30 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.Hooks;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.RenderType;
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.Redirect;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(targets = {"net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask"})
public class MixinOptifineChunkRender {
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 invokeReflector = "Lnet/optifine/reflect/Reflector;callBoolean(Ljava/lang/Object;Lnet/optifine/reflect/ReflectorMethod;[Ljava/lang/Object;)Z";
private static final String forgeBlockCanRender = "Lnet/minecraft/client/renderer/chunk/ChunkRender;FORGE_BLOCK_CAN_RENDER_IN_LAYER:Z";
// @Redirect(
// method = compile,
// at = @At(value = "INVOKE", target = invokeReflector),
// slice = @Slice(
// from = @At(value = "FIELD", target = forgeBlockCanRender)
// )
// )
// @SuppressWarnings("UnresolvedMixinReference")
// boolean canRenderInLayer(Object state, @Coerce Object reflector, Object[] layer) {
// return Hooks.canRenderInLayerOverride((BlockState) state, (RenderType) layer[0]);
// }
}

View File

@@ -4,8 +4,10 @@ import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.block.vanilla.RoundLogOverlayLayer
import mods.betterfoliage.render.block.vanilla.StandardCactusDiscovery
import mods.betterfoliage.render.block.vanilla.StandardCactusModel
import mods.betterfoliage.render.block.vanilla.StandardDirtDiscovery
import mods.betterfoliage.render.block.vanilla.StandardDirtKey
import mods.betterfoliage.render.block.vanilla.StandardDirtModel
import mods.betterfoliage.render.block.vanilla.StandardGrassDiscovery
import mods.betterfoliage.render.block.vanilla.StandardGrassModel
@@ -13,43 +15,57 @@ 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.StandardLogDiscovery
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.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.LeafWindTracker
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
import mods.betterfoliage.resource.generated.GeneratedTexturePack
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.render.particle.LeafParticleRegistry
import mods.betterfoliage.render.particle.RisingSoulParticle
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraftforge.common.ForgeConfig
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
* except for the call hooks.
*/
object Client {
/** Resource pack holding generated assets */
val generatedPack = GeneratedTexturePack("bf_gen", "Better Foliage generated assets")
var blockTypes = BlockTypeCache()
val suppressRenderErrors = mutableSetOf<BlockState>()
/** List of recognized [BlockState]s */
var blockTypes = BlockTypeCache()
fun init() {
// discoverers
BetterFoliageMod.bus.register(BakeWrapperManager)
BetterFoliageMod.bus.register(LeafParticleRegistry)
BetterFoliageMod.bus.register(this)
(Minecraft.getInstance().resourceManager as IReloadableResourceManager).addReloadListener(LeafParticleRegistry)
ChunkOverlayManager.layers.add(RoundLogOverlayLayer)
listOf(
StandardLeafDiscovery,
StandardGrassDiscovery,
StandardDirtDiscovery,
StandardMyceliumDiscovery,
StandardSandDiscovery,
StandardLilypadDiscovery
StandardLilypadDiscovery,
StandardCactusDiscovery,
StandardNetherrackDiscovery,
StandardLogDiscovery
).forEach {
BakeWrapperManager.discoverers.add(it)
}
@@ -68,7 +84,11 @@ object Client {
StandardDirtModel.Companion,
StandardMyceliumModel.Companion,
StandardSandModel.Companion,
StandardLilypadModel.Companion
StandardLilypadModel.Companion,
StandardCactusModel.Companion,
StandardNetherrackModel.Companion,
StandardRoundLogModel.Companion,
RisingSoulParticle.Companion
)
// init mod integrations
@@ -77,5 +97,11 @@ object Client {
OptifineCustomColors
)
}
@SubscribeEvent
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
blockTypes = BlockTypeCache()
}
}

View File

@@ -1,44 +1,47 @@
@file:JvmName("Hooks")
package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.getActualRenderModel
import mods.betterfoliage.render.block.vanilla.RoundLogKey
import mods.betterfoliage.render.particle.FallingLeafParticle
import mods.betterfoliage.texture.LeafBlockModel
import mods.betterfoliage.render.particle.LeafBlockModel
import mods.betterfoliage.render.particle.RisingSoulParticle
import mods.betterfoliage.util.plus
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.Minecraft
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.shapes.VoxelShape
import net.minecraft.util.math.shapes.VoxelShapes
import net.minecraft.world.IBlockReader
import net.minecraft.world.World
import java.util.Random
fun getAmbientOcclusionLightValueOverride(original: Float, state: BlockState): Float {
// if (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block)) return Config.roundLogs.dimming.toFloat();
return original
}
fun getUseNeighborBrightnessOverride(original: Boolean, state: BlockState): Boolean {
// return original || (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block));
if (Config.enabled && Config.roundLogs.enabled && Client.blockTypes.stateKeys[state] is RoundLogKey)
return Config.roundLogs.dimming.toFloat()
return original
}
fun onClientBlockChanged(worldClient: ClientWorld, pos: BlockPos, oldState: BlockState, newState: BlockState, flags: Int) {
// ChunkOverlayManager.onBlockChange(worldClient, pos)
ChunkOverlayManager.onBlockChange(worldClient, pos)
}
fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: BlockPos, random: Random) {
// if (Config.enabled &&
// Config.risingSoul.enabled &&
// state.block == Blocks.SOUL_SAND &&
// world.isAirBlock(pos + up1) &&
// Math.random() < Config.risingSoul.chance) {
// EntityRisingSoulFX(world, pos).addIfValid()
// }
if (Config.enabled &&
Config.risingSoul.enabled &&
state.block == Blocks.SOUL_SAND &&
world.isAirBlock(pos.offset(UP)) &&
Math.random() < Config.risingSoul.chance) {
RisingSoulParticle(world, pos).addIfValid()
}
if (Config.enabled &&
Config.fallingLeaves.enabled &&
@@ -53,6 +56,10 @@ fun onRandomDisplayTick(block: Block, state: BlockState, world: World, pos: Bloc
}
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
// if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
if (Config.enabled && Config.roundLogs.enabled && Client.blockTypes.stateKeys[state] is RoundLogKey)
return VoxelShapes.empty()
return state.getFaceOcclusionShape(reader, pos, dir)
}
fun shouldForceSideRenderOF(state: BlockState, world: IBlockReader, pos: BlockPos, face: Direction) =
world.getBlockState(pos.offset(face)).let { neighbor -> Client.blockTypes.stateKeys[neighbor] is RoundLogKey }

View File

@@ -27,8 +27,11 @@ interface BlockCtx {
fun offset(offset: Int3): BlockCtx
val state: BlockState get() = world.getBlockState(pos)
fun state(dir: Direction) = world.getBlockState(pos + dir.offset)
fun state(offset: Int3) = world.getBlockState(pos + offset)
fun state(dir: Direction) = state(dir.offset)
fun isAir(offset: Int3) = (pos + offset).let { world.getBlockState(it).isAir(world, it) }
fun isAir(dir: Direction) = isAir(dir.offset)
val biome: Biome? get() =
(world as? IWorldReader)?.getBiome(pos) ?:

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliage
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
@@ -67,7 +68,7 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_I
object cactus : ConfigCategory(){
val enabled by featureEnable()
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
val size by double(min=1.0, max=2.0, default=1.3).lang("size")
val sizeVariation by double(max=0.5, default=0.1)
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
}
@@ -117,8 +118,8 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_I
object netherrack : ConfigCategory(){
val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=0.8).lang("heightMax")
val heightMin by double(min=0.1, max=1.5, default=0.2).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=0.5).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
}
@@ -147,6 +148,7 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_I
val trailLength by int(min=2, max=128, default=48)
val trailDensity by int(min=1, max=16, default=3)
}
}
object BlockConfig {
@@ -174,4 +176,4 @@ object BlockConfig {
is ModelTextureListConfiguration -> it.readDefaults()
} }
}
}
}

View File

@@ -1,24 +1,19 @@
package mods.betterfoliage.model
import mods.betterfoliage.render.pipeline.RenderCtxBase
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.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.client.renderer.vertex.VertexFormatElement
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
import java.util.Random
import java.util.function.Function
/**
* Hybrid baked quad implementation, carrying both baked and unbaked information.
@@ -47,23 +42,16 @@ open class HalfBakedSpecialWrapper(val baseModel: SpecialRenderModel): IBakedMod
}
abstract class HalfBakedWrapperKey : ModelBakingKey, HasLogger() {
override fun bake(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? {
val baseModel = super.bake(location, unbaked, transform, bakery, spriteGetter)
override fun bake(ctx: ModelBakingContext): IBakedModel? {
val baseModel = super.bake(ctx)
val halfBaked = when(baseModel) {
is SimpleBakedModel -> HalfBakedSimpleModelWrapper(baseModel)
else -> null
}
return if (halfBaked == null) baseModel else replace(halfBaked)
return if (halfBaked == null) baseModel else bake(ctx, halfBaked)
}
abstract fun replace(wrapped: SpecialRenderModel): SpecialRenderModel
abstract fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel): SpecialRenderModel
}
fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
@@ -106,6 +94,8 @@ fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
HalfBakedQuad(quad, builder.build())
}
fun Array<List<Quad>>.bake(applyDiffuseLighting: Boolean) = mapArray { it.bake(applyDiffuseLighting) }
fun BakedQuad.unbake(): HalfBakedQuad {
val size = DefaultVertexFormats.BLOCK.integerSize
val verts = Array(4) { vIdx ->

View File

@@ -9,6 +9,7 @@ import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.rotate
import mods.betterfoliage.util.times
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.texture.NativeImage
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction
import java.lang.Math.max
@@ -83,6 +84,7 @@ data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
/** Red is assumed to be LSB, see [NativeImage.PixelFormat.RGBA] */
fun fromColorRGBA(color: Int): HSB {
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])

View File

@@ -73,8 +73,8 @@ fun fullCubeTextured(
.bake(true)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
return Array(num) { idx ->
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): List<List<Quad>> {
return (0 until num).map { idx ->
listOf(
Quad.verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41),
Quad.verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
@@ -93,7 +93,7 @@ fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, tintIndex: In
.bake(false)
fun crossModelsTextured(
leafBase: Array<List<Quad>>,
leafBase: Iterable<List<Quad>>,
tintIndex: Int,
scrambleUV: Boolean,
spriteGetter: (Int) -> ResourceLocation
@@ -101,8 +101,8 @@ fun crossModelsTextured(
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], tintIndex, scrambleUV)
}.toTypedArray()
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun List<List<Quad>>.buildTufts(applyDiffuseLighting: Boolean = false) =
fun Iterable<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun Iterable<List<Quad>>.buildTufts(applyDiffuseLighting: Boolean = false) =
map { it.withOpposites().bake(applyDiffuseLighting) }.toTypedArray()
fun List<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }
fun Iterable<List<Quad>>.transform(trans: Quad.(Int)-> Quad) = mapIndexed { idx, qList -> qList.map { it.trans(idx) } }

View File

@@ -1,66 +0,0 @@
package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.old.AbstractEntityFX
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import java.util.*
class EntityRisingSoulFX(world: World, pos: BlockPos) :
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
val particleTrail: Deque<Double3> = LinkedList<Double3>()
val initialPhase = rand.nextInt(64)
init {
motionY = 0.1
particleGravity = 0.0f
// sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = (initialPhase + age) % 64
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
if (!Config.enabled) setExpired()
}
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
// var alpha = Config.risingSoul.opacity.toFloat()
// if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
//
// renderParticleQuad(worldRenderer, partialTickTime,
// size = Config.risingSoul.headSize * 0.25,
// alpha = alpha
// )
//
// var scale = Config.risingSoul.trailSize * 0.25
// particleTrail.forEachPairIndexed { idx, current, previous ->
// scale *= Config.risingSoul.sizeDecay
// alpha *= Config.risingSoul.opacityDecay.toFloat()
// if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
// currentPos = current,
// prevPos = previous,
// size = scale,
// alpha = alpha,
// icon = RisingSoulTextures.trackIcon
// )
// }
}
}
//object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
// val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
// val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track"))
//}

View File

@@ -0,0 +1,93 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
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.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.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.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.ResourceLocation
object StandardCactusDiscovery : AbstractModelDiscovery() {
val CACTUS_BLOCKS = listOf(Blocks.CACTUS)
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is BlockModel && ctx.blockState.block in CACTUS_BLOCKS) {
Client.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardCactusKey)
}
super.processModel(ctx)
}
}
object StandardCactusKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardCactusModel(wrapped)
}
class StandardCactusModel(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
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])
}
companion object {
val cactusCrossSprite by SpriteDelegate(Atlas.BLOCKS) {
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus")
}
val cactusArmSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx")
}
val cactusArmModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = Config.cactus.let { tuftShapeSet(0.8, 0.8, 0.8, 0.2) }
val models = tuftModelSet(shapes, -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 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.name }
}
}
}

View File

@@ -15,7 +15,8 @@ import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Int3
@@ -23,40 +24,36 @@ import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.randomI
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
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.client.renderer.model.ModelBakery
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
object StandardDirtDiscovery : AbstractModelDiscovery() {
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in DIRT_BLOCKS) {
Client.blockTypes.dirt.add(state)
replacements[location] = StandardDirtKey
// RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutoutMipped())
return true
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.getSolid()
!Config.connectedGrass.enabled && !Config.algae.enabled && !Config.reed.enabled -> layer == RenderType.getSolid()
else -> layer == RenderType.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in DIRT_BLOCKS) {
Client.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardDirtKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
return super.processModel(bakery, state, location, sprites, replacements)
super.processModel(ctx)
}
}
object StandardDirtKey : HalfBakedWrapperKey() {
override fun replace(wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
}
class StandardDirtModel(

View File

@@ -4,8 +4,6 @@ import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HalfBakedSpecialWrapper
@@ -19,8 +17,11 @@ import mods.betterfoliage.model.tuftShapeSet
import mods.betterfoliage.render.lighting.LightingPreferredFace
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.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
@@ -28,7 +29,6 @@ import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isSnow
import mods.betterfoliage.util.randomI
import net.minecraft.block.BlockState
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
@@ -37,17 +37,9 @@ object StandardGrassDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
replacements[location] = StandardGrassKey(textureMatch[0], null)
Client.blockTypes.grass.add(state)
// RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
ctx.addReplacement(StandardGrassKey(textureMatch[0], null))
Client.blockTypes.grass.add(ctx.blockState)
}
}
@@ -57,7 +49,7 @@ data class StandardGrassKey(
) : HalfBakedWrapperKey() {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun replace(wrapped: SpecialRenderModel): SpecialRenderModel {
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)
@@ -81,7 +73,7 @@ class StandardGrassModel(
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isAir = ctx.isAir(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
@@ -93,7 +85,7 @@ class StandardGrassModel(
super.render(ctx, noDecorations)
}
if (Config.shortGrass.enabled(ctx.random) && !ctx.isNeighborSolid(UP)) {
if (Config.shortGrass.enabled(ctx.random) && (isAir || isSnowed)) {
ctx.vertexLighter = tuftLighting
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.renderQuads(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])

View File

@@ -4,8 +4,6 @@ import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.HSB
import mods.betterfoliage.model.HalfBakedSpecialWrapper
@@ -14,21 +12,22 @@ import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.crossModelsRaw
import mods.betterfoliage.model.crossModelsTextured
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.lighting.RoundLeafLightingPreferUp
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.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableBlockMatcher
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.resource.generated.GeneratedLeaf
import mods.betterfoliage.texture.LeafBlockModel
import mods.betterfoliage.texture.LeafParticleKey
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.isSnow
import net.minecraft.block.BlockState
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
@@ -39,21 +38,15 @@ object StandardLeafDiscovery : ConfigurableModelDiscovery() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
override fun processModel(ctx: ModelDiscoveryContext, textureMatch: List<ResourceLocation>) {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeaf(textureMatch[0], leafType)
.register(Client.generatedPack)
.apply { sprites.add(this) }
.apply { ctx.sprites.add(this) }
detailLogger.log(INFO, " particle $leafType")
replacements[location] = StandardLeafKey(generated, leafType, null)
return true
ctx.addReplacement(StandardLeafKey(generated, leafType, null))
}
}
@@ -76,7 +69,7 @@ data class StandardLeafKey(
) : HalfBakedWrapperKey(), LeafParticleKey {
val tintIndex: Int get() = if (overrideColor == null) 0 else -1
override fun replace(wrapped: SpecialRenderModel): SpecialRenderModel {
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)
@@ -97,9 +90,10 @@ class StandardLeafModel(
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = RoundLeafLighting }
ctx.renderQuads(leafNormal[ctx.random.nextInt(64)])
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[ctx.random.nextInt(64)])
ctx.vertexLighter = RoundLeafLightingPreferUp
val leafIdx = ctx.random.nextInt(64)
ctx.renderQuads(leafNormal[leafIdx])
if (ctx.state(UP).isSnow) ctx.renderQuads(leafSnowed[leafIdx])
}
companion object {

View File

@@ -14,7 +14,9 @@ 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.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
@@ -27,23 +29,17 @@ import net.minecraft.util.ResourceLocation
object StandardLilypadDiscovery : AbstractModelDiscovery() {
val LILYPAD_BLOCKS = listOf(Blocks.LILY_PAD)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in LILYPAD_BLOCKS) {
replacements[location] = StandardLilypadKey
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in LILYPAD_BLOCKS) {
ctx.addReplacement(StandardLilypadKey)
}
return super.processModel(bakery, state, location, sprites, replacements)
super.processModel(ctx)
}
}
object StandardLilypadKey : HalfBakedWrapperKey() {
override fun replace(wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardLilypadModel(wrapped)
}
class StandardLilypadModel(

View File

@@ -13,42 +13,33 @@ import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.randomI
import net.minecraft.block.BlockState
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.client.renderer.model.ModelBakery
import net.minecraft.util.Direction
import net.minecraft.util.ResourceLocation
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in MYCELIUM_BLOCKS) {
replacements[location] = StandardMyceliumKey
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
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.getCutout())
}
return super.processModel(bakery, state, location, sprites, replacements)
super.processModel(ctx)
}
}
object StandardMyceliumKey : HalfBakedWrapperKey() {
override fun replace(wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
}
class StandardMyceliumModel(

View File

@@ -0,0 +1,82 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.model.buildTufts
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.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.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.get
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
object StandardNetherrackDiscovery : AbstractModelDiscovery() {
val NETHERRACK_BLOCKS = listOf(Blocks.NETHERRACK)
fun canRenderInLayer(layer: RenderType) = when {
!Config.enabled -> layer == RenderType.getSolid()
!Config.netherrack.enabled -> layer == RenderType.getSolid()
else -> layer == RenderType.getCutoutMipped()
}
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in NETHERRACK_BLOCKS) {
Client.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardNetherrackKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, ::canRenderInLayer)
}
super.processModel(ctx)
}
}
object StandardNetherrackKey : HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardNetherrackModel(wrapped)
}
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
if (ctx.isAir(DOWN)) {
ctx.vertexLighter = tuftLighting
ctx.renderQuads(netherrackTuftModels[ctx.random])
}
}
companion object {
val netherrackTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx")
}
val netherrackTuftModels by LazyInvalidatable(BakeWrapperManager) {
val shapes = Config.netherrack.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
tuftModelSet(shapes, -1) { netherrackTuftSprites[randomI()] }
.transform { rotate(Rotation.fromUp[DOWN.ordinal]).rotateUV(2) }
.buildTufts()
}
}
}

View File

@@ -1,13 +1,98 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.Client
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.resource.discovery.ModelTextureList
import mods.betterfoliage.model.HalfBakedWrapperKey
import mods.betterfoliage.model.SpecialRenderModel
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 net.minecraft.util.Direction
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
data class RoundLogKey(
override val axis: Direction.Axis?,
val barkSprite: ResourceLocation,
interface RoundLogKey : ColumnBlockKey, ModelBakingKey {
val barkSprite: ResourceLocation
val endSprite: ResourceLocation
) : ColumnBlockKey, ModelBakingKey {
}
object RoundLogOverlayLayer : ColumnRenderLayer() {
override fun getColumnKey(state: BlockState) = Client.blockTypes.getTyped<ColumnBlockKey>(state)
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
object StandardLogDiscovery : 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>) {
val axis = getAxis(ctx.blockState)
detailLogger.log(INFO, " axis $axis")
StandardRoundLogKey(axis, textureMatch[0], textureMatch[1]).let { key ->
ctx.addReplacement(key)
Client.blockTypes.stateKeys[ctx.blockState] = key
}
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y
"z" -> Axis.Z
else -> null
}
}
}
data class StandardRoundLogKey(
override val axis: Axis?,
override val barkSprite: ResourceLocation,
override val endSprite: ResourceLocation
) : RoundLogKey, HalfBakedWrapperKey() {
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardRoundLogModel(this, wrapped)
}
class StandardRoundLogModel(
val key: StandardRoundLogKey,
wrapped: SpecialRenderModel
) : ColumnModelBase(wrapped) {
override val enabled: Boolean get() = Config.enabled && Config.roundLogs.enabled
override val overlayLayer: ColumnRenderLayer get() = RoundLogOverlayLayer
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
val modelSet by modelSets.delegate(key)
override fun getMeshSet(axis: Axis, quadrant: Int) = modelSet
companion object {
val modelSets = LazyMapInvalidatable(BakeWrapperManager) { key: StandardRoundLogKey ->
val barkSprite = Atlas.BLOCKS[key.barkSprite]
val endSprite = Atlas.BLOCKS[key.endSprite]
Config.roundLogs.let { config ->
ColumnMeshSet(
config.radiusSmall, config.radiusLarge, config.zProtection,
key.axis ?: Axis.Y,
barkSprite, barkSprite,
endSprite, endSprite
)
}
}
}
}

View File

@@ -18,7 +18,8 @@ import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ModelBakingKey
import mods.betterfoliage.resource.discovery.ModelBakingContext
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.Rotation
@@ -28,39 +29,30 @@ import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
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.client.renderer.model.ModelBakery
import net.minecraft.util.Direction
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
object StandardSandDiscovery : AbstractModelDiscovery() {
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in SAND_BLOCKS) {
Client.blockTypes.dirt.add(state)
replacements[location] = StandardSandKey
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutoutMipped())
return true
override fun processModel(ctx: ModelDiscoveryContext) {
if (ctx.getUnbaked() is BlockModel && ctx.blockState.block in SAND_BLOCKS) {
Client.blockTypes.dirt.add(ctx.blockState)
ctx.addReplacement(StandardSandKey)
RenderTypeLookup.setRenderLayer(ctx.blockState.block, RenderType.getCutoutMipped())
}
return super.processModel(bakery, state, location, sprites, replacements)
super.processModel(ctx)
}
}
object StandardSandKey : HalfBakedWrapperKey() {
override fun replace(wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
override fun bake(ctx: ModelBakingContext, wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
}
class StandardSandModel(

View File

@@ -0,0 +1,166 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.Quad
import mods.betterfoliage.model.UV
import mods.betterfoliage.model.Vertex
import mods.betterfoliage.model.bake
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.EAST
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.UP
/**
* Collection of dynamically generated meshes used to render rounded columns.
*/
class ColumnMeshSet(
radiusSmall: Double,
radiusLarge: Double,
zProtection: Double,
val axis: Axis,
val spriteLeft: TextureAtlasSprite,
val spriteRight: TextureAtlasSprite,
val spriteTop: TextureAtlasSprite,
val spriteBottom: TextureAtlasSprite
) {
protected fun sideRounded(radius: Double, yBottom: Double, yTop: Double): List<Quad> {
val halfRadius = radius * 0.5
return listOf(
// left side of the diagonal
Quad.verticalRectangle(0.0, 0.5, 0.5 - radius, 0.5, yBottom, yTop).clampUV(minU = 0.0, maxU = 0.5 - radius),
Quad.verticalRectangle(0.5 - radius, 0.5, 0.5 - halfRadius, 0.5 - halfRadius, yBottom, yTop).clampUV(minU = 0.5 - radius),
// right side of the diagonal
Quad.verticalRectangle(0.5 - halfRadius, 0.5 - halfRadius, 0.5, 0.5 - radius, yBottom, yTop).clampUV(maxU = radius - 0.5),
Quad.verticalRectangle(0.5, 0.5 - radius, 0.5, 0.0, yBottom, yTop).clampUV(minU = radius - 0.5, maxU = 0.0)
)
}
protected fun sideRoundedTransition(radiusBottom: Double, radiusTop: Double, yBottom: Double, yTop: Double): List<Quad> {
val ySplit = 0.5 * (yBottom + yTop)
val modelTop = sideRounded(radiusTop, yBottom, yTop)
val modelBottom = sideRounded(radiusBottom, yBottom, yTop)
return (modelBottom zip modelTop).map { (quadBottom, quadTop) ->
Quad.mix(quadBottom, quadTop) { vBottom, vTop -> if (vBottom.xyz.y < ySplit) vBottom.copy() else vTop.copy() }
}
}
protected fun sideSquare(yBottom: Double, yTop: Double) = listOf(
Quad.verticalRectangle(0.0, 0.5, 0.5, 0.5, yBottom, yTop).clampUV(minU = 0.0),
Quad.verticalRectangle(0.5, 0.5, 0.5, 0.0, yBottom, yTop).clampUV(maxU = 0.0)
)
protected fun lidRounded(radius: Double, y: Double, isBottom: Boolean) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
val v1 = Vertex(Double3(0.0, y, 0.0), UV(0.0, 0.0))
val v2 = Vertex(Double3(0.0, y, 0.5), UV(0.0, 0.5))
val v3 = Vertex(Double3(0.5 - radius, y, 0.5), UV(0.5 - radius, 0.5))
val v4 = Vertex(Double3(0.5 - radius * 0.5, y, 0.5 - radius * 0.5), UV(0.5, 0.5))
val v5 = Vertex(Double3(0.5, y, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, y, 0.0), UV(0.5, 0.0))
listOf(Quad(v1, v2, v3, v4), Quad(v1, v4, v5, v6))
.map { it.cycleVertices(if (isBottom xor Config.nVidia) 0 else 1) }
.map { it.rotate(rotation).rotateUV(quadrant) }
.map { it.sprite(if (isBottom) spriteBottom else spriteTop) }
.map { if (isBottom) it.flipped else it }
}
protected fun lidSquare(y: Double, isBottom: Boolean) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
listOf(
Quad.horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = y).clampUV(minU = 0.0, minV = 0.0)
.rotate(rotation).rotateUV(quadrant)
.sprite(if (isBottom) spriteBottom else spriteTop)
.let { if (isBottom) it.flipped else it }
)
}
protected val zProtectionScale = zProtection.let { Double3(it, 1.0, it) }
protected fun List<Quad>.extendTop(size: Double) = map { q -> q.clampUV(minV = 0.5 - size).transformV { v ->
if (v.xyz.y > 0.501) v.copy(xyz = v.xyz * zProtectionScale) else v }
}
protected fun List<Quad>.extendBottom(size: Double) = map { q -> q.clampUV(maxV = -0.5 + size).transformV { v ->
if (v.xyz.y < -0.501) v.copy(xyz = v.xyz * zProtectionScale) else v }
}
protected fun List<Quad>.buildSides(quadsPerSprite: Int) = Array(4) { quadrant ->
val rotation = baseRotation(axis) + quadrantRotations[quadrant]
this.map { it.rotate(rotation) }
.mapIndexed { idx, q -> if (idx % (2 * quadsPerSprite) >= quadsPerSprite) q.sprite(spriteRight) else q.sprite(spriteLeft) }
.bake(false)
}
companion object {
fun baseRotation(axis: Axis) = when(axis) {
Axis.X -> Rotation.fromUp[EAST.ordinal]
Axis.Y -> Rotation.fromUp[UP.ordinal]
Axis.Z -> Rotation.fromUp[SOUTH.ordinal]
}
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
}
//
// Mesh definitions
// 4-element arrays hold prebuild meshes for each of the rotations around the axis
//
val sideSquare = sideSquare(-0.5, 0.5).buildSides(quadsPerSprite = 1)
val sideRoundSmall = sideRounded(radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val sideRoundLarge = sideRounded(radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val sideExtendTopSquare = sideSquare(0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 1)
val sideExtendTopRoundSmall = sideRounded(radiusSmall, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendTopRoundLarge = sideRounded(radiusLarge, 0.5, 0.5 + radiusLarge).extendTop(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendBottomSquare = sideSquare(-0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 1)
val sideExtendBottomRoundSmall = sideRounded(radiusSmall, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val sideExtendBottomRoundLarge = sideRounded(radiusLarge, -0.5 - radiusLarge, -0.5).extendBottom(radiusLarge).buildSides(quadsPerSprite = 2)
val lidTopSquare = lidSquare(0.5, false).bake(false)
val lidTopRoundSmall = lidRounded(radiusSmall, 0.5, false).bake(false)
val lidTopRoundLarge = lidRounded(radiusLarge, 0.5, false).bake(false)
val lidBottomSquare = lidSquare(-0.5, true).bake(false)
val lidBottomRoundSmall = lidRounded(radiusSmall, -0.5, true).bake(false)
val lidBottomRoundLarge = lidRounded(radiusLarge, -0.5, true).bake(false)
val transitionTop = sideRoundedTransition(radiusLarge, radiusSmall, -0.5, 0.5).buildSides(quadsPerSprite = 2)
val transitionBottom = sideRoundedTransition(radiusSmall, radiusLarge, -0.5, 0.5).buildSides(quadsPerSprite = 2)
//
// Helper fuctions for lids (block ends)
//
fun flatTop(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> lidTopRoundSmall[quadrant]
LARGE_RADIUS -> lidTopRoundLarge[quadrant]
SQUARE -> lidTopSquare[quadrant]
INVISIBLE -> lidTopSquare[quadrant]
}
fun flatBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> lidBottomRoundSmall[quadrant]
LARGE_RADIUS -> lidBottomRoundLarge[quadrant]
SQUARE -> lidBottomSquare[quadrant]
INVISIBLE -> lidBottomSquare[quadrant]
}
fun extendTop(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> sideExtendTopRoundSmall[quadrant]
LARGE_RADIUS -> sideExtendTopRoundLarge[quadrant]
SQUARE -> sideExtendTopSquare[quadrant]
INVISIBLE -> sideExtendTopSquare[quadrant]
}
fun extendBottom(quadrantTypes: Array<QuadrantType>, quadrant: Int) = when(quadrantTypes[quadrant]) {
SMALL_RADIUS -> sideExtendBottomRoundSmall[quadrant]
LARGE_RADIUS -> sideExtendBottomRoundLarge[quadrant]
SQUARE -> sideExtendBottomSquare[quadrant]
INVISIBLE -> sideExtendBottomSquare[quadrant]
}
}

View File

@@ -0,0 +1,112 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.model.HalfBakedSpecialWrapper
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.render.column.ColumnLayerData.NormalRender
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.render.lighting.ColumnLighting
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.util.Direction.Axis
abstract class ColumnModelBase(
wrapped: SpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
abstract val enabled: Boolean
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract fun getMeshSet(axis: Axis, quadrant: Int): ColumnMeshSet
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
when(roundLog) {
ColumnLayerData.SkipRender -> return
NormalRender -> return super.render(ctx, noDecorations)
ColumnLayerData.ResolveError, null -> {
return super.render(ctx, noDecorations)
}
}
// 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)
}
ctx.vertexLighter = ColumnLighting
val axis = roundLog.column.axis ?: Axis.Y
val baseRotation = ColumnMeshSet.baseRotation(axis)
ColumnMeshSet.quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
val meshSet = getMeshSet(axis, idx)
// disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate
if (roundLog.quadrants[idx] == LARGE_RADIUS &&
roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS &&
roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) {
roundLog.quadrants[idx] = SMALL_RADIUS
}
// select meshes for current quadrant based on connectivity rules
val sideMesh = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> meshSet.sideRoundSmall[idx]
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) meshSet.transitionTop[idx]
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) meshSet.transitionBottom[idx]
else meshSet.sideRoundLarge[idx]
SQUARE -> meshSet.sideSquare[idx]
else -> null
}
val upMesh = when(roundLog.upType) {
NONSOLID -> meshSet.flatTop(roundLog.quadrants, idx)
PERPENDICULAR -> {
if (!connectPerpendicular) {
meshSet.flatTop(roundLog.quadrants, idx)
} else {
meshSet.extendTop(roundLog.quadrants, idx)
}
}
PARALLEL -> {
if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsTop[idx] &&
roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } )
meshSet.flatTop(roundLog.quadrants, idx)
else null
}
else -> null
}
val downMesh = when(roundLog.downType) {
NONSOLID -> meshSet.flatBottom(roundLog.quadrants, idx)
PERPENDICULAR -> {
if (!connectPerpendicular) {
meshSet.flatBottom(roundLog.quadrants, idx)
} else {
meshSet.extendBottom(roundLog.quadrants, idx)
}
}
PARALLEL -> {
if (roundLog.quadrants[idx] discontinuousWith roundLog.quadrantsBottom[idx] &&
roundLog.quadrants[idx].let { it == SQUARE || it == INVISIBLE } )
meshSet.flatBottom(roundLog.quadrants, idx)
else null
}
else -> null
}
// render
sideMesh?.let { ctx.renderQuads(it) }
upMesh?.let { ctx.renderQuads(it) }
downMesh?.let { ctx.renderQuads(it) }
}
}
}

View File

@@ -90,7 +90,6 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
override fun calculate(ctx: BlockCtx): ColumnLayerData {
// TODO detect round logs
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube } }) return ColumnLayerData.SkipRender
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally

View File

@@ -2,7 +2,8 @@ package mods.betterfoliage.render.lighting
import mods.betterfoliage.model.HalfBakedQuad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.EPSILON
import mods.betterfoliage.util.EPSILON_ONE
import mods.betterfoliage.util.EPSILON_ZERO
import mods.betterfoliage.util.minBy
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.Direction
@@ -41,6 +42,12 @@ class VanillaQuadLighting {
abstract class VanillaVertexLighter {
abstract fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting)
/**
* Update lighting for each vertex with AO values from one of the corners.
* Does not calculate missing AO values!
* @param quad the quad to shade
* @param func selector function from vertex position to directed corner index of desired AO values
*/
inline fun VanillaQuadLighting.updateWithCornerAo(quad: HalfBakedQuad, func: (Double3)->Int?) {
quad.raw.verts.forEachIndexed { idx, vertex ->
func(vertex.xyz)?.let {
@@ -51,9 +58,13 @@ abstract class VanillaVertexLighter {
}
}
/**
* Replicates vanilla shading for full blocks. Interpolation for non-full blocks
* is not implemented.
*/
object VanillaFullBlockLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// TODO bounds checking
// TODO bounds checking & interpolation
val face = quad.raw.face()
lighting.calc.fillLightData(face, true)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
@@ -62,7 +73,7 @@ object VanillaFullBlockLighting : VanillaVertexLighter() {
}
}
object RoundLeafLighting : VanillaVertexLighter() {
object RoundLeafLightingPreferUp : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
val angles = getAngles45(quad)?.let { normalFaces ->
lighting.calc.fillLightData(normalFaces.first)
@@ -79,6 +90,28 @@ object RoundLeafLighting : VanillaVertexLighter() {
}
}
/**
* Lights vertices with the AO values from the nearest corner on either of
* the 2 faces the quad normal points towards.
*/
object RoundLeafLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
val angles = getAngles45(quad)?.let { normalFaces ->
lighting.calc.fillLightData(normalFaces.first)
lighting.calc.fillLightData(normalFaces.second)
lighting.updateWithCornerAo(quad) { vertex ->
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
val preferredFace = normalFaces.minBy { faceDistance(it, vertex) }
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
}
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
}
/**
* Lights vertices with the AO values from the nearest corner on the preferred face.
*/
class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
lighting.calc.fillLightData(face)
@@ -87,6 +120,38 @@ class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
}
}
object ColumnLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// faces pointing in cardinal directions
getNormalFace(quad)?.let { face ->
lighting.calc.fillLightData(face)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
return
}
// faces pointing at 45deg angles
getAngles45(quad)?.let { (face1, face2) ->
lighting.calc.fillLightData(face1)
lighting.calc.fillLightData(face2)
quad.raw.verts.forEachIndexed { idx, vertex ->
val cornerUndir = AoSideHelper.getCornerUndir(vertex.xyz.x, vertex.xyz.y, vertex.xyz.z)
val cornerDir1 = AoSideHelper.boxCornersDirFromUndir[face1.ordinal][cornerUndir]
val cornerDir2 = AoSideHelper.boxCornersDirFromUndir[face2.ordinal][cornerUndir]
if (cornerDir1 == null || cornerDir2 == null) return@let
val ao1 = lighting.calc.aoData[cornerDir1]
val ao2 = lighting.calc.aoData[cornerDir2]
lighting.packedLight[idx] = ((ao1.packedLight + ao2.packedLight) shr 1) and 0xFF00FF
lighting.colorMultiplier[idx] = (ao1.colorMultiplier + ao2.colorMultiplier) * 0.5f
}
lighting.updateBlockTint(quad.baked.tintIndex)
return
}
// something is wrong...
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, quad.raw.face()) }
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
/**
* Return the directed box corner index for the corner nearest the given vertex,
* which is on the given face. May return null if the vertex is closest to
@@ -107,9 +172,9 @@ fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
val normal = quad.raw.normal
// one of the components must be close to zero
val zeroAxis = when {
abs(normal.x) < EPSILON -> Axis.X
abs(normal.y) < EPSILON -> Axis.Y
abs(normal.z) < EPSILON -> Axis.Z
abs(normal.x) < EPSILON_ZERO -> Axis.X
abs(normal.y) < EPSILON_ZERO -> Axis.Y
abs(normal.z) < EPSILON_ZERO -> Axis.Z
else -> return null
}
// the other two must be of similar magnitude
@@ -118,7 +183,7 @@ fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON) return null
if (diff > EPSILON_ZERO) return null
return when(zeroAxis) {
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
@@ -126,6 +191,18 @@ fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
}
}
fun getNormalFace(quad: HalfBakedQuad) = quad.raw.normal.let { normal ->
when {
normal.x > EPSILON_ONE -> EAST
normal.x < -EPSILON_ONE -> WEST
normal.y > EPSILON_ONE -> UP
normal.y < -EPSILON_ONE -> DOWN
normal.z > EPSILON_ONE -> SOUTH
normal.z < -EPSILON_ONE -> NORTH
else -> null
}
}
fun faceDistance(face: Direction, pos: Double3) = when(face) {
WEST -> pos.x; EAST -> 1.0 - pos.x
DOWN -> pos.y; UP -> 1.0 - pos.y

View File

@@ -1,9 +1,6 @@
package mods.betterfoliage.render.particle
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.HSB
import mods.betterfoliage.texture.LeafParticleKey
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax

View File

@@ -1,10 +1,10 @@
package mods.betterfoliage.texture
package mods.betterfoliage.render.particle
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.model.Color
import mods.betterfoliage.model.FixedSpriteSet
import mods.betterfoliage.model.SpriteSet
import mods.betterfoliage.resource.discovery.ModelDefinitionsLoadedEvent
import mods.betterfoliage.resource.VeryEarlyReloadListener
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.get
@@ -15,7 +15,7 @@ import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.INFO
interface LeafBlockModel {
val key: LeafParticleKey
@@ -26,26 +26,28 @@ interface LeafParticleKey {
val overrideColor: Color?
}
object LeafParticleRegistry : HasLogger() {
object LeafParticleRegistry : HasLogger(), VeryEarlyReloadListener {
val typeMappings = TextureMatcher()
val allTypes get() = (typeMappings.mappings.map { it.type } + "default").distinct()
val particles = hashMapOf<String, SpriteSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
@SubscribeEvent
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
override fun onReloadStarted() {
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
detailLogger.log(INFO, "Loaded leaf particle mappings, types = [${allTypes.joinToString(", ")}]")
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation == Atlas.PARTICLES.resourceId) {
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
allTypes.forEach { leafType ->
val locations = (0 until 16).map { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/falling_leaf_${leafType}_$idx")
}.filter { resourceManager.hasResource(Atlas.PARTICLES.file(it)) }
detailLogger.log(Level.INFO, "Registering sprites for leaf particle type [$leafType], ${locations.size} sprites found")
detailLogger.log(INFO, "Registering sprites for leaf particle type [$leafType], ${locations.size} sprites found")
locations.forEach { event.addSprite(it) }
}
}
@@ -60,7 +62,7 @@ object LeafParticleRegistry : HasLogger() {
}
.map { event.map.getSprite(it) }
.filter { it !is MissingTextureSprite }
detailLogger.log(Level.INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
detailLogger.log(INFO, "Leaf particle type [$leafType], ${sprites.size} sprites in atlas")
particles[leafType] = FixedSpriteSet(sprites)
}
}

View File

@@ -0,0 +1,89 @@
package mods.betterfoliage.render.particle
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.model.SpriteDelegate
import mods.betterfoliage.model.SpriteSetDelegate
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.forEachPairIndexed
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import net.minecraft.client.particle.IParticleRenderType
import net.minecraft.client.renderer.ActiveRenderInfo
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
import java.util.Deque
import java.util.LinkedList
import kotlin.math.cos
import kotlin.math.sin
class RisingSoulParticle(
world: World, pos: BlockPos
) : AbstractParticle(
world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5
) {
val particleTrail: Deque<Double3> = LinkedList<Double3>()
val initialPhase = randomD(max = PI2)
init {
motionY = 0.1
particleGravity = 0.0f
sprite = headIcons[randomI(max = 1024)]
maxAge = MathHelper.floor((0.6 + 0.4 * randomD()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = initialPhase + (age.toDouble() * PI2 / 64.0)
val cosPhase = cos(phase);
val sinPhase = sin(phase)
velocity.setTo(Config.risingSoul.perturb.let { Double3(cosPhase * it, 0.1, sinPhase * it) })
particleTrail.addFirst(currentPos.copy())
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
if (!Config.enabled) setExpired()
}
override fun renderParticle(vertexBuilder: IVertexBuilder, camera: ActiveRenderInfo, tickDelta: Float) {
var alpha = Config.risingSoul.opacity.toFloat()
if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
renderParticleQuad(
vertexBuilder, camera, tickDelta,
size = Config.risingSoul.headSize * 0.25,
alpha = alpha
)
var scale = Config.risingSoul.trailSize * 0.25
particleTrail.forEachPairIndexed { idx, current, previous ->
scale *= Config.risingSoul.sizeDecay
alpha *= Config.risingSoul.opacityDecay.toFloat()
if (idx % Config.risingSoul.trailDensity == 0)
renderParticleQuad(
vertexBuilder, camera, tickDelta,
currentPos = current,
prevPos = previous,
size = scale,
alpha = alpha,
sprite = trackIcon
)
}
}
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
companion object {
val headIcons by SpriteSetDelegate(Atlas.PARTICLES) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "particle/rising_soul_$idx")
}
val trackIcon by SpriteDelegate(Atlas.PARTICLES) { ResourceLocation(BetterFoliageMod.MOD_ID, "particle/soul_track") }
}
}

View File

@@ -0,0 +1,27 @@
package mods.betterfoliage.resource
import net.minecraft.profiler.IProfiler
import net.minecraft.resources.IFutureReloadListener
import net.minecraft.resources.IResourceManager
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
/**
* Catch resource reload extremely early, before any of the reloaders
* have started working.
*/
interface VeryEarlyReloadListener : IFutureReloadListener {
override fun reload(
stage: IFutureReloadListener.IStage,
resourceManager: IResourceManager,
preparationsProfiler: IProfiler,
reloadProfiler: IProfiler,
backgroundExecutor: Executor,
gameExecutor: Executor
): CompletableFuture<Void> {
onReloadStarted()
return stage.markCompleteAwaitingOthers(null)
}
fun onReloadStarted() {}
}

View File

@@ -5,6 +5,7 @@ import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.SimpleInvalidator
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.renderer.model.IUnbakedModel
@@ -19,7 +20,9 @@ import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.loading.progress.StartupMessageManager
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
import java.lang.ref.WeakReference
import java.util.function.Function
@@ -27,6 +30,21 @@ data class ModelDefinitionsLoadedEvent(
val bakery: ModelBakery
) : Event()
data class ModelDiscoveryContext(
val bakery: ModelBakery,
val blockState: BlockState,
val modelLocation: ResourceLocation,
val sprites: MutableSet<ResourceLocation>,
val replacements: MutableMap<ResourceLocation, ModelBakingKey>,
val logger: Logger
) {
fun getUnbaked(location: ResourceLocation = modelLocation) = bakery.getUnbakedModel(location)
fun addReplacement(key: ModelBakingKey) {
replacements[modelLocation] = key
logger.log(INFO, "Adding model replacement $modelLocation -> $key")
}
}
interface ModelDiscovery {
fun onModelsLoaded(
bakery: ModelBakery,
@@ -35,14 +53,20 @@ interface ModelDiscovery {
)
}
data class ModelBakingContext(
val bakery: ModelBakery,
val spriteGetter: Function<Material, TextureAtlasSprite>,
val location: ResourceLocation,
val transform: IModelTransform,
val logger: Logger
) {
fun getUnbaked() = bakery.getUnbakedModel(location)
fun getBaked() = bakery.getBakedModel(location, transform, spriteGetter)
}
interface ModelBakingKey {
fun bake(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? = unbaked.bakeModel(bakery, spriteGetter, transform, location)
fun bake(ctx: ModelBakingContext): IBakedModel? =
ctx.getUnbaked().bakeModel(ctx.bakery, ctx.spriteGetter, ctx.transform, ctx.location)
}
object BakeWrapperManager : Invalidator, HasLogger() {
@@ -57,14 +81,16 @@ object BakeWrapperManager : Invalidator, HasLogger() {
@SubscribeEvent(priority = EventPriority.LOWEST)
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
val startTime = System.currentTimeMillis()
modelsValid.invalidate()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")
logger.log(INFO, "starting model discovery (${discoverers.size} listeners)")
discoverers.forEach { listener ->
val replacementsLocal = mutableMapOf<ResourceLocation, ModelBakingKey>()
listener.onModelsLoaded(event.bakery, sprites, replacementsLocal)
replacements.putAll(replacementsLocal)
listener.onModelsLoaded(event.bakery, sprites, replacements)
}
val elapsed = System.currentTimeMillis() - startTime
logger.log(INFO, "finished model discovery in $elapsed ms, ${replacements.size} top-level replacements")
}
@SubscribeEvent
@@ -89,17 +115,12 @@ object BakeWrapperManager : Invalidator, HasLogger() {
transform: IModelTransform,
location: ResourceLocation
): IBakedModel? {
val ctx = ModelBakingContext(bakery, spriteGetter, location, transform, detailLogger)
// bake replacement if available
replacements[location]?.let { replacement ->
detailLogger.log(INFO, "Baking replacement for [${unbaked::class.java.simpleName}] $location -> $replacement")
return replacement.bake(location, unbaked, transform, bakery, spriteGetter)
return replacement.bake(ctx)
}
// container model support
if (unbaked is VariantList) SpecialRenderVariantList.bakeIfSpecial(location, unbaked, bakery, spriteGetter)?.let {
detailLogger.log(INFO, "Wrapping container [${unbaked::class.java.simpleName}] $location")
return it
}
return unbaked.bakeModel(bakery, spriteGetter, transform, location)
}
}

View File

@@ -1,19 +1,13 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.Client
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.util.ResourceLocation
class BlockTypeCache {
val leaf = mutableSetOf<BlockState>()
val grass = mutableSetOf<BlockState>()
val dirt = mutableSetOf<BlockState>()
companion object : ModelDiscovery {
override fun onModelsLoaded(bakery: ModelBakery, sprites: MutableSet<ResourceLocation>, replacements: MutableMap<ResourceLocation, ModelBakingKey>
) {
Client.blockTypes = BlockTypeCache()
}
}
val stateKeys = mutableMapOf<BlockState, ModelBakingKey>()
inline fun <reified T> getTyped(state: BlockState) = stateKeys[state] as? T
}

View File

@@ -1,4 +1,4 @@
package mods.betterfoliage.config
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.getJavaClass
@@ -6,7 +6,7 @@ import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import net.minecraft.block.Block
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Level.INFO
interface IBlockMatcher {
fun matchesClass(block: Block): Boolean
@@ -28,7 +28,6 @@ class ConfigurableBlockMatcher(val location: ResourceLocation) : IBlockMatcher {
val blackList = mutableListOf<Class<*>>()
val whiteList = mutableListOf<Class<*>>()
// override fun convertValue(line: String) = getJavaClass(line)
override fun matchesClass(block: Block): Boolean {
val blockClass = block.javaClass
@@ -48,7 +47,7 @@ class ConfigurableBlockMatcher(val location: ResourceLocation) : IBlockMatcher {
blackList.clear()
whiteList.clear()
resourceManager.getAllResources(location).forEach { resource ->
logger.log(DEBUG, "Reading resource $location from pack ${resource.packName}")
logger.log(INFO, "Reading block class configuration $location from pack ${resource.packName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
if (line.startsWith("-")) getJavaClass(line.substring(1))?.let { blackList.add(it) }
else getJavaClass(line)?.let { whiteList.add(it) }
@@ -68,7 +67,7 @@ class ModelTextureListConfiguration(val location: ResourceLocation) {
val modelList = mutableListOf<ModelTextureList>()
fun readDefaults() {
resourceManager.getAllResources(location).forEach { resource ->
logger.log(DEBUG, "Reading resource $location from pack ${resource.packName}")
logger.log(INFO, "Reading model/texture configuration $location from pack ${resource.packName}")
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
val elements = line.split(",")
modelList.add(ModelTextureList(ResourceLocation(elements.first()), elements.drop(1)))

View File

@@ -1,15 +1,11 @@
package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner
import mods.betterfoliage.config.IBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.util.HasLogger
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.model.multipart.Multipart
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.registries.ForgeRegistries
@@ -25,36 +21,29 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
.flatMap { block -> block.stateContainer.validStates }
.forEach { state ->
val location = BlockModelShapes.getModelLocation(state)
val ctx = ModelDiscoveryContext(bakery, state, location, sprites, replacements, detailLogger)
try {
val hasReplaced = processModel(bakery, state, location, sprites, replacements)
processModel(ctx)
} catch (e: Exception) {
logger.log(Level.WARN, "Discovery error in $location", e)
}
}
}
open fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
open fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
// built-in support for container models
return when (val model = bakery.getUnbakedModel(location)) {
is VariantList -> {
val hasReplaced = model.variantList.fold(false) { hasReplaced, variant ->
processModel(bakery, state, variant.modelLocation, sprites, replacements) || hasReplaced
}
if (hasReplaced) replacements[location]
hasReplaced
if (model is VariantList) {
// per-location replacements need to be scoped to the variant list, as replacement models
// may need information from the BlockState which is not available at baking time
val scopedReplacements = mutableMapOf<ResourceLocation, ModelBakingKey>()
model.variantList.forEach { variant ->
processModel(ctx.copy(modelLocation = variant.modelLocation, replacements = scopedReplacements))
}
is Multipart -> model.variants.fold(false) { hasReplaced, variantList ->
variantList.variantList.fold(false) { hasReplaced, variant ->
processModel(bakery, state, variant.modelLocation, sprites, replacements) || hasReplaced
} || hasReplaced
if (scopedReplacements.isNotEmpty()) {
ctx.addReplacement(SpecialRenderVariantList(scopedReplacements))
}
else -> false
}
}
}
@@ -64,37 +53,23 @@ abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean
ctx: ModelDiscoveryContext,
textureMatch: List<ResourceLocation>
)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakingKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
override fun processModel(ctx: ModelDiscoveryContext) {
val model = ctx.getUnbaked()
if (model is BlockModel) {
val matchClass = matchClasses.matchingClass(state.block) ?: return false
val matchClass = matchClasses.matchingClass(ctx.blockState.block) ?: return
detailLogger.log(Level.INFO, "block state $state")
detailLogger.log(Level.INFO, " model $location")
replacements[location]?.let { existing ->
detailLogger.log(Level.INFO, " already processed as $existing")
return true
}
detailLogger.log(Level.INFO, " class ${state.block.javaClass.name} matches ${matchClass.name}")
detailLogger.log(Level.INFO, "block state ${ctx.blockState}")
detailLogger.log(Level.INFO, " model ${ctx.modelLocation}")
detailLogger.log(Level.INFO, " class ${ctx.blockState.block.javaClass.name} matches ${matchClass.name}")
modelTextures
.filter { matcher -> bakery.modelDerivesFrom(model, location, matcher.modelLocation) }
.filter { matcher -> ctx.bakery.modelDerivesFrom(model, ctx.modelLocation, matcher.modelLocation) }
.forEach { match ->
detailLogger.log(Level.INFO, " model ${model} matches ${match.modelLocation}")
detailLogger.log(Level.INFO, " model $model matches ${match.modelLocation}")
val materials = match.textureNames.map { it to model.resolveTextureName(it) }
val texMapString = Joiner.on(", ").join(materials.map { "${it.first}=${it.second.textureLocation}" })
@@ -102,12 +77,11 @@ abstract class ConfigurableModelDiscovery : AbstractModelDiscovery() {
if (materials.all { it.second.textureLocation != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist)
if (processModel(state, location, materials.map { it.second.textureLocation }, sprites, replacements))
return true
processModel(ctx, materials.map { it.second.textureLocation })
}
}
}
return super.processModel(bakery, state, location, sprites, replacements)
return super.processModel(ctx)
}
}

View File

@@ -0,0 +1,54 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.model.HalfBakedSimpleModelWrapper
import mods.betterfoliage.model.SpecialRenderModel
import mods.betterfoliage.model.SpecialRenderVariantList
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Level.WARN
class SpecialRenderVariantList(
val replacements: Map<ResourceLocation, ModelBakingKey>
) : ModelBakingKey {
override fun bake(ctx: ModelBakingContext): IBakedModel? {
val unbaked = ctx.getUnbaked()
if (unbaked !is VariantList) return super.bake(ctx)
// bake all variants, replace as needed
val bakedModels = unbaked.variantList.mapNotNull {
val variantCtx = ctx.copy(location = it.modelLocation, transform = it)
val replacement = replacements[it.modelLocation]
val baked = replacement?.let { replacement ->
ctx.logger.log(INFO, "Baking replacement for variant [${variantCtx.getUnbaked()::class.java.simpleName}] ${variantCtx.location} -> $replacement")
replacement.bake(variantCtx)
} ?: variantCtx.getBaked()
when(baked) {
is SpecialRenderModel -> it to baked
is SimpleBakedModel -> it to HalfBakedSimpleModelWrapper(baked)
else -> null
}
}
// something fishy going on, possibly unknown model type
// let it through unchanged
if (bakedModels.isEmpty()) return super.bake(ctx)
if (bakedModels.size < unbaked.variantList.size) {
SpecialRenderVariantList.detailLogger.log(
WARN,
"Dropped ${unbaked.variantList.size - bakedModels.size} variants from model ${ctx.location}"
)
}
val weightedSpecials = bakedModels.map { (variant, model) ->
SpecialRenderVariantList.WeightedModel(model, variant.weight)
}
return SpecialRenderVariantList(weightedSpecials, weightedSpecials[0].model)
}
override fun toString() = "[SpecialRenderVariantList, ${replacements.size} replacements]"
}

View File

@@ -1,8 +1,8 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.texture.loadSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.loadSprite
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage

View File

@@ -1,10 +1,10 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.texture.blendRGB
import mods.betterfoliage.texture.loadSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.blendRGB
import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.get
import mods.betterfoliage.util.loadSprite
import mods.betterfoliage.util.set
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation

View File

@@ -1,11 +1,11 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.texture.loadSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.get
import mods.betterfoliage.util.loadImage
import mods.betterfoliage.util.loadSprite
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.set
import net.minecraft.resources.IResource

View File

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

View File

@@ -8,7 +8,8 @@ import net.minecraft.util.Direction.AxisDirection.NEGATIVE
import net.minecraft.util.Direction.AxisDirection.POSITIVE
import net.minecraft.util.math.BlockPos
val EPSILON = 0.05
val EPSILON_ZERO = 0.05
val EPSILON_ONE = 0.95
// ================================
// Axes and directions

View File

@@ -1,12 +1,4 @@
// Vanilla
block/column_side,end,end,side
block/cube_column,end,end,side
block/cube_all,all,all,all
// Agricultural Revolution
agriculturalrevolution:block/palmlog,top,top,texture
// Lithos Core
block/column_top,end,end,side_a,side_b
block/column_side_x,end,end,side_a,side_b
block/column_side_z,end,end,side_a,side_b
block/column_side,side,end
block/cube_column,side,end
block/cube_all,all,all

View File

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