[WIP] major rewrite, grass and leaves working already

This commit is contained in:
octarine-noise
2021-05-06 22:40:32 +02:00
parent 09ccb83e8b
commit f44d2a7a50
96 changed files with 2348 additions and 3531 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ run/
build/
classes/
temp/
logs

View File

@@ -9,13 +9,16 @@ repositories {
maven("http://files.minecraftforge.net/maven")
maven("https://repo.spongepowered.org/maven")
maven("https://minecraft.curseforge.com/api/maven")
maven("https://maven.shedaniel.me/")
maven("https://www.cursemaven.com")
}
dependencies {
"minecraft"("net.minecraftforge:forge:${properties["mcVersion"]}-${properties["forgeVersion"]}")
"api"(fg.deobf("curse.maven:clothconfig-348521:2938583"))
"implementation"("kottle:Kottle:${properties["kottleVersion"]}")
"implementation"("org.spongepowered:mixin:0.8-SNAPSHOT")
// "implementation"("org.spongepowered:mixin:0.8-SNAPSHOT")
}
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
@@ -44,7 +47,9 @@ java {
kotlin {
target.compilations.configureEach {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += listOf("-Xno-param-assertions", "-Xno-call-assertions")
}
}

View File

@@ -7,7 +7,7 @@ jarName = BetterFoliage-Forge
version = 2.5.1
mcVersion = 1.15.2
forgeVersion = 31.2.0
forgeVersion = 31.2.44
mappingsChannel = snapshot
mappingsVersion = 20200514-1.15.1

View File

@@ -14,6 +14,5 @@ public class MixinConnector implements IMixinConnector {
} catch (ClassNotFoundException e) {
Mixins.addConfiguration("betterfoliage.vanilla.mixins.json");
}
}
}

View File

@@ -0,0 +1,41 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import mods.betterfoliage.render.ISpecialRenderModel;
import mods.betterfoliage.render.pipeline.RenderCtxVanilla;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraftforge.client.model.data.IModelData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(BlockModelRenderer.class)
public class MixinBlockModelRenderer {
private static final String renderModel = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModel(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelFlat = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelFlat(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "Lnet/minecraft/client/renderer/BlockModelRenderer;renderModelSmooth(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelSmooth), remap = false)
public boolean onRenderModelSmooth(BlockModelRenderer renderer, ILightReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof ISpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (ISpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, true);
else
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
}
@Redirect(method = renderModel, at = @At(value = "INVOKE", target = renderModelFlat), remap = false)
public boolean onRenderModelFlat(BlockModelRenderer renderer, ILightReader world, IBakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, IVertexBuilder buffer, boolean checkSides, Random random, long rand, int combinedOverlay, IModelData modelData) {
if (model instanceof ISpecialRenderModel)
return RenderCtxVanilla.render(renderer, world, (ISpecialRenderModel) model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData, false);
else
return renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, rand, combinedOverlay, modelData);
}
}

View File

@@ -1,27 +0,0 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import mods.betterfoliage.Hooks;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.client.model.data.IModelData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(targets = {"net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask"})
public class MixinChunkRender {
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 renderModel = "Lnet/minecraft/client/renderer/BlockRendererDispatcher;renderModel(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/ILightReader;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;Lnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = compile, at = @At(value = "INVOKE", target = renderModel))
public boolean renderModel(BlockRendererDispatcher dispatcher, BlockState state, BlockPos pos, ILightReader reader, MatrixStack matrixStack, IVertexBuilder vertexBuilder, boolean checkSides, Random random, IModelData modelData) {
return Hooks.renderWorldBlock(dispatcher, state, pos, reader, matrixStack, vertexBuilder, checkSides, random, modelData, MinecraftForgeClient.getRenderLayer());
}
}

View File

@@ -1,20 +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.Redirect;
@Mixin(targets = {"net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$ChunkRender$RebuildTask"})
public class MixinChunkRenderVanilla {
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 canRenderInLayer = "Lnet/minecraft/client/renderer/RenderTypeLookup;canRenderInLayer(Lnet/minecraft/block/BlockState;Lnet/minecraft/client/renderer/RenderType;)Z";
@Redirect(method = compile, at = @At(value = "INVOKE", target = canRenderInLayer))
boolean canRenderInLayer(BlockState state, RenderType layer) {
return Hooks.canRenderInLayerOverride(state, layer);
}
}

View File

@@ -0,0 +1,44 @@
package mods.betterfoliage.mixin;
import com.mojang.blaze3d.matrix.MatrixStack;
import mods.betterfoliage.render.pipeline.RenderCtxForge;
import mods.betterfoliage.render.ISpecialRenderModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.pipeline.ForgeBlockModelRenderer;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Random;
@Mixin(ForgeBlockModelRenderer.class)
public class MixinForgeBlockModelRenderer {
private static final String renderModelFlat = "renderModelFlat(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String renderModelSmooth = "renderModelSmooth(Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;Lcom/mojang/blaze3d/vertex/IVertexBuilder;ZLjava/util/Random;JILnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String render = "Lnet/minecraftforge/client/model/pipeline/ForgeBlockModelRenderer;render(Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;Lnet/minecraft/world/ILightReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lcom/mojang/blaze3d/matrix/MatrixStack;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
@Redirect(method = {renderModelFlat, renderModelSmooth}, at = @At(value = "INVOKE", target = render), remap = false)
public boolean render(
VertexLighterFlat lighter,
ILightReader world,
IBakedModel model,
BlockState state,
BlockPos pos,
MatrixStack matrixStack,
boolean checkSides,
Random rand,
long seed,
IModelData modelData
) {
if (model instanceof ISpecialRenderModel)
return RenderCtxForge.render(lighter, world, (ISpecialRenderModel) model, state, pos, matrixStack, checkSides, rand, seed, modelData);
else
return ForgeBlockModelRenderer.render(lighter, world, model, state, pos, matrixStack, checkSides, rand, seed, modelData);
}
}

View File

@@ -0,0 +1,66 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.render.lighting.ForgeVertexLighter;
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess;
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(VertexLighterFlat.class)
abstract public class MixinForgeCustomVertexLighting implements ForgeVertexLighter, ForgeVertexLighterAccess {
private static final String processQuad = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;processQuad()V";
private static final String updateLightmap = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateLightmap([F[FFFF)V";
private static final String updateColor = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;updateColor([F[FFFFFI)V";
private static final String resetBlockInfo = "Lnet/minecraftforge/client/model/pipeline/VertexLighterFlat;resetBlockInfo()V";
@NotNull
public ForgeVertexLighter vertexLighter = this;
@NotNull
public ForgeVertexLighter getVertexLighter() {
return vertexLighter;
}
public void setVertexLighter(@NotNull ForgeVertexLighter vertexLighter) {
this.vertexLighter = vertexLighter;
}
@Shadow
protected abstract void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z);
@Shadow
protected abstract void updateColor(float[] normal, float[] color, float x, float y, float z, float tint, int multiplier);
@Override
public void updateVertexLightmap(@NotNull float[] normal, @NotNull float[] lightmap, float x, float y, float z) {
updateLightmap(normal, lightmap, x, y, z);
}
@Override
public void updateVertexColor(@NotNull float[] normal, @NotNull float[] color, float x, float y, float z, float tint, int multiplier) {
updateColor(normal, color, x, y, z, tint, multiplier);
}
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateColor), remap = false)
void onUpdateColor(VertexLighterFlat self, float[] normal, float[] color, float x, float y, float z, float tint, int multiplier) {
vertexLighter.updateVertexColor(normal, color, x, y, z, tint, multiplier);
}
@Redirect(method = processQuad, at = @At(value = "INVOKE", target = updateLightmap), remap = false)
void onUpdateLightmap(VertexLighterFlat self, float[] normal, float[] lightmap, float x, float y, float z) {
vertexLighter.updateVertexLightmap(normal, lightmap, x, y, z);
}
@Inject(method = resetBlockInfo, at = @At("RETURN"), remap = false)
void onReset(CallbackInfo ci) {
// just in case
vertexLighter = this;
}
}

View File

@@ -1,25 +1,19 @@
package mods.betterfoliage.mixin;
import com.google.common.collect.Maps;
import mods.betterfoliage.BetterFoliage;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.AtlasTexture;
import mods.betterfoliage.BetterFoliageMod;
import mods.betterfoliage.ModelDefinitionsLoadedEvent;
import mods.betterfoliage.resource.discovery.BakeWrapperManager;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.function.Function;
@Mixin(ModelBakery.class)
abstract public class MixinModelBakery {
@@ -27,17 +21,23 @@ abstract public class MixinModelBakery {
private static final String processLoading = "Lnet/minecraft/client/renderer/model/ModelBakery;processLoading(Lnet/minecraft/profiler/IProfiler;I)V";
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/util/stream/Stream;Lnet/minecraft/profiler/IProfiler;I)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
private static final String profilerSection = "Lnet/minecraft/profiler/IProfiler;endStartSection(Ljava/lang/String;)V";
@Redirect(method = processLoading, at = @At(value = "INVOKE", target = stitch))
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Stream<ResourceLocation> idStream, IProfiler profiler, int maxMipmapLevel) {
Set<ResourceLocation> idSetIn = idStream.collect(Collectors.toSet());
Set<ResourceLocation> idSetOut = BetterFoliage.INSTANCE.getBlockSprites().prepare(this, manager, idSetIn, profiler);
AtlasTexture.SheetData sheetData = atlas.stitch(manager, idSetOut.stream(), profiler, maxMipmapLevel);
return BetterFoliage.INSTANCE.getBlockSprites().finish(sheetData, profiler);
}
private static final String getBakedModel = "Lnet/minecraft/client/renderer/model/ModelBakery;getBakedModel(Lnet/minecraft/util/ResourceLocation;Lnet/minecraft/client/renderer/model/IModelTransform;Ljava/util/function/Function;)Lnet/minecraft/client/renderer/model/IBakedModel;";
private static final String bakeModel = "Lnet/minecraft/client/renderer/model/IUnbakedModel;bakeModel(Lnet/minecraft/client/renderer/model/ModelBakery;Ljava/util/function/Function;Lnet/minecraft/client/renderer/model/IModelTransform;Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/model/IBakedModel;";
@Inject(method = processLoading, at = @At(value = "INVOKE", target = profilerSection, ordinal = 4))
void onBeforeTextures(IProfiler profiler, int maxMipmapLevel, CallbackInfo ci) {
profiler.endStartSection("betterfoliage");
BetterFoliageMod.INSTANCE.getBus().post(new ModelDefinitionsLoadedEvent(ModelBakery.class.cast(this)));
}
@Redirect(method = getBakedModel, at = @At(value = "INVOKE", target = bakeModel))
IBakedModel onStoreBakedModel(
IUnbakedModel unbaked,
ModelBakery bakery,
Function<Material, TextureAtlasSprite> spriteGetter,
IModelTransform transform,
ResourceLocation locationIn
) {
return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn);
}
}

View File

@@ -6,12 +6,13 @@ import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.optifine.util.BlockUtils;
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.Redirect;
@Mixin(BlockUtils.class)
@Pseudo
@Mixin(targets = "net.optifine.util.BlockUtils")
public class MixinOptifineBlockUtils {
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;";

View File

@@ -16,15 +16,15 @@ public class MixinOptifineChunkRender {
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]);
}
// @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

@@ -1,33 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.BetterFoliage;
import mods.octarinecore.client.resource.AsnycSpriteProviderManager;
import net.minecraft.client.particle.ParticleManager;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Mixin(ParticleManager.class)
public class MixinParticleManager {
private static final String reload = "reload(Lnet/minecraft/resources/IFutureReloadListener$IStage;Lnet/minecraft/resources/IResourceManager;Lnet/minecraft/profiler/IProfiler;Lnet/minecraft/profiler/IProfiler;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;";
private static final String stitch = "Lnet/minecraft/client/renderer/texture/AtlasTexture;stitch(Lnet/minecraft/resources/IResourceManager;Ljava/util/stream/Stream;Lnet/minecraft/profiler/IProfiler;I)Lnet/minecraft/client/renderer/texture/AtlasTexture$SheetData;";
// ewww :S
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = "*", at = @At(value = "INVOKE", target = stitch))
AtlasTexture.SheetData onStitchModelTextures(AtlasTexture atlas, IResourceManager manager, Stream<ResourceLocation> idStream, IProfiler profiler, int maxMipmapLevel) {
Set<ResourceLocation> idSetIn = idStream.collect(Collectors.toSet());
Set<ResourceLocation> idSetOut = BetterFoliage.INSTANCE.getParticleSprites().prepare(this, manager, idSetIn, profiler);
AtlasTexture.SheetData sheetData = atlas.stitch(manager, idSetOut.stream(), profiler, maxMipmapLevel);
return BetterFoliage.INSTANCE.getParticleSprites().finish(sheetData, profiler);
}
}

View File

@@ -1,24 +0,0 @@
package mods.betterfoliage.mixin;
import mods.betterfoliage.integration.ShadersModIntegration;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ILightReader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(BlockModelRenderer.class)
public class MixinShadersBlockModelRenderer {
private static final String renderModel = "renderModel(Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/renderer/BufferBuilder;ZLjava/util/Random;JLnet/minecraftforge/client/model/data/IModelData;)Z";
private static final String pushEntity = "Lnet/optifine/shaders/SVertexBuilder;pushEntity(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/IEnviromentBlockReader;Lnet/minecraft/client/renderer/BufferBuilder;)V";
@SuppressWarnings("UnresolvedMixinReference")
@ModifyArg(method = renderModel, at = @At(value = "INVOKE", target = pushEntity), remap = false)
BlockState overrideBlockState(BlockState state, BlockPos pos, ILightReader world, BufferBuilder buffer) {
return ShadersModIntegration.getBlockStateOverride(state, world, pos);
}
}

View File

@@ -1,5 +0,0 @@
package net.optifine.util;
public class BlockUtils {
// whyyyy?
}

View File

@@ -1,73 +1,5 @@
package mods.betterfoliage
import mods.betterfoliage.util.textComponent
import mods.octarinecore.client.resource.AsnycSpriteProviderManager
import mods.betterfoliage.resource.generated.GeneratedBlockTexturePack
import mods.betterfoliage.util.Atlas
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.particle.ParticleManager
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TranslationTextComponent
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
object BetterFoliage {
var log = LogManager.getLogger("BetterFoliage")
var logDetail = SimpleLogger(
"BetterFoliage",
Level.DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
PropertiesUtil(Properties()),
PrintStream(File("logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
})
)
val blockSprites = AsnycSpriteProviderManager<ModelBakery>("bf-blocks-extra")
val particleSprites = AsnycSpriteProviderManager<ParticleManager>("bf-particles-extra")
val asyncPack = GeneratedBlockTexturePack("bf_gen", "Better Foliage generated assets", logDetail)
fun getSpriteManager(atlas: Atlas) = when(atlas) {
Atlas.BLOCKS -> blockSprites
Atlas.PARTICLES -> particleSprites
} as AsnycSpriteProviderManager<Any>
init {
blockSprites.providers.add(asyncPack)
}
fun log(level: Level, msg: String) {
log.log(level, "[BetterFoliage] $msg")
logDetail.log(level, msg)
}
fun logDetail(msg: String) {
logDetail.log(Level.DEBUG, msg)
}
fun logRenderError(state: BlockState, location: BlockPos) {
if (state in Client.suppressRenderErrors) return
Client.suppressRenderErrors.add(state)
val blockName = ForgeRegistries.BLOCKS.getKey(state.block).toString()
val blockLoc = "${location.x},${location.y},${location.z}"
Minecraft.getInstance().ingameGUI.chatGUI.printChatMessage(TranslationTextComponent(
"betterfoliage.rendererror",
textComponent(blockName, TextFormatting.GOLD),
textComponent(blockLoc, TextFormatting.GOLD)
))
logDetail("Error rendering block $state at $blockLoc")
}
}

View File

@@ -7,6 +7,13 @@ import net.minecraft.client.Minecraft
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.config.ModConfig
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.Properties
@Mod(BetterFoliageMod.MOD_ID)
object BetterFoliageMod {
@@ -14,9 +21,19 @@ object BetterFoliageMod {
val bus = FMLKotlinModLoadingContext.get().modEventBus
val detailLogStream = PrintStream(File("logs/betterfoliage.log").apply {
parentFile.mkdirs()
if (!exists()) createNewFile()
})
fun logger(obj: Any) = LogManager.getLogger(obj)
fun detailLogger(obj: Any) = SimpleLogger(
obj::class.java.simpleName, Level.DEBUG, false, true, true, false, "yyyy-MM-dd HH:mm:ss", null, PropertiesUtil(Properties()), detailLogStream
)
init {
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.build())
Minecraft.getInstance().resourcePackList.addPackFinder(BetterFoliage.asyncPack.finder)
Minecraft.getInstance().resourcePackList.addPackFinder(Client.asyncPack.finder)
bus.register(BlockConfig)
Client.init()
}

View File

@@ -2,83 +2,64 @@ package mods.betterfoliage
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.integration.*
import mods.betterfoliage.render.*
import mods.betterfoliage.render.block.vanillaold.AsyncCactusDiscovery
import mods.betterfoliage.render.block.vanillaold.AsyncLogDiscovery
import mods.betterfoliage.render.block.vanillaold.RenderAlgae
import mods.betterfoliage.render.block.vanillaold.RenderCactus
import mods.betterfoliage.render.block.vanillaold.RenderConnectedGrass
import mods.betterfoliage.render.block.vanillaold.RenderConnectedGrassLog
import mods.betterfoliage.render.block.vanillaold.RenderCoral
import mods.betterfoliage.render.block.vanillaold.RenderGrass
import mods.betterfoliage.render.block.vanillaold.RenderLeaves
import mods.betterfoliage.render.block.vanillaold.RenderLilypad
import mods.betterfoliage.render.block.vanillaold.RenderLog
import mods.betterfoliage.render.block.vanillaold.RenderMycelium
import mods.betterfoliage.render.block.vanillaold.RenderNetherrack
import mods.betterfoliage.render.block.vanillaold.RenderReeds
import mods.betterfoliage.texture.AsyncGrassDiscovery
import mods.betterfoliage.texture.AsyncLeafDiscovery
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.resource.IConfigChangeListener
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.LeafWindTracker
import mods.betterfoliage.render.block.vanilla.StandardDirtDiscovery
import mods.betterfoliage.render.block.vanilla.StandardDirtKey
import mods.betterfoliage.render.block.vanilla.StandardGrassDiscovery
import mods.betterfoliage.render.block.vanilla.StandardGrassModel
import mods.betterfoliage.render.block.vanilla.StandardLeafDiscovery
import mods.betterfoliage.render.block.vanilla.StandardLeafModel
import mods.betterfoliage.render.lighting.AoSideHelper
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.BlockTypeCache
import mods.betterfoliage.resource.generated.GeneratedTexturePack
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
/**
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
* except for the call hooks.
*/
object Client {
var renderers = emptyList<RenderDecorator>()
var configListeners = emptyList<IConfigChangeListener>()
val asyncPack = GeneratedTexturePack("bf_gen", "Better Foliage generated assets")
var blockTypes = BlockTypeCache()
val suppressRenderErrors = mutableSetOf<BlockState>()
fun init() {
// init renderers
renderers = listOf(
RenderGrass(),
RenderMycelium(),
RenderLeaves(),
RenderCactus(),
RenderLilypad(),
RenderReeds(),
RenderAlgae(),
RenderCoral(),
RenderLog(),
RenderNetherrack(),
RenderConnectedGrass(),
RenderConnectedGrassLog()
)
// discoverers
BetterFoliageMod.bus.register(BakeWrapperManager)
listOf(
StandardLeafDiscovery,
StandardGrassDiscovery,
StandardDirtDiscovery
).forEach {
BakeWrapperManager.discoverers.add(it)
}
// init other singletons
// init singletons
val singletons = listOf(
AoSideHelper,
BlockConfig,
ChunkOverlayManager,
LeafWindTracker,
RisingSoulTextures
LeafWindTracker
)
val modelSingletons = listOf(
StandardLeafModel.Companion,
StandardGrassModel.Companion
)
// init mod integrations
val integrations = listOf(
ShadersModIntegration,
OptifineCustomColors
// ForestryIntegration,
// IC2RubberIntegration,
// TechRebornRubberIntegration
)
LeafParticleRegistry.init()
// add basic block support instances as last
AsyncLeafDiscovery.init()
AsyncGrassDiscovery.init()
AsyncLogDiscovery.init()
AsyncCactusDiscovery.init()
configListeners = listOf(renderers, singletons, integrations).flatten().filterIsInstance<IConfigChangeListener>()
configListeners.forEach { it.onConfigChange() }
}
}

View File

@@ -1,27 +1,34 @@
package mods.octarinecore
import mods.betterfoliage.util.ClassRef
import mods.betterfoliage.util.ClassRef.Companion.float
import mods.betterfoliage.util.ClassRef.Companion.void
import mods.betterfoliage.util.FieldRef
import mods.betterfoliage.util.MethodRef
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.pipeline.BlockInfo
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.*
// Java
val String = ClassRef<String>("java.lang.String")
val Map = ClassRef<Map<*, *>>("java.util.Map")
val List = ClassRef<List<*>>("java.util.List")
val Random = ClassRef<Random>("java.util.Random")
fun <K, V> mapRef() = ClassRef<Map<K, V>>("java.util.Map")
fun <K, V> mapRefMutable() = ClassRef<MutableMap<K, V>>("java.util.Map")
// Minecraft
val IBlockReader = ClassRef<IBlockReader>("net.minecraft.world.IBlockReader")
@@ -38,6 +45,19 @@ val BlockRendererDispatcher = ClassRef<BlockRendererDispatcher>("net.minecraft.c
val ChunkRenderCache = ClassRef<ChunkRenderCache>("net.minecraft.client.renderer.chunk.ChunkRenderCache")
val ResourceLocation = ClassRef<ResourceLocation>("net.minecraft.util.ResourceLocation")
val BakedQuad = ClassRef<BakedQuad>("net.minecraft.client.renderer.model.BakedQuad")
val BlockModelRenderer = ClassRef<BlockModelRenderer>("net.minecraft.client.renderer.BlockModelRenderer")
val VertexLighterFlat = ClassRef<VertexLighterFlat>("net.minecraftforge.client.model.pipeline.VertexLighterFlat")
val BlockInfo = ClassRef<BlockInfo>("net.minecraftforge.client.model.pipeline.BlockInfo")
val VertexLighterFlat_blockInfo = FieldRef(VertexLighterFlat, "blockInfo", BlockInfo)
val BlockInfo_shx = FieldRef(BlockInfo, "shx", float)
val BlockInfo_shy = FieldRef(BlockInfo, "shy", float)
val BlockInfo_shz = FieldRef(BlockInfo, "shz", float)
object ModelBakery : ClassRef<ModelBakery>("net.minecraft.client.renderer.model.ModelBakery") {
val unbakedModels = FieldRef(this, "unbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable<ResourceLocation, IUnbakedModel>())
}
// Optifine
val OptifineClassTransformer = ClassRef<Any>("optifine.OptiFineClassTransformer")

View File

@@ -0,0 +1,11 @@
package mods.betterfoliage
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.Event
data class ModelDefinitionsLoadedEvent(
val bakery: ModelBakery
) : Event()

View File

@@ -1,128 +1,49 @@
@file:JvmName("Hooks")
package mods.betterfoliage
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.EntityFallingLeavesFX
import mods.betterfoliage.render.EntityRisingSoulFX
import mods.betterfoliage.render.block.vanillaold.LogRegistry
import mods.betterfoliage.render.canRenderInLayer
import mods.betterfoliage.render.down1
import mods.betterfoliage.render.isCutout
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.old.BasicBlockCtx
import mods.betterfoliage.render.old.CachedBlockCtx
import mods.betterfoliage.render.old.NonNullWorld
import mods.betterfoliage.render.old.RenderCtx
import mods.betterfoliage.render.lighting.DefaultLightingCtx
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.util.ThreadLocalDelegate
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.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.Direction
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.ILightReader
import net.minecraft.world.World
import net.minecraftforge.client.model.data.IModelData
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();
// 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));
// return original || (Config.enabled && Config.roundLogs.enabled && BlockConfig.logBlocks.matchesClass(state.block));
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.fallingLeaves.enabled &&
BlockConfig.leafBlocks.matchesClass(state.block) &&
world.isAirBlock(pos + down1) &&
Math.random() < Config.fallingLeaves.chance) {
EntityFallingLeavesFX(world, pos).addIfValid()
}
// 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.fallingLeaves.enabled &&
// BlockConfig.leafBlocks.matchesClass(state.block) &&
// world.isAirBlock(pos + down1) &&
// Math.random() < Config.fallingLeaves.chance) {
// EntityFallingLeavesFX(world, pos).addIfValid()
// }
}
fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos, dir: Direction): VoxelShape {
if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
// if (LogRegistry[state, reader, pos] != null) return VoxelShapes.empty()
return state.getFaceOcclusionShape(reader, pos, dir)
}
val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) }
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
state: BlockState,
pos: BlockPos,
reader: ILightReader,
matrixStack: MatrixStack,
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
modelData: IModelData,
layer: RenderType
): Boolean {
// build context
val blockCtx = CachedBlockCtx(reader, pos)
val renderCtx = RenderCtx(dispatcher, buffer, matrixStack, layer, checkSides, random, modelData)
lightingCtx.reset(blockCtx)
val combinedCtx = CombinedContext(blockCtx, renderCtx, lightingCtx)
combinedCtx.render()
return combinedCtx.hasRendered
// loop render decorators
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(
otherCutoutLayer
))
Client.renderers.forEach { renderer ->
if (renderer.isEligible(combinedCtx)) {
// render on the block's default layer
// also render on the cutout layer if the renderer requires it
val doCutoutRender = renderer.renderOnCutout && layer == targetCutoutLayer
val stopRender = renderer.onlyOnCutout && !layer.isCutout
if ((doBaseRender || doCutoutRender) && !stopRender) {
renderer.render(combinedCtx)
return combinedCtx.hasRendered
}
}
}
// no render decorators have taken on this block, proceed to normal rendering
combinedCtx.render()
return combinedCtx.hasRendered
}
fun canRenderInLayerOverride(state: BlockState, layer: RenderType) = state.canRenderInLayer(layer) || layer == targetCutoutLayer
fun canRenderInLayerOverrideOptifine(state: BlockState, optifineReflector: Any?, layerArray: Array<Any>) =
canRenderInLayerOverride(state, layerArray[0] as RenderType)
val targetCutoutLayer: RenderType get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) RenderType.getCutoutMipped() else RenderType.getCutout()
val otherCutoutLayer: RenderType get() = if (Minecraft.getInstance().gameSettings.mipmapLevels > 0) RenderType.getCutout() else RenderType.getCutoutMipped()

View File

@@ -1,7 +1,5 @@
package mods.betterfoliage.render.old
package mods.betterfoliage.chunk
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset
@@ -9,15 +7,12 @@ import mods.betterfoliage.util.plus
import mods.betterfoliage.util.semiRandom
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IWorldReader
import net.minecraft.world.biome.Biome
import net.minecraftforge.client.model.data.IModelData
import java.util.*
import net.minecraft.world.level.ColorResolver
/**
* Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
@@ -38,6 +33,8 @@ interface BlockCtx {
val isNormalCube: Boolean get() = state.isNormalCube(world, pos)
fun isNeighborSolid(dir: Direction) = offset(dir).let { it.state.isSolidSide(it.world, it.pos, dir.opposite) }
fun shouldSideBeRendered(side: Direction) = Block.shouldSideBeRendered(state, world, pos, side)
/** Get a semi-random value based on the block coordinate and the given seed. */
@@ -45,36 +42,14 @@ interface BlockCtx {
/** Get an array of semi-random values based on the block coordinate. */
fun semiRandomArray(num: Int): Array<Int> = Array(num) { semiRandom(it) }
fun color(resolver: ColorResolver) = world.getBlockColor(pos, resolver)
}
open class BasicBlockCtx(
class BasicBlockCtx(
override val world: ILightReader,
override val pos: BlockPos
) : BlockCtx {
override var state: BlockState = world.getBlockState(pos)
protected set
override val state: BlockState = world.getBlockState(pos)
override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset)
fun cache() = CachedBlockCtx(world, pos)
}
open class CachedBlockCtx(world: ILightReader, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome? = super.biome
override fun state(dir: Direction) = neighbors[dir.ordinal]
}
data class RenderCtx(
val dispatcher: BlockRendererDispatcher,
val renderBuffer: IVertexBuilder,
val matrixStack: MatrixStack,
val layer: RenderType,
val checkSides: Boolean,
val random: Random,
val modelData: IModelData
) {
fun render(worldBlock: BlockCtx) =
// dispatcher.renderBlock(worldBlock.state, worldBlock.pos, worldBlock.world, renderBuffer, random, modelData)
dispatcher.renderModel(worldBlock.state, worldBlock.pos, worldBlock.world, matrixStack, renderBuffer, checkSides, random, modelData)
}

View File

@@ -1,7 +1,6 @@
package mods.betterfoliage.chunk
import mods.octarinecore.ChunkCacheOF
import mods.betterfoliage.render.old.BlockCtx
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isInstance
import net.minecraft.client.renderer.chunk.ChunkRenderCache

View File

@@ -5,9 +5,17 @@ import mods.betterfoliage.BetterFoliageMod
import net.minecraft.util.ResourceLocation
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import java.util.Random
private fun featureEnable() = boolean(true).lang("enabled")
abstract class PopulationConfigCategory() : ConfigCategory() {
abstract val enabled: Boolean
abstract val population: Int
fun enabled(random: Random) = random.nextInt(64) < population && enabled
}
// Config singleton
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_ID) {
@@ -24,22 +32,22 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_I
val hideInternal by boolean(true)
}
object shortGrass : ConfigCategory(){
val grassEnabled by boolean(true)
object shortGrass : PopulationConfigCategory(){
override val enabled by featureEnable()
val myceliumEnabled by boolean(true)
val snowEnabled by boolean(true)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=0.1, max=2.5, default=0.6).lang("heightMin")
val heightMax by double(min=0.1, max=2.5, default=0.8).lang("heightMax")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val population by int(max=64, default=64).lang("population")
override val population by int(max=64, default=64).lang("population")
val useGenerated by boolean(false)
val shaderWind by boolean(true).lang("shaderWind")
val saturationThreshold by double(default=0.1)
}
object connectedGrass : ConfigCategory(){
val enabled by boolean(true)
val enabled by featureEnable()
val snowEnabled by boolean(false)
}
@@ -63,44 +71,44 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.MOD_I
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
}
object lilypad : ConfigCategory(){
val enabled by featureEnable()
object lilypad : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val flowerChance by int(max=64, default=16, min=0)
override val population by int(max=64, default=16, min=0)
}
object reed : ConfigCategory(){
val enabled by featureEnable()
object reed : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
val population by int(max=64, default=32).lang("population")
override val population by int(max=64, default=32).lang("population")
val minBiomeTemp by double(default=0.4)
val minBiomeRainfall by double(default=0.4)
// val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
val shaderWind by boolean(true).lang("shaderWind")
}
object algae : ConfigCategory(){
val enabled by featureEnable()
object algae : PopulationConfigCategory(){
override val enabled by featureEnable()
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
val population by int(max=64, default=48).lang("population")
override val population by int(max=64, default=48).lang("population")
// val biomes by biomeList { it.filterClass("river", "ocean") }
val shaderWind by boolean(true).lang("shaderWind")
}
object coral : ConfigCategory(){
val enabled by featureEnable()
object coral : PopulationConfigCategory(){
override val enabled by featureEnable()
val shallowWater by boolean(false)
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
val size by double(min=0.5, max=1.5, default=0.7).lang("size")
val crustSize by double(min=0.5, max=1.5, default=1.4)
val chance by int(max=64, default=32)
val population by int(max=64, default=48).lang("population")
override val population by int(max=64, default=48).lang("population")
// val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
}
@@ -154,8 +162,8 @@ object BlockConfig {
val lilypad = blocks("lilypad_default.cfg")
init { BetterFoliageMod.bus.register(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(BetterFoliage.logDetail, ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun blocks(cfgName: String) = ConfigurableBlockMatcher(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
private fun models(cfgName: String) = ModelTextureListConfiguration(ResourceLocation(BetterFoliageMod.MOD_ID, cfgName)).apply { list.add(this) }
@SubscribeEvent
fun onConfig(event: ModConfig.ModConfigEvent) {

View File

@@ -1,11 +1,12 @@
package mods.betterfoliage.config
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.getJavaClass
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.getJavaClass
import net.minecraft.block.Block
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.Level.DEBUG
interface IBlockMatcher {
fun matchesClass(block: Block): Boolean
@@ -22,7 +23,8 @@ class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
}
}
class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocation) : IBlockMatcher {
class ConfigurableBlockMatcher(val location: ResourceLocation) : IBlockMatcher {
val logger = BetterFoliageMod.detailLogger(this)
val blackList = mutableListOf<Class<*>>()
val whiteList = mutableListOf<Class<*>>()
@@ -46,7 +48,7 @@ class ConfigurableBlockMatcher(val logger: Logger, val location: ResourceLocatio
blackList.clear()
whiteList.clear()
resourceManager.getAllResources(location).forEach { resource ->
logger.debug("Reading resource $location from pack ${resource.packName}")
logger.log(DEBUG, "Reading resource $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) }
@@ -60,11 +62,13 @@ data class ModelTextureList(val modelLocation: ResourceLocation, val textureName
constructor(vararg args: String) : this(ResourceLocation(args[0]), listOf(*args).drop(1))
}
class ModelTextureListConfiguration(val logger: Logger, val location: ResourceLocation) {
class ModelTextureListConfiguration(val location: ResourceLocation) {
val logger = BetterFoliageMod.detailLogger(this)
val modelList = mutableListOf<ModelTextureList>()
fun readDefaults() {
resourceManager.getAllResources(location).forEach { resource ->
logger.debug("Reading resource $location from pack ${resource.packName}")
logger.log(DEBUG, "Reading resource $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,142 +0,0 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.render.block.vanillaold.AsyncLogDiscovery
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.discovery.ModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.ModelRenderRegistry
import mods.betterfoliage.texture.LeafInfo
import mods.betterfoliage.texture.defaultRegisterLeaf
import mods.betterfoliage.util.ClassRef
import mods.octarinecore.Map
import mods.octarinecore.ResourceLocation
import mods.octarinecore.String
import mods.octarinecore.client.resource.*
import mods.betterfoliage.util.ClassRef.Companion.boolean
import mods.betterfoliage.util.FieldRef
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.MethodRef
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.resources.IResourceManager
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level
import java.util.concurrent.CompletableFuture
import kotlin.collections.component1
import kotlin.collections.component2
val TextureLeaves = ClassRef<Any>("forestry.arboriculture.models.TextureLeaves")
val TextureLeaves_leafTextures = FieldRef(TextureLeaves, "leafTextures", Map)
val TextureLeaves_plain = FieldRef(TextureLeaves, "plain", ResourceLocation)
val TextureLeaves_fancy = FieldRef(TextureLeaves, "fancy", ResourceLocation)
val TextureLeaves_pollinatedPlain = FieldRef(TextureLeaves, "pollinatedPlain", ResourceLocation)
val TextureLeaves_pollinatedFancy = FieldRef(TextureLeaves, "pollinatedFancy", ResourceLocation)
val TileLeaves = ClassRef<Any>("forestry.arboriculture.tiles.TileLeaves")
val TileLeaves_getLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", ResourceLocation, boolean)
val PropertyWoodType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyWoodType")
val IWoodType = ClassRef<Any>("forestry.api.arboriculture.IWoodType")
val IWoodType_barkTex = MethodRef(IWoodType, "getBarkTexture", String)
val IWoodType_heartTex = MethodRef(IWoodType, "getHeartTexture", String)
val PropertyTreeType = ClassRef<Any>("forestry.arboriculture.blocks.PropertyTreeType")
val IAlleleTreeSpecies = ClassRef<Any>("forestry.api.arboriculture.IAlleleTreeSpecies")
val ILeafSpriteProvider = ClassRef<Any>("forestry.api.arboriculture.ILeafSpriteProvider")
val TreeDefinition = ClassRef<Any>("forestry.arboriculture.genetics.TreeDefinition")
val IAlleleTreeSpecies_getLeafSpriteProvider = MethodRef(IAlleleTreeSpecies, "getLeafSpriteProvider", ILeafSpriteProvider)
val TreeDefinition_species = FieldRef(TreeDefinition, "species", IAlleleTreeSpecies)
val ILeafSpriteProvider_getSprite = MethodRef(ILeafSpriteProvider, "getSprite", ResourceLocation, boolean, boolean)
object ForestryIntegration {
init {
if (ModList.get().isLoaded("forestry") && allAvailable(TileLeaves_getLeaveSprite, IAlleleTreeSpecies_getLeafSpriteProvider, ILeafSpriteProvider_getSprite)) {
// Just keep it inactive for now until Forestry updates
}
}
}
object ForestryLeafDiscovery : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<LeafInfo> {
override val logger = BetterFoliage.logDetail
var idToValue = emptyMap<Identifier, LeafInfo>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos): LeafInfo? {
// check variant property (used in decorative leaves)
state.values.entries.find {
PropertyTreeType.isInstance(it.key) && TreeDefinition.isInstance(it.value)
} ?.let {
val species = it.value[TreeDefinition_species]
val spriteProvider = species[IAlleleTreeSpecies_getLeafSpriteProvider]()
val textureLoc = spriteProvider[ILeafSpriteProvider_getSprite](false, Minecraft.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
// extract leaf texture information from TileEntity
val tile = world.getTileEntity(pos) ?: return null
if (!TileLeaves.isInstance(tile)) return null
val textureLoc = tile[TileLeaves_getLeaveSprite](Minecraft.isFancyGraphicsEnabled())
return idToValue[textureLoc]
}
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlasFuture: AtlasFuture): StitchPhases {
val futures = mutableMapOf<Identifier, CompletableFuture<LeafInfo>>()
return StitchPhases(
discovery = bakeryF.thenRunAsync {
val allLeaves = TextureLeaves_leafTextures.getStatic()
allLeaves.entries.forEach { (type, leaves) ->
log("base leaf type $type")
leaves!!
listOf(
leaves[TextureLeaves_plain], leaves[TextureLeaves_pollinatedPlain],
leaves[TextureLeaves_fancy], leaves[TextureLeaves_pollinatedFancy]
).forEach { textureLocation ->
futures[textureLocation] = defaultRegisterLeaf(textureLocation, atlasFuture)
}
}
},
cleanup = atlasFuture.runAfter {
idToValue = futures.mapValues { it.value.get() }
}
)
}
}
object ForestryLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// respect class list to avoid triggering on fences, stairs, etc.
if (!BlockConfig.logBlocks.matchesClass(ctx.state.block)) return null
// find wood type property
val woodType = ctx.state.values.entries.find {
PropertyWoodType.isInstance(it.key) && IWoodType.isInstance(it.value)
}
if (woodType != null) {
logger.log(Level.DEBUG, "ForestryLogRegistry: block state ${ctx.state}")
logger.log(Level.DEBUG, "ForestryLogRegistry: variant ${woodType.value}")
// get texture names for wood type
val bark = woodType.value[IWoodType_barkTex]()
val heart = woodType.value[IWoodType_heartTex]()
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
val heartSprite = atlas.sprite(heart)
val barkSprite = atlas.sprite(bark)
return atlas.mapAfter {
SimpleColumnInfo(AsyncLogDiscovery.getAxis(ctx.state), heartSprite.get(), heartSprite.get(), listOf(barkSprite.get()))
}
}
return null
}
}

View File

@@ -1,39 +1,44 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.octarinecore.*
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.reflectField
import mods.octarinecore.BlockPos
import mods.octarinecore.BlockState
import mods.octarinecore.CustomColors
import mods.octarinecore.RenderEnv
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.util.Direction.UP
import net.minecraft.util.math.BlockPos
import org.apache.logging.log4j.Level
import net.minecraft.world.level.ColorResolver
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.LogManager
/**
* Integration for OptiFine custom block colors.
*/
@Suppress("UNCHECKED_CAST")
object OptifineCustomColors {
val logger = LogManager.getLogger(this)
val isColorAvailable = allAvailable(CustomColors, CustomColors.getColorMultiplier)
init {
BetterFoliage.log(Level.INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
logger.log(INFO, "Optifine custom color support is ${if (isColorAvailable) "enabled" else "disabled" }")
}
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true)
fun getBlockColor(ctx: CombinedContext): Int {
fun getBlockColor(ctx: BlockCtx, resolver: ColorResolver): Int {
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.state, ctx.pos)
CustomColors.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
} else null
return if (ofColor == null || ofColor == -1) ctx.lightingCtx.color else ofColor
return if (ofColor == null || ofColor == -1) ctx.color(resolver) else ofColor
}
}

View File

@@ -1,164 +0,0 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.resource.Sprite
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.lighting.QuadIconResolver
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.resource.discovery.ModelDiscovery
import mods.betterfoliage.resource.discovery.ModelDiscoveryContext
import mods.betterfoliage.resource.discovery.derivesFrom
import mods.octarinecore.client.resource.*
import mods.betterfoliage.util.rotate
import mods.betterfoliage.util.ClassRef
import mods.betterfoliage.util.allAvailable
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation
import net.minecraftforge.fml.ModList
import java.util.concurrent.CompletableFuture
object IC2RubberIntegration {
val BlockRubWood = ClassRef<Any>("ic2.core.block.BlockRubWood")
init {
if (ModList.get().isLoaded("ic2") && allAvailable(BlockRubWood)) {
// keep it inactive for now until IC2 updates
// BetterFoliage.log(Level.INFO, "IC2 rubber support initialized")
// LogRegistry.registries.add(IC2LogDiscovery)
// BetterFoliage.blockSprites.providers.add(IC2LogDiscovery)
}
}
}
// Probably unneeded, as TechReborn went Fabric-only
/*
object TechRebornRubberIntegration {
val BlockRubberLog = ClassRef<Any>("techreborn.blocks.BlockRubberLog")
init {
if (ModList.get().isLoaded("techreborn") && allAvailable(BlockRubberLog)) {
BetterFoliage.log(Level.INFO, "TechReborn rubber support initialized")
LogRegistry.registries.add(TechRebornLogDiscovery)
BetterFoliage.blockSprites.providers.add(TechRebornLogDiscovery)
}
}
}
*/
class RubberLogInfo(
axis: Axis?,
val spotDir: Direction,
topTexture: Sprite,
bottomTexture: Sprite,
val spotTexture: Sprite,
sideTextures: List<Sprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
if (worldFace == spotDir) spotTexture else {
val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0
this.sideTextures[sideIdx]
}
}
}
object IC2LogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock, and "state" blockstate property
if (!IC2RubberIntegration.BlockRubWood.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.firstOrNull() as Pair<BlockModel, ResourceLocation> ?: return null
val type = ctx.state.values.entries.find { it.key.getName() == "state" }?.value?.toString() ?: return null
// logs with no rubber spot
if (blockLoc.derivesFrom(ResourceLocation("block/cube_column"))) {
val axis = when(type) {
"plain_y" -> Axis.Y
"plain_x" -> Axis.X
"plain_z" -> Axis.Z
else -> null
}
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it).textureLocation }
if (textureNames.any { it == MissingTextureSprite.getLocation() }) return null
log("IC2LogSupport: block state ${ctx.state.toString()}")
log("IC2LogSupport: axis=$axis, end=${textureNames[0]}, side=${textureNames[1]}")
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(axis, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
// logs with rubber spot
val spotDir = when(type) {
"dry_north", "wet_north" -> NORTH
"dry_south", "wet_south" -> SOUTH
"dry_west", "wet_west" -> WEST
"dry_east", "wet_east" -> EAST
else -> null
}
val textureNames = listOf("up", "down", "north", "south").map { blockLoc.first.resolveTextureName(it).textureLocation }
if (textureNames.any { it == MissingTextureSprite.getLocation() }) return null
log("IC2LogSupport: block state ${ctx.state}")
log("IC2LogSupport: spotDir=$spotDir, up=${textureNames[0]}, down=${textureNames[1]}, side=${textureNames[2]}, spot=${textureNames[3]}")
val upSprite = atlas.sprite(textureNames[0])
val downSprite = atlas.sprite(textureNames[1])
val sideSprite = atlas.sprite(textureNames[2])
val spotSprite = atlas.sprite(textureNames[3])
return if (spotDir != null) atlas.mapAfter {
RubberLogInfo(Axis.Y, spotDir, upSprite.get(), downSprite.get(), spotSprite.get(), listOf(sideSprite.get()))
} else atlas.mapAfter {
SimpleColumnInfo(Axis.Y, upSprite.get(), downSprite.get(), listOf(sideSprite.get()))
}
}
}
/*
object TechRebornLogDiscovery : ModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
// check for proper block class, existence of ModelBlock
if (!TechRebornRubberIntegration.BlockRubberLog.isInstance(ctx.state.block)) return null
val blockLoc = ctx.models.map { it as? Pair<BlockModel, ResourceLocation> }.firstOrNull() ?: return null
val hasSap = ctx.state.values.entries.find { it.key.getName() == "hassap" }?.value as? Boolean ?: return null
val sapSide = ctx.state.values.entries.find { it.key.getName() == "sapside" }?.value as? Direction ?: return null
log("$logName: block state ${ctx.state}")
if (hasSap) {
val textureNames = listOf("end", "side", "sapside").map { blockLoc.first.resolveTextureName(it) }
log("$logName: spotDir=$sapSide, end=${textureNames[0]}, side=${textureNames[2]}, spot=${textureNames[3]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
val sapSprite = atlas.sprite(textureNames[2])
return atlas.mapAfter {
RubberLogInfo(Axis.Y, sapSide, endSprite.get(), endSprite.get(), sapSprite.get(), listOf(sideSprite.get()))
}
}
} else {
val textureNames = listOf("end", "side").map { blockLoc.first.resolveTextureName(it) }
log("$logName: end=${textureNames[0]}, side=${textureNames[1]}")
if (textureNames.all { it != "missingno" }) {
val endSprite = atlas.sprite(textureNames[0])
val sideSprite = atlas.sprite(textureNames[1])
return atlas.mapAfter {
SimpleColumnInfo(Axis.Y, endSprite.get(), endSprite.get(), listOf(sideSprite.get()))
}
}
}
return null
}
}
*/

View File

@@ -1,16 +1,15 @@
package mods.betterfoliage.integration
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.texture.LeafRegistry
import mods.octarinecore.*
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.allAvailable
import mods.betterfoliage.util.get
import mods.octarinecore.*
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import org.apache.logging.log4j.Level.INFO
@@ -18,8 +17,7 @@ import org.apache.logging.log4j.Level.INFO
/**
* Integration for ShadersMod.
*/
object ShadersModIntegration {
object ShadersModIntegration : HasLogger() {
@JvmStatic val isAvailable = allAvailable(SVertexBuilder, SVertexBuilder.pushState, SVertexBuilder.popState, BlockAliases.getAliasBlockId)
val defaultLeaves = Blocks.OAK_LEAVES.defaultState
@@ -30,22 +28,18 @@ object ShadersModIntegration {
* @see mods.betterfoliage.loader.BetterFoliageTransformer
*/
@JvmStatic fun getBlockStateOverride(state: BlockState, world: ILightReader, pos: BlockPos): BlockState {
if (LeafRegistry[state, world, pos] != null) return defaultLeaves
if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
// if (LeafRegistry[state, world, pos] != null) return defaultLeaves
// if (BlockConfig.crops.matchesClass(state.block)) return defaultGrass
return state
}
init {
BetterFoliage.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
logger.log(INFO, "ShadersMod integration is ${if (isAvailable) "enabled" else "disabled" }")
}
inline fun renderAs(ctx: CombinedContext, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, ctx.state, renderType, enabled, func)
/** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(ctx: CombinedContext, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
inline fun renderAs(buffer: BufferBuilder, state: BlockState, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) {
if (isAvailable && enabled) {
val buffer = ctx.renderCtx.renderBuffer
val aliasBlockId = BlockAliases.getAliasBlockId.invokeStatic(state)
val sVertexBuilder = buffer[BufferBuilder_sVertexBuilder]
SVertexBuilder.pushState.invoke(sVertexBuilder, aliasBlockId)
@@ -57,10 +51,10 @@ object ShadersModIntegration {
}
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultGrass, MODEL, enabled, func)
inline fun grass(buffer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(buffer, defaultGrass, MODEL, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(ctx, defaultLeaves, MODEL, enabled, func)
inline fun leaves(buffer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(buffer, defaultLeaves, MODEL, enabled, func)
}

View File

@@ -1,10 +1,8 @@
package mods.betterfoliage.render
import mods.betterfoliage.config.Config
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.texture.LeafRegistry
import mods.betterfoliage.render.old.AbstractEntityFX
import mods.betterfoliage.render.lighting.HSB
import mods.betterfoliage.render.old.HSB
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax
@@ -18,7 +16,7 @@ import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.*
import java.util.Random
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
@@ -47,15 +45,15 @@ class EntityFallingLeavesFX(
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
val leafInfo = LeafRegistry[state, world, pos]
// val leafInfo = LeafRegistry[state, world, pos]
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
if (leafInfo != null) {
sprite = leafInfo.particleTextures[rand.nextInt(1024)]
calculateParticleColor(leafInfo.averageColor, blockColor)
} else {
sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
setColor(blockColor)
}
// if (leafInfo != null) {
// sprite = leafInfo.particleTextures[rand.nextInt(1024)]
// calculateParticleColor(leafInfo.averageColor, blockColor)
// } else {
// sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
// setColor(blockColor)
// }
}
override val isValid: Boolean get() = (sprite != null)

View File

@@ -2,13 +2,10 @@ package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.AbstractEntityFX
import mods.betterfoliage.resource.ResourceHandler
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
@@ -23,7 +20,7 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
init {
motionY = 0.1
particleGravity = 0.0f
sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
// sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
@@ -63,7 +60,7 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
}
}
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"))
}
//object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
// val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
// val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track"))
//}

View File

@@ -1,158 +0,0 @@
@file:JvmName("ModelColumn")
package mods.betterfoliage.render
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.CornerSingleFallback
import mods.betterfoliage.render.lighting.EdgeInterpolateFallback
import mods.betterfoliage.render.lighting.FaceCenter
import mods.betterfoliage.render.lighting.FaceFlat
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.cornerInterpolate
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.lighting.faceOrientedInterpolate
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.UV
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.exchange
import net.minecraft.util.Direction.*
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
const val chamferAffinity = 0.9f
/** Amount to shrink column extension bits to stop Z-fighting. */
val zProtectionScale: Double3 get() = Double3(Config.roundLogs.zProtection, 1.0, Config.roundLogs.zProtection)
fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
val halfRadius = radius * 0.5
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5 - radius, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0, maxU = 0.5 - radius)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.5 - radius)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))
)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
predicate = { v, vi -> vi == 1 || vi == 2}
)
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
listOf(
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = radius - 0.5)
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))
)
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
predicate = { v, vi -> vi == 0 || vi == 3}
),
verticalRectangle(x1 = 0.5, z1 = 0.5 - radius, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(minU = radius - 0.5, maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach { transform(it.setFlatShader(FaceFlat(EAST))).add() }
quads.exchange(1, 2)
}
/**
* Create a model of the side of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnSideSquare(yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5, z1 = 0.5, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach {
transform(it.setFlatShader(faceOrientedAuto(corner = cornerFlat))).add()
}
}
/**
* Create a model of the top lid of a chamfered column quadrant.
*
* @param[radius] the chamfer radius
* @param[transform] transformation to apply to the model
*/
fun Model.columnLid(radius: Double, transform: (Quad)-> Quad = { it }) {
val v1 = Vertex(Double3(0.0, 0.5, 0.0), UV(0.0, 0.0))
val v2 = Vertex(Double3(0.0, 0.5, 0.5), UV(0.0, 0.5))
val v3 = Vertex(Double3(0.5 - radius, 0.5, 0.5), UV(0.5 - radius, 0.5))
val v4 = Vertex(Double3(0.5 - radius * 0.5, 0.5, 0.5 - radius * 0.5), UV(0.5, 0.5))
val v5 = Vertex(Double3(0.5, 0.5, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, 0.5, 0.0), UV(0.5, 0.0))
val q1 = Quad(v1, v2, v3, v4).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
val q2 = Quad(v1, v4, v5, v6).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
3 -> EdgeInterpolateFallback(UP, EAST, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() }
}
/**
* Create a model of the top lid of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) {
transform(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5)
.transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
2 -> CornerSingleFallback(UP, SOUTH, EAST, UP)
else -> EdgeInterpolateFallback(UP, EAST, 0.0)
}) }
.setFlatShader(FaceFlat(UP))
).add()
}
/**
* Transform a chamfered side quadrant model of a column that extends from the top of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the top
*/
fun topExtension(size: Double) = { q: Quad ->
q.clampUV(minV = 0.5 - size).transformVI { vertex, idx ->
if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}
/**
* Transform a chamfered side quadrant model of a column that extends from the bottom of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the bottom
*/
fun bottomExtension(size: Double) = { q: Quad ->
q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx ->
if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}

View File

@@ -0,0 +1,48 @@
package mods.betterfoliage.render
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.WeightedRandom
import java.util.Random
import java.util.function.Function
interface ISpecialRenderModel : IBakedModel {
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
}
open class SpecialRenderWrapper(val baseModel: IBakedModel) : IBakedModel by baseModel, ISpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.renderFallback(baseModel)
}
}
/**
* If any of the variants in this [VariantList] bake to [ISpecialRenderModel], give back a
* [SpecialRenderVariantList] so that variants can take advantage of extra features.
* Otherwise, give back null.
*/
fun VariantList.bakeSpecial(bakery: ModelBakery, spriteGetter: Function<Material, TextureAtlasSprite>): SpecialRenderVariantList? {
val bakedModels = variantList.map { bakery.getBakedModel(it.modelLocation, it, spriteGetter) }
if (bakedModels.all { it !is ISpecialRenderModel }) return null
val weightedItems = (variantList zip bakedModels)
.filter { it.second != null }
.map { (variant, model) ->
val modelWrapped = (model!! as? ISpecialRenderModel) ?: SpecialRenderWrapper(model)
SpecialRenderVariantList.WeightedModel(modelWrapped, variant.weight)
}
return SpecialRenderVariantList(weightedItems, weightedItems[0].model)
}
open class SpecialRenderVariantList(
val models: List<WeightedModel>, baseModel: ISpecialRenderModel
): IBakedModel by baseModel, ISpecialRenderModel {
class WeightedModel(val model: ISpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
val totalWeight = models.sumBy { it.itemWeight }
fun getModel(random: Random) = WeightedRandom.getRandomItem(models, random.nextInt(totalWeight))
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) = getModel(ctx.random).model.render(ctx, noDecorations)
}

View File

@@ -1,78 +0,0 @@
@file:JvmName("Utils")
package mods.betterfoliage.render
import mods.betterfoliage.render.lighting.PostProcessLambda
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.times
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.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.EAST
import net.minecraft.util.Direction.NORTH
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.UP
import net.minecraft.util.Direction.WEST
import kotlin.math.cos
import kotlin.math.sin
val up1 = Int3(1 to UP)
val up2 = Int3(2 to UP)
val down1 = Int3(1 to DOWN)
val snowOffset = UP * 0.0625
val normalLeavesRot = arrayOf(Rotation.identity)
val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Rotation.rot90[SOUTH.ordinal])
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW }
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT)
fun Quad.toCross(rotAxis: Direction, trans: (Quad) -> Quad) =
(0..3).map { rotIdx ->
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
}
fun Quad.toCross(rotAxis: Direction) = toCross(rotAxis) { it }
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
val rotationFromUp = arrayOf(
Rotation.rot90[EAST.ordinal] * 2,
Rotation.identity,
Rotation.rot90[WEST.ordinal],
Rotation.rot90[EAST.ordinal],
Rotation.rot90[SOUTH.ordinal],
Rotation.rot90[NORTH.ordinal]
)
fun Model.mix(first: Model, second: Model, predicate: (Int) -> Boolean) {
first.quads.forEachIndexed { qi, quad ->
val otherQuad = second.quads[qi]
Quad(
if (predicate(0)) otherQuad.v1.copy() else quad.v1.copy(),
if (predicate(1)) otherQuad.v2.copy() else quad.v2.copy(),
if (predicate(2)) otherQuad.v3.copy() else quad.v3.copy(),
if (predicate(3)) otherQuad.v4.copy() else quad.v4.copy()
).add()
}
}
val RenderType.isCutout: Boolean get() = (this == RenderType.getCutout()) || (this == RenderType.getCutoutMipped())
fun BlockState.canRenderInLayer(layer: RenderType) = RenderTypeLookup.canRenderInLayer(this, layer)
fun BlockState.canRenderInCutout() =
RenderTypeLookup.canRenderInLayer(this, RenderType.getCutout()) ||
RenderTypeLookup.canRenderInLayer(this, RenderType.getCutoutMipped())

View File

@@ -0,0 +1,65 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.Client
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.discovery.ModelReplacer
import mods.betterfoliage.util.offset
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.UP
import net.minecraft.util.ResourceLocation
object StandardDirtDiscovery : ModelReplacer() {
val dirtBlocks = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in dirtBlocks) {
Client.blockTypes.dirt.add(state)
replacements[location] = StandardDirtKey
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
}
return super.processModel(bakery, state, location, sprites, replacements)
}
}
object StandardDirtKey : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel) = StandardDirtModel(wrapped)
}
class StandardDirtModel(
wrapped: ISpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, false)
val stateUp = ctx.offset(UP).state
val isConnectedGrass = Config.connectedGrass.enabled && stateUp in Client.blockTypes.grass
if (isConnectedGrass) {
(ctx.blockModelShapes.getModel(stateUp) as? ISpecialRenderModel)?.let { grassModel ->
ctx.renderMasquerade(UP.offset) {
grassModel.render(ctx, true)
}
return
}
return super.render(ctx, false)
}
super.render(ctx, false)
}
}

View File

@@ -0,0 +1,123 @@
package mods.betterfoliage.render.block.vanilla
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.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.old.Color
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableModelReplacer
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.model.SpriteSetDelegate
import mods.betterfoliage.resource.model.buildTufts
import mods.betterfoliage.resource.model.fullCubeTextured
import mods.betterfoliage.resource.model.fullCubeTinted
import mods.betterfoliage.resource.model.tuftModelSet
import mods.betterfoliage.resource.model.tuftShapeSet
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isSnow
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.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
object StandardGrassDiscovery : ConfigurableModelReplacer() {
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, ModelBakeKey>
): Boolean {
replacements[location] = StandardGrassKey(textureMatch[0])
Client.blockTypes.grass.add(state)
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
}
}
data class StandardGrassKey(
val grassLocation: ResourceLocation
) : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
Atlas.BLOCKS[grassLocation].logColorOverride(detailLogger, Config.shortGrass.saturationThreshold)
return StandardGrassModel(wrapped, this)
}
}
class StandardGrassModel(
wrapped: ISpecialRenderModel,
key: StandardGrassKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key)
val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
val fullBlock by grassFullBlockMeshes.delegate(key)
val upNormal = arrayOf(0.0f, 1.0f, 0.0f, 0.0f).toFloatArray()
val vanillaTuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
Client.blockTypes.run { stateBelow in grass || stateBelow in dirt }
if (connected) {
ctx.render(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
} else {
super.render(ctx, noDecorations)
}
if (Config.shortGrass.enabled(ctx.random) && !ctx.isNeighborSolid(UP)) {
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = vanillaTuftLighting }
ctx.render(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
}
}
companion object {
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
val overrideColor = Atlas.BLOCKS[key.grassLocation].getColorOverride(Config.shortGrass.saturationThreshold)
tuftModelSet(grassTuftShapes, overrideColor) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, Color.white) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTinted(key.grassLocation, Config.shortGrass.saturationThreshold) }
}
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), Color.white) }
}
}
}

View File

@@ -0,0 +1,112 @@
package mods.betterfoliage.render.block.vanilla
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.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.old.Color
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableModelReplacer
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.generated.GeneratedLeaf
import mods.betterfoliage.resource.model.SpriteSetDelegate
import mods.betterfoliage.resource.model.crossModelsRaw
import mods.betterfoliage.resource.model.crossModelsTextured
import mods.betterfoliage.resource.model.crossModelsTinted
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.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
object StandardLeafDiscovery : ConfigurableModelReplacer() {
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, ModelBakeKey>
): Boolean {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeaf(textureMatch[0], leafType)
.register(Client.asyncPack)
.apply { sprites.add(this) }
detailLogger.log(INFO, " particle $leafType")
replacements[location] = StandardLeafKey(generated, leafType)
return true
}
}
fun TextureAtlasSprite.logColorOverride(logger: Logger, threshold: Double) {
val hsb = averageColor
return if (hsb.saturation >= threshold) {
logger.log(INFO, " brightness ${hsb.brightness}")
logger.log(INFO, " saturation ${hsb.saturation} >= ${threshold}, will use texture color")
} else {
logger.log(INFO, " saturation ${hsb.saturation} < ${threshold}, will use block color")
}
}
fun TextureAtlasSprite.getColorOverride(threshold: Double) = averageColor.let {
if (it.saturation < threshold) null else it.copy(brightness = (it.brightness * 2.0f).coerceAtMost(0.9f))
}?.asColor?.let { Color(it) }
data class StandardLeafKey(
val roundLeafTexture: ResourceLocation,
val leafType: String
) : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
Atlas.BLOCKS[roundLeafTexture].logColorOverride(BetterFoliageMod.detailLogger(this), 0.1)
return StandardLeafModel(wrapped, this)
}
}
class StandardLeafModel(
model: ISpecialRenderModel,
key: StandardLeafKey
) : HalfBakedSpecialWrapper(model) {
val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = RoundLeafLighting }
ctx.render(leafNormal[ctx.random.nextInt(64)])
if (ctx.state(UP).isSnow) ctx.render(leafSnowed[ctx.random.nextInt(64)])
}
companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
}
val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
}
val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTinted(leafModelsBase[key], Config.shortGrass.saturationThreshold) { key.roundLeafTexture }
}
val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], Color.white, false) { leafSpritesSnowed[it].name }
}
}
}

View File

@@ -0,0 +1,13 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.resource.discovery.ModelBakeKey
import net.minecraft.util.Direction
import net.minecraft.util.ResourceLocation
data class RoundLogKey(
override val axis: Direction.Axis?,
val barkSprite: ResourceLocation,
val endSprite: ResourceLocation
) : ColumnBlockKey, ModelBakeKey {
}

View File

@@ -1,42 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.old.RenderDecorator
import net.minecraft.block.material.Material
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
class RenderAlgae : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val algaeIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") }
val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) }
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.algae.enabled &&
ctx.state(up2).material == Material.WATER &&
ctx.state(up1).material == Material.WATER &&
DIRT_BLOCKS.contains(ctx.state.block) &&
ctx.biome?.category
.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } &&
noise[ctx.pos] < Config.algae.population
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val rand = ctx.semiRandomArray(3)
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.render(
algaeModels[rand[2]],
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]] }
)
}
}
}

View File

@@ -1,111 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerAoMaxGreen
import mods.betterfoliage.render.lighting.edgeOrientedAuto
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.octarinecore.client.resource.*
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.config.SimpleBlockMatcher
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.Vertex
import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock
import net.minecraft.util.Direction.*
import java.util.concurrent.CompletableFuture
object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
val sprites = textures.map { atlas.sprite(it) }
return atlas.mapAfter {
SimpleColumnInfo(
Axis.Y,
sprites[0].get(),
sprites[1].get(),
sprites.drop(2).map { it.get() }
)
}
}
fun init() {
BetterFoliage.blockSprites.providers.add(this)
}
}
class RenderCactus : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross by sprite(Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus"))
val iconArm = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx") }
val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
.scaleUV(cactusStemRadius * 2.0)
.let { listOf(it.flipped.move(1.0 to DOWN), it) }
.forEach { it.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)).add() }
verticalRectangle(x1 = -0.5, z1 = cactusStemRadius, x2 = 0.5, z2 = cactusStemRadius, yBottom = -0.5, yTop = 0.5)
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null))
.toCross(UP).addAll()
}
val modelCross = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.scale(1.4)
.transformV { v ->
val perturb = xzDisk(modelIdx) * Config.cactus.sizeVariation
Vertex(v.xyz + (if (v.uv.u < 0.0) perturb else -perturb), v.uv, v.aoShader)
}
.toCross(UP).addAll()
}
val modelArm = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.cactus.size).move(0.5 to UP)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y), edge = null))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
}
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled &&
AsyncCactusDiscovery[ctx] != null
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val icons = AsyncCactusDiscovery[ctx]!!
ctx.render(
modelStem.model,
icon = { ctx, qi, q -> when(qi) {
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
} }
)
ctx.render(
modelCross[ctx.semiRandom(0)],
icon = { _, _, _ -> iconCross }
)
ctx.render(
modelArm[ctx.semiRandom(1)],
cactusArmRotation[ctx.semiRandom(2) % 4],
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)] }
)
}
}

View File

@@ -1,48 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.texture.GrassRegistry
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.offset
class RenderConnectedGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.connectedGrass.enabled &&
DIRT_BLOCKS.contains(ctx.state.block) &&
GrassRegistry[ctx, up1] != null &&
(Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow)
override fun render(ctx: CombinedContext) {
// if the block sides are not visible anyway, render normally
if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) {
ctx.render()
} else {
ctx.exchange(Int3.zero, up1).exchange(up1, up2).render()
}
}
}
class RenderConnectedGrassLog : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
DIRT_BLOCKS.contains(ctx.state.block) &&
LogRegistry[ctx, up1] != null
override fun render(ctx: CombinedContext) {
val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null }
if (grassDir == null) {
ctx.render()
} else {
ctx.exchange(Int3.zero, grassDir.offset).render()
}
}
}

View File

@@ -1,69 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.rotationFromUp
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import net.minecraft.block.material.Material
import net.minecraft.tags.BlockTags
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
class RenderCoral : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val coralIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") }
val crustIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") }
val coralModels = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.coral.size).move(0.5 to UP)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.coral.hOffset) }.addAll()
val separation = randomD(0.01, Config.coral.vOffset)
horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
.scale(Config.coral.crustSize).move(0.5 + separation to UP).add()
transformQ {
it.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
}
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.coral.enabled &&
(ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.state(up1).material == Material.WATER &&
BlockTags.SAND.contains(ctx.state.block) &&
ctx.biome?.category
.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } &&
noise[ctx.pos] < Config.coral.population
override fun render(ctx: CombinedContext) {
val baseRender = ctx.render()
if (!ctx.isCutout) return
allDirections.forEachIndexed { idx, face ->
if (ctx.state(face).material == Material.WATER && ctx.semiRandom(idx) < Config.coral.chance) {
var variation = ctx.semiRandom(6)
ctx.render(
coralModels[variation++],
rotationFromUp[idx],
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation] else coralIcons[variation + (qi and 1)] }
)
}
}
}
}

View File

@@ -1,105 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.down1
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.generated.GeneratedGrass
import mods.betterfoliage.texture.GrassRegistry
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.fullCube
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.snowOffset
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import net.minecraft.util.Direction.*
class RenderGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5,
yTop = 0.5 + randomD(heightMin, heightMax)
)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
}
val noise = simplexNoise()
val normalIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") }
val snowedIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_snowed_$idx") }
val normalGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = false).register(BetterFoliage.asyncPack) }
val snowedGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = true).register(BetterFoliage.asyncPack) }
val grassModels = modelSet(64) { idx -> grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
GrassRegistry[ctx] != null
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val isConnected = DIRT_BLOCKS.contains(ctx.state(DOWN).block) || GrassRegistry[ctx, down1] != null
val isSnowed = ctx.state(UP).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grass = GrassRegistry[ctx]!!
val blockColor = OptifineCustomColors.getBlockColor(ctx)
if (connectedGrass) {
// check occlusion
val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) }
// render full grass block
ctx.render(
fullCube,
quadFilter = { qi, _ -> isVisible[qi] },
icon = { _, _, _ -> grass.grassTopTexture },
postProcess = { ctx, _, _, _, _ ->
rotateUV(2)
if (isSnowed) {
if (!ctx.aoEnabled) setGrey(1.4f)
} else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor)
}
)
} else {
ctx.render()
}
if (!Config.shortGrass.grassEnabled) return
if (isSnowed && !Config.shortGrass.snowEnabled) return
if (ctx.offset(UP).isNormalCube) return
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return
// render grass quads
val iconset = if (isSnowed) snowedIcons else normalIcons
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
val rand = ctx.semiRandomArray(2)
ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
ctx.render(
grassModels[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen else iconset[rand[qi and 1]] },
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
)
}
}
}

View File

@@ -1,83 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.denseLeavesRot
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.texture.LeafRegistry
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffset
import mods.betterfoliage.render.lighting.cornerAoMaxGreen
import mods.betterfoliage.render.lighting.edgeOrientedAuto
import mods.betterfoliage.render.normalLeavesRot
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.whitewash
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.vec
import net.minecraft.util.Direction.UP
import java.lang.Math.cos
import java.lang.Math.sin
class RenderLeaves : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val leavesModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.setFlatShader(FlatOffset(Int3.zero))
.scale(Config.leaves.size)
.toCross(UP).addAll()
}
val snowedIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") }
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0
Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset +
UP.vec * randomD(-1.0, 1.0) * Config.leaves.vOffset
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
Config.leaves.enabled &&
LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } )
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val isSnowed = ctx.state(UP).isSnow
val leafInfo = LeafRegistry[ctx]!!
val blockColor = OptifineCustomColors.getBlockColor(ctx)
ctx.render(force = true)
ShadersModIntegration.leaves(ctx) {
val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
ctx.render(
leavesModel.model,
rotation,
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> leafInfo.roundLeafTexture },
postProcess = { _, _, _, _, _ ->
rotateUV(rand[1])
multiplyColor(blockColor)
}
)
}
if (isSnowed && Config.leaves.snowEnabled) ctx.render(
leavesModel.model,
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> snowedIcon[rand[1]] },
postProcess = whitewash
)
}
}
}

View File

@@ -1,58 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffsetNoColor
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.Int3
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
class RenderLilypad : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val flowerModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(0.5).move(0.5 to DOWN)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val rootIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") }
val flowerIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") }
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
BlockConfig.lilypad.matchesClass(ctx.state.block)
override fun render(ctx: CombinedContext) {
ctx.render()
val rand = ctx.semiRandomArray(5)
ShadersModIntegration.grass(ctx) {
ctx.render(
rootModel.model,
translation = ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]] }
)
}
if (rand[3] < Config.lilypad.flowerChance) ctx.render(
flowerModel.model,
translation = ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]] }
)
}
}

View File

@@ -1,89 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.AbstractRenderColumn
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelRenderRegistry
import mods.betterfoliage.resource.discovery.ModelRenderRegistryRoot
import mods.octarinecore.client.resource.*
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis
import org.apache.logging.log4j.Level
import java.util.concurrent.CompletableFuture
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override val renderOnCutout: Boolean get() = false
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled &&
LogRegistry[ctx] != null
override val overlayLayer = RoundLogOverlayLayer()
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
init {
ChunkOverlayManager.layers.add(overlayLayer)
}
}
class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) }
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 LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo> {
val axis = getAxis(state)
logger.log(Level.DEBUG, "$logName: axis $axis")
val spriteList = textures.map { atlas.sprite(it) }
return atlas.mapAfter {
SimpleColumnInfo(
axis,
spriteList[0].get(),
spriteList[1].get(),
spriteList.drop(2).map { it.get() }
)
}
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
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
}
}
fun init() {
LogRegistry.registries.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}

View File

@@ -1,45 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.noPost
import mods.betterfoliage.render.snowOffset
import mods.betterfoliage.render.whitewash
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.util.Double3
import net.minecraft.util.Direction.UP
class RenderMycelium : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val myceliumIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(
Config.shortGrass.heightMin,
Config.shortGrass.heightMax
)(idx) }
override fun isEligible(ctx: CombinedContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return BlockConfig.mycelium.matchesClass(ctx.state.block)
}
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val isSnowed = ctx.state(UP).isSnow
if (isSnowed && !Config.shortGrass.snowEnabled) return
if (ctx.offset(UP).isNormalCube) return
val rand = ctx.semiRandomArray(2)
ctx.render(
myceliumModel[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]] },
postProcess = if (isSnowed) whitewash else noPost
)
}
}

View File

@@ -1,44 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.util.randomD
import net.minecraft.block.Blocks
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.*
class RenderNetherrack : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val netherrackIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - randomD(Config.netherrack.heightMin, Config.netherrack.heightMax))
.setAoShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.netherrack.enabled && ctx.state.block == Blocks.NETHERRACK
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
if (ctx.offset(DOWN).isNormalCube) return
val rand = ctx.semiRandomArray(2)
ctx.render(
netherrackModel[rand[0]],
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]] }
)
}
}

View File

@@ -1,70 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffsetNoColor
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.randomD
import net.minecraft.block.material.Material
import net.minecraft.util.Direction.UP
class RenderReeds : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val reedIcons = spriteSetTransformed(
check = { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx")},
register = { CenteredSprite(it).register(BetterFoliage.asyncPack) }
)
val reedModels = modelSet(64) { modelIdx ->
val height = randomD(Config.reed.heightMin, Config.reed.heightMax)
val waterline = 0.875f
val vCutLine = 0.5 - waterline / height
listOf(
// below waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, yTop = 0.5 + waterline)
.setFlatShader(FlatOffsetNoColor(up1)).clampUV(minV = vCutLine),
// above waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5 + waterline, yTop = 0.5 + height)
.setFlatShader(FlatOffsetNoColor(up2)).clampUV(maxV = vCutLine)
).forEach {
it.clampUV(minU = -0.25, maxU = 0.25)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.reed.hOffset) }.addAll()
}
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.reed.enabled &&
ctx.state(up2).material == Material.AIR &&
ctx.state(UP).material == Material.WATER &&
DIRT_BLOCKS.contains(ctx.state.block) &&
ctx.biome
?.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } ?: false &&
noise[ctx.pos] < Config.reed.population
override val onlyOnCutout get() = false
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val iconVar = ctx.semiRandom(1)
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.render(
reedModels[ctx.semiRandom(0)],
forceFlat = true,
icon = { _, _, _ -> reedIcons[iconVar] }
)
}
}
}

View File

@@ -1,209 +0,0 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.render.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.noPost
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.face
import mods.betterfoliage.util.rot
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.util.Direction.*
import net.minecraftforge.eventbus.api.IEventBus
@Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : RenderDecorator(modId, modBus) {
/** The rotations necessary to bring the models in position for the 4 quadrants */
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
// ============================
// Configuration
// ============================
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract val radiusSmall: Double
abstract val radiusLarge: Double
// ============================
// Models
// ============================
val sideSquare = model { columnSideSquare(-0.5, 0.5) }
val sideRoundSmall = model { columnSide(radiusSmall, -0.5, 0.5) }
val sideRoundLarge = model { columnSide(radiusLarge, -0.5, 0.5) }
val extendTopSquare = model { columnSideSquare(0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundSmall = model { columnSide(radiusSmall, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundLarge = model { columnSide(radiusLarge, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
inline fun extendTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> extendTopRoundSmall.model
LARGE_RADIUS -> extendTopRoundLarge.model
SQUARE -> extendTopSquare.model
INVISIBLE -> extendTopSquare.model
else -> null
}
val extendBottomSquare = model { columnSideSquare(-0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundSmall = model { columnSide(radiusSmall, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundLarge = model { columnSide(radiusLarge, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
inline fun extendBottom(type: QuadrantType) = when (type) {
SMALL_RADIUS -> extendBottomRoundSmall.model
LARGE_RADIUS -> extendBottomRoundLarge.model
SQUARE -> extendBottomSquare.model
INVISIBLE -> extendBottomSquare.model
else -> null
}
val topSquare = model { columnLidSquare() }
val topRoundSmall = model { columnLid(radiusSmall) }
val topRoundLarge = model { columnLid(radiusLarge) }
inline fun flatTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> topRoundSmall.model
LARGE_RADIUS -> topRoundLarge.model
SQUARE -> topSquare.model
INVISIBLE -> topSquare.model
else -> null
}
val bottomSquare = model { columnLidSquare() { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
inline fun flatBottom(type: QuadrantType) = when(type) {
SMALL_RADIUS -> bottomRoundSmall.model
LARGE_RADIUS -> bottomRoundLarge.model
SQUARE -> bottomSquare.model
INVISIBLE -> bottomSquare.model
else -> null
}
val transitionTop = model { mix(sideRoundLarge.model, sideRoundSmall.model) { it > 1 } }
val transitionBottom = model { mix(sideRoundSmall.model, sideRoundLarge.model) { it > 1 } }
inline fun continuous(q1: QuadrantType, q2: QuadrantType) =
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
@Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: CombinedContext) {
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
when(roundLog) {
ColumnLayerData.SkipRender -> return
ColumnLayerData.NormalRender -> return ctx.render()
ColumnLayerData.ResolveError, null -> {
BetterFoliage.logRenderError(ctx.state, ctx.pos)
return ctx.render()
}
}
// 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 ctx.render()
}
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
renderAs(ctx, MODEL) {
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
// 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
}
// render side of current quadrant
val sideModel = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> sideRoundSmall.model
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
else sideRoundLarge.model
SQUARE -> sideSquare.model
else -> null
}
if (sideModel != null) ctx.render(
sideModel,
rotation,
icon = roundLog.column.side,
postProcess = noPost
)
// render top and bottom end of current quadrant
var upModel: Model? = null
var downModel: Model? = null
var upIcon = roundLog.column.top
var downIcon = roundLog.column.bottom
var isLidUp = true
var isLidDown = true
when (roundLog.upType) {
NONSOLID -> upModel = flatTop(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
upModel = flatTop(roundLog.quadrants[idx])
} else {
upIcon = roundLog.column.side
upModel = extendTop(roundLog.quadrants[idx])
isLidUp = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) {
if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) {
upModel = topSquare.model
}
}
}
}
when (roundLog.downType) {
NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
downModel = flatBottom(roundLog.quadrants[idx])
} else {
downIcon = roundLog.column.side
downModel = extendBottom(roundLog.quadrants[idx])
isLidDown = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) &&
(roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) {
downModel = bottomSquare.model
}
}
}
if (upModel != null) ctx.render(
upModel,
rotation,
icon = upIcon,
postProcess = { _, _, _, _, _ ->
if (isLidUp) {
rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0)
}
}
)
if (downModel != null) ctx.render(
downModel,
rotation,
icon = downIcon,
postProcess = { _, _, _, _, _ ->
if (isLidDown) {
rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx)
}
}
)
}
}
}
}

View File

@@ -1,23 +1,27 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.chunk.ChunkOverlayLayer
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.chunk.dimType
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
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.BlockType.SOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.render.rotationFromUp
import mods.betterfoliage.render.old.BlockCtx
import mods.betterfoliage.resource.discovery.ModelRenderRegistry
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.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.face
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState
import net.minecraft.util.Direction
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.AxisDirection
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
@@ -30,6 +34,10 @@ const val NW = 2
/** Index of SOUTH-WEST quadrant. */
const val SW = 3
interface ColumnBlockKey {
val axis: Axis?
}
/**
* Sealed class hierarchy for all possible render outcomes
*/
@@ -39,7 +47,7 @@ sealed class ColumnLayerData {
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
data class SpecialRender(
val column: ColumnTextureInfo,
val column: ColumnBlockKey,
val upType: BlockType,
val downType: BlockType,
val quadrants: Array<QuadrantType>,
@@ -47,7 +55,12 @@ sealed class ColumnLayerData {
val quadrantsBottom: Array<QuadrantType>
) : ColumnLayerData() {
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE }
enum class QuadrantType {
SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE;
infix fun continuousWith(other: QuadrantType) =
this == other || ((this == SQUARE || this == INVISIBLE) && (other == SQUARE || other == INVISIBLE))
infix fun discontinuousWith(other: QuadrantType) = !continuousWith(other)
}
}
/** Column block should not be rendered at all */
@@ -60,15 +73,14 @@ sealed class ColumnLayerData {
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
abstract val blockPredicate: (BlockState)->Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean
abstract fun getColumnKey(state: BlockState): ColumnBlockKey?
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: ILightReader, pos: BlockPos) {
@@ -76,15 +88,16 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
}
override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube && registry[ctx] == null }}) return ColumnLayerData.SkipRender
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
// 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
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
// check log neighborhood
val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
val baseRotation = Rotation.fromUp[(logAxis to Direction.AxisDirection.POSITIVE).face.ordinal]
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
@@ -171,11 +184,11 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
*/
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation)
val state = state(offsetRot)
return if (!blockPredicate(state)) {
val key = getColumnKey(state(offsetRot))
return if (key == null) {
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
} else {
(registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
(key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID
}

View File

@@ -1,32 +0,0 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.render.lighting.QuadIconResolver
import mods.betterfoliage.util.rotate
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.*
interface ColumnTextureInfo {
val axis: Axis?
val top: QuadIconResolver
val bottom: QuadIconResolver
val side: QuadIconResolver
}
open class SimpleColumnInfo(
override val axis: Axis?,
val topTexture: TextureAtlasSprite,
val bottomTexture: TextureAtlasSprite,
val sideTextures: List<TextureAtlasSprite>
) : ColumnTextureInfo {
// index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture
val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5)
override val top: QuadIconResolver = { _, _, _ -> topTexture }
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
override val side: QuadIconResolver = { ctx, idx, _ ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx]
}
}

View File

@@ -0,0 +1,138 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.perpendiculars
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
typealias BoxCorner = Triple<Direction, Direction, Direction>
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
return null
}
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
forEachIndexed { idx, test -> if (predicate(test)) return idx }
return null
}
class AoSideHelper private constructor(face: Direction) {
val sides = faceSides[face]
val cornerSideDirections = faceCorners[face]
val aoIndex = faceCornersIdx.mapArray { corner ->
boxCornersDirIdx[face][sides[corner.first]][sides[corner.second]]!!
}
companion object {
/**
* Indexing for undirected box corners (component order does not matter).
* Array contains [Direction] triplets fully defining the corner.
*/
@JvmField
val boxCornersUndir = Array(8) { idx -> Triple(
if (idx and 1 != 0) EAST else WEST,
if (idx and 2 != 0) UP else DOWN,
if (idx and 4 != 0) SOUTH else NORTH
) }
/**
* Reverse lookup for [boxCornersUndir]. Index 3 times with the corner's cardinal directions.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
boxCornersUndir.findIdx(BoxCorner(
Direction.values()[idx1],
Direction.values()[idx2],
Direction.values()[idx3]
))
} } }
/**
* Indexing for directed face sides
* First index is the face, second is index of side on face
*/
@JvmField
val faceSides = Array(6) { faceIdx -> Array(4) { sideIdx ->
Direction.values()[faceIdx].perpendiculars[sideIdx]
} }
/**
* Pairs of [faceSides] side indexes that form a valid pair describing a corner
*/
@JvmField
val faceCornersIdx = arrayOf(0 to 2, 0 to 3, 1 to 2, 1 to 3)
/**
* Indexing for directed face corners
* First index is the face, second is index of corner on face
*/
@JvmField
val faceCorners = Array(6) { faceIdx -> Array(4) { cornerIdx ->
faceCornersIdx[cornerIdx].let { faceSides[faceIdx][it.first] to faceSides[faceIdx][it.second] }
} }
/**
* Indexing scheme for directed box corners.
* The first direction - the face - matters, the other two are unordered.
* 1:1 correspondence with possible AO values.
* Array contains triplets defining the corner fully.
*/
@JvmField
val boxCornersDir = Array(24) { idx ->
val faceIdx = idx / 4; val face = Direction.values()[faceIdx]
val cornerIdx = idx % 4; val corner = faceCorners[faceIdx][cornerIdx]
BoxCorner(face, corner.first, corner.second)
}
/**
* Reverse lookup for [boxCornersDir]. Index 3 times with the corner's cardinal directions.
* The first direction - the face - matters, the other two are unordered.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersDirIdx = Array(6) { face -> Array(6) { side1 -> Array(6) { side2 ->
boxCornersDir.findIdx { boxCorner ->
boxCorner.first.ordinal == face && boxCorner.equalsUnordered(BoxCorner(
Direction.values()[face],
Direction.values()[side1],
Direction.values()[side2]
))
}
} } }
/**
* Reverse lookup for [cornersDir].
* 1st index: primary face
* 2nd index: undirected corner index.
* value: directed corner index
* A null value indicates an invalid corner (primary face not shared by corner).
*/
@JvmField
val boxCornersDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
val face = Direction.values()[faceIdx]
val corner = boxCornersUndir[undirIdx]
if (!corner.contains(face)) null
else boxCornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
} }
@JvmField
val forSide = Direction.values().mapArray { AoSideHelper(it) }
/**
* Get corner index for vertex coordinates
*/
@JvmStatic
fun getCornerUndir(x: Double, y: Double, z: Double): Int {
var result = 0
if (x > 0.0) result += 1
if (y > 0.0) result += 2
if (z > 0.0) result += 4
return result
}
}
}

View File

@@ -0,0 +1,33 @@
package mods.betterfoliage.render.lighting
interface ForgeVertexLighterAccess {
var vertexLighter: ForgeVertexLighter
}
interface ForgeVertexLighter {
fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float)
fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int)
}
fun ForgeVertexLighter.grass() = object: ForgeVertexLighter {
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
this@grass.updateVertexLightmap(normal, lightmap, x * 0.5f, 1.0f, z * 0.5f)
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
this@grass.updateVertexColor(normal, color, x * 0.5f, 1.0f, z * 0.5f, tint, multiplier
)
}
}
fun ForgeVertexLighter.grassSimple() = object: ForgeVertexLighter {
val normalUp = floatArrayOf(0.0f, 1.0f, 0.0f, 0.0f)
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
this@grassSimple.updateVertexLightmap(normalUp, lightmap, 0.0f, 1.0f, 0.0f)
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
this@grassSimple.updateVertexColor(normalUp, color, 0.0f, 1.0f, 0.0f, tint, multiplier
)
}
}

View File

@@ -1,158 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.axes
import mods.betterfoliage.util.boxEdges
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.nearestPosition
import mods.betterfoliage.util.perpendiculars
import mods.betterfoliage.util.vec
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import java.lang.Math.min
typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */
class CornerLightData {
var valid = false
var brightness = 0
var red: Float = 0.0f
var green: Float = 0.0f
var blue: Float = 0.0f
fun reset() { valid = false }
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
if (valid) return
this.valid = true
this.brightness = brightness
this.red = red
this.green = green
this.blue = blue
}
fun set(brightness: Int, colorMultiplier: Float) {
this.valid = true
this.brightness = brightness
this.red = colorMultiplier
this.green = colorMultiplier
this.blue = colorMultiplier
}
companion object {
val black: CornerLightData get() = CornerLightData()
}
}
/**
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
* values to a [RenderVertex].
*/
interface ModelLighter {
/**
* Set shading values of a [RenderVertex]
*
* @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference
* @param[vertex] the [RenderVertex] to manipulate
*/
fun shade(context: LightingCtx, vertex: RenderVertex)
/**
* Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself.
*/
fun rotate(rot: Rotation): ModelLighter
/** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */
fun RenderVertex.shade(shading: CornerLightData) {
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
}
/** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */
fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
}
/**
* Set the lighting values on the [RenderVertex] directly.
*
* @param[brightness] packed brightness value
* @param[color] packed color value
*/
fun RenderVertex.shade(brightness: Int, color: Int) {
this.brightness = brightness; setColor(color)
}
}
/**
* Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces.
* The resolver works the following way:
* - determines which face the _quad_ normal points towards (if not overridden)
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_
*
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
* @param[corner] [ModelLighter] instantiation lambda for corner vertices
* @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices
*/
fun faceOrientedAuto(overrideFace: Direction? = null,
corner: CornerShaderFactory? = null,
edge: EdgeShaderFactory? = null) =
fun(quad: Quad, vertex: Vertex): ModelLighter {
val quadFace = overrideFace ?: quad.normal.nearestCardinal
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) {
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
}
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
(quadFace.vec + it.vec) * 0.5
}
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
return edge(quadFace, nearestEdge.first)
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
}
/**
* Returns a ModelLighter resolver for quads that point towards one of the 12 block edges.
* The resolver works the following way:
* - determines which edge the _quad_ normal points towards (if not overridden)
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
* - determines which block corner _of this face_ the _vertex_ is closest to
* - returns the [ModelLighter] created by _corner_
*
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
* @param[corner] ModelLighter instantiation lambda
*/
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
corner: CornerShaderFactory
) =
fun(quad: Quad, vertex: Vertex): ModelLighter {
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) {
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
}.first
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
}
fun faceOrientedInterpolate(overrideFace: Direction? = null) =
fun(quad: Quad, vertex: Vertex): ModelLighter {
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
val vec = Double3((axis to AxisDirection.POSITIVE).face)
val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos)
})
return resolver(quad, vertex)
}

View File

@@ -1,118 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.BlockCtx
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.rotate
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.WorldRenderer
import net.minecraft.util.Direction
import java.util.*
val Direction.aoMultiplier: Float get() = when(this) {
Direction.UP -> 1.0f
Direction.DOWN -> 0.5f
Direction.NORTH, Direction.SOUTH -> 0.8f
Direction.EAST, Direction.WEST -> 0.6f
}
interface LightingCtx {
val modelRotation: Rotation
val blockContext: BlockCtx
val aoEnabled: Boolean
val brightness get() = brightness(Int3.zero)
val color get() = color(Int3.zero)
fun brightness(face: Direction) = brightness(face.offset)
fun color(face: Direction) = color(face.offset)
fun brightness(offset: Int3) = offset.rotate(modelRotation).let {
WorldRenderer.getCombinedLight(blockContext.world, blockContext.pos + it)
}
fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) }
fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData
}
class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx {
override var modelRotation = Rotation.identity
override var aoEnabled = false
protected set
override var blockContext: BlockCtx = blockContext
protected set
override var brightness = brightness(Int3.zero)
protected set
override var color = color(Int3.zero)
protected set
override fun brightness(face: Direction) = brightness(face.offset)
override fun color(face: Direction) = color(face.offset)
// smooth lighting stuff
val lightingData = Array(6) { FaceLightData(allDirections[it]) }
override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData ->
if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier)
return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)]
}
fun reset(blockContext: BlockCtx) {
this.blockContext = blockContext
brightness = brightness(Int3.zero)
color = color(Int3.zero)
modelRotation = Rotation.identity
lightingData.forEach { it.isValid = false }
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) }
}
}
private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
class FaceLightData(val face: Direction) {
val topDir = boxFaces[face].top
val leftDir = boxFaces[face].left
val topLeft = CornerLightData()
val topRight = CornerLightData()
val bottomLeft = CornerLightData()
val bottomRight = CornerLightData()
val vanillaOrdered = when(face) {
Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
}
val delegate = vanillaAOFactory()
var isValid = false
fun update(blockCtx: BlockCtx, multiplier: Float) {
val quadBounds = FloatArray(12)
val flags = BitSet(3).apply { set(0) }
// delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags)
vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) }
isValid = true
}
operator fun get(dir1: Direction, dir2: Direction): CornerLightData {
val isTop = topDir == dir1 || topDir == dir2
val isLeft = leftDir == dir1 || leftDir == dir2
return if (isTop) {
if (isLeft) topLeft else topRight
} else {
if (isLeft) bottomLeft else bottomRight
}
}
}

View File

@@ -1,162 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.axes
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.rotate
import net.minecraft.util.Direction
const val defaultCornerDimming = 0.5f
const val defaultEdgeDimming = 0.8f
// ================================
// Shader instantiation lambdas
// ================================
fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, dir2 ->
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
CornerSingleFallback(face, dir1, dir2, fallbackDir)
}
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) }
fun cornerAoTri(func: (CornerLightData, CornerLightData)-> CornerLightData) = { face: Direction, dir1: Direction, dir2: Direction ->
CornerTri(face, dir1, dir2, func)
}
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 }
fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
}
// ================================
// Shaders
// ================================
object NoLighting : ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black)
override fun rotate(rot: Rotation) = this
}
class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) :
ModelLighter {
val offset = Int3(fallbackDir)
override fun shade(context: LightingCtx, vertex: RenderVertex) {
val shading = context.lighting(face, dir1, dir2)
if (shading.valid)
vertex.shade(shading)
else {
vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
}
}
override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming)
}
inline fun accumulate(v1: CornerLightData?, v2: CornerLightData?, func: ((CornerLightData, CornerLightData)-> CornerLightData)): CornerLightData? {
val v1ok = v1 != null && v1.valid
val v2ok = v2 != null && v2.valid
if (v1ok && v2ok) return func(v1!!, v2!!)
if (v1ok) return v1
if (v2ok) return v2
return null
}
class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
var acc = accumulate(
context.lighting(face, dir1, dir2),
context.lighting(dir1, face, dir2),
func)
acc = accumulate(
acc,
context.lighting(dir2, face, dir1),
func)
vertex.shade(acc ?: CornerLightData.black)
}
override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func)
}
class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming):
ModelLighter {
val offset = Int3(edgeDir)
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
val weightN = (0.5 - pos).toFloat()
val weightP = (0.5 + pos).toFloat()
override fun shade(context: LightingCtx, vertex: RenderVertex) {
val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face)
val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face)
if (!shadingP.valid && !shadingN.valid) {
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
}
if (!shadingP.valid) return vertex.shade(shadingN)
if (!shadingN.valid) return vertex.shade(shadingP)
vertex.shade(shadingP, shadingN, weightP, weightN)
}
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
}
class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction,
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming
) : ModelLighter {
val offset = Int3(edgeDir)
override fun shade(context: LightingCtx, vertex: RenderVertex) {
var shading1 = context.lighting(face1, edgeDir, face2)
var shading2 = context.lighting(face2, edgeDir, face1)
var weight1 = weight
var weight2 = 1.0f - weight
if (!shading1.valid && !shading2.valid) {
return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
}
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
if (!shading2.valid) { shading2 = shading1; weight2 *= dimming }
vertex.shade(shading1, shading2, weight1, weight2)
}
override fun rotate(rot: Rotation) =
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
}
class FaceCenter(val face: Direction): ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
val b = IntArray(4)
boxFaces[face].allCorners.forEachIndexed { idx, corner ->
val shading = context.lighting(face, corner.first, corner.second)
vertex.red += shading.red
vertex.green += shading.green
vertex.blue += shading.blue
b[idx] = shading.brightness
}
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
vertex.brightness = brSum(0.25f, *b)
}
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
}
class FaceFlat(val face: Direction): ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.shade(context.brightness(face.offset), context.color(Int3.zero))
}
override fun rotate(rot: Rotation): ModelLighter = FaceFlat(face.rotate(rot))
}
class FlatOffset(val offset: Int3): ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.brightness = context.brightness(offset)
vertex.setColor(context.color(offset))
}
override fun rotate(rot: Rotation): ModelLighter = this
}
class FlatOffsetNoColor(val offset: Int3): ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.brightness = context.brightness(offset)
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f
}
override fun rotate(rot: Rotation): ModelLighter = this
}

View File

@@ -1,3 +0,0 @@
@file:JvmName("PixelFormat")
package mods.betterfoliage.render.lighting

View File

@@ -0,0 +1,147 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.chunk.BlockCtx
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
data class LightingData(
@JvmField var packedLight: Int = 0,
@JvmField var colorMultiplier: Float = 1.0f
) {
fun mixFrom(corner: LightingData, side1: LightingData, side2: LightingData, center: LightingData) {
colorMultiplier =
(center.colorMultiplier + side1.colorMultiplier + side2.colorMultiplier + corner.colorMultiplier) * 0.25f
packedLight = (
center.packedLight +
(side1.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(side2.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(corner.packedLight.takeUnless { it == 0 } ?: center.packedLight)
).let { sum -> (sum shr 2) and 0xFF00FF }
}
}
/**
* Replacement for [BlockModelRenderer.AmbientOcclusionFace]
* This gets called on a LOT, so object instantiation is avoided.
* Not thread-safe, always use a [ThreadLocal] instance
*/
class VanillaAoCalculator {
lateinit var world: ILightReader
/** [blockPos] is used to get block-related information (i.e. tint, opacity, etc.)
* [lightPos] is used to get light-related information
* this facilitates masquerade rendering of blocks */
lateinit var blockPos: BlockPos
lateinit var lightPos: BlockPos
private val probe = LightProbe(BlockModelRenderer.CACHE_COMBINED_LIGHT.get())
val isValid = BooleanArray(6)
val aoData = Array(24) { LightingData() }
// scratchpad values used during calculation
private val centerAo = LightingData()
private val sideAo = Array(4) { LightingData() }
private val cornerAo = Array(4) { LightingData() }
private val isOccluded = BooleanArray(4)
fun reset(ctx: BlockCtx) {
world = ctx.world; blockPos = ctx.pos; lightPos = ctx.pos
(0 until 6).forEach { isValid[it] = false }
}
fun fillLightData(lightFace: Direction, isOpaque: Boolean? = null) {
if (!isValid[lightFace.ordinal]) calculate(lightFace, isOpaque)
}
/**
* Replicate [BlockModelRenderer.AmbientOcclusionFace.updateVertexBrightness]
* Does not handle interpolation for non-cubic models, that should be
* done in a [VanillaVertexLighter]
* @param lightFace face of the block to calculate
* @param forceFull force full-block status for lighting calculation, null for auto
*/
private fun calculate(lightFace: Direction, forceFull: Boolean?) {
if (isValid[lightFace.ordinal]) return
val sideHelper = AoSideHelper.forSide[lightFace.ordinal]
// Bit 0 of the bitset in vanilla calculations
// true if the block model is planar with the block boundary
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeOpaque(world, blockPos)
val lightOrigin = if (isFullBlock) lightPos.offset(lightFace) else lightPos
// AO calculation for the face center
probe.position { setPos(lightOrigin) }.writeTo(centerAo)
if (!isFullBlock && !probe.position { move(lightFace) }.state.isOpaqueCube(world, probe.pos)) {
// if the neighboring block in the lightface direction is
// transparent (non-opaque), use its packed light instead of our own
// (if our block is a full block, we are already using this value)
centerAo.packedLight = probe.packedLight
}
// AO calculation for the 4 sides
sideHelper.sides.forEachIndexed { sideIdx, sideDir ->
// record light data in the block 1 step to the side
probe.position { setPos(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
// side is considered occluded if the block 1 step to that side and
// 1 step forward (in the lightface direction) is not fully transparent
isOccluded[sideIdx] = probe.position { move(lightFace) }.isNonTransparent
}
// AO Calculation for the 4 corners
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val bothOccluded = isOccluded[sideIndices.first] && isOccluded[sideIndices.second]
if (bothOccluded) cornerAo[cornerIdx].apply {
// if both sides are occluded, just use the packed light for one of the sides instead
val copyFrom = sideAo[sideIndices.first]
packedLight = copyFrom.packedLight; colorMultiplier = copyFrom.colorMultiplier
}
else {
// lookup actual packed light from the cornering block in the world
probe.position {
setPos(lightOrigin)
.move(sideHelper.sides[sideIndices.first])
.move(sideHelper.sides[sideIndices.second])
}.writeTo(cornerAo[cornerIdx])
}
}
// Calculate and store final interpolated value for each corner
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val aoIdx = sideHelper.aoIndex[cornerIdx]
aoData[aoIdx].mixFrom(
cornerAo[cornerIdx],
sideAo[sideIndices.first],
sideAo[sideIndices.second],
centerAo
)
}
isValid[lightFace.ordinal] = true
}
inner class LightProbe(
val cache: BlockModelRenderer.Cache
) {
lateinit var state: BlockState
val pos = BlockPos.Mutable()
val packedLight: Int get() = cache.getPackedLight(state, world, pos)
val colorMultiplier: Float get() = cache.getBrightness(state, world, pos)
val isNonTransparent: Boolean get() = state.getOpacity(world, pos) > 0
fun writeTo(data: LightingData) {
data.packedLight = packedLight
data.colorMultiplier = colorMultiplier
}
inline fun position(func: BlockPos.Mutable.() -> Unit): LightProbe {
pos.func()
state = world.getBlockState(pos)
return this
}
}
}

View File

@@ -0,0 +1,133 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.EPSILON
import mods.betterfoliage.util.minBy
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis
import net.minecraftforge.client.model.pipeline.LightUtil
import kotlin.math.abs
class VanillaQuadLighting {
val packedLight = IntArray(4)
val colorMultiplier = FloatArray(4)
val tint = FloatArray(3)
val calc = VanillaAoCalculator()
lateinit var blockColors: BlockColors
fun updateBlockTint(tintIndex: Int) {
if (tintIndex == -1) {
tint[0] = 1.0f; tint[1] = 1.0f; tint[2] = 1.0f
} else {
val state = calc.world.getBlockState(calc.blockPos)
blockColors.getColor(state, calc.world, calc.blockPos, tintIndex).let { blockTint ->
tint[0] = (blockTint shr 16 and 255).toFloat() / 255.0f
tint[1] = (blockTint shr 8 and 255).toFloat() / 255.0f
tint[2] = (blockTint and 255).toFloat() / 255.0f
}
}
}
fun applyDiffuseLighting(face: Direction) {
val factor = LightUtil.diffuseLight(face)
tint[0] *= factor; tint[1] *= factor; tint[2] *= factor
}
}
abstract class VanillaVertexLighter {
abstract fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting)
inline fun VanillaQuadLighting.updateWithCornerAo(quad: HalfBakedQuad, func: (Double3)->Int?) {
quad.raw.verts.forEachIndexed { idx, vertex ->
func(vertex.xyz)?.let {
packedLight[idx] = calc.aoData[it].packedLight
colorMultiplier[idx] = calc.aoData[it].colorMultiplier
}
}
}
}
object VanillaFullBlockLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// TODO bounds checking
val face = quad.raw.face()
lighting.calc.fillLightData(face, true)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
if (quad.baked.shouldApplyDiffuseLighting()) lighting.applyDiffuseLighting(face)
}
}
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)
if (normalFaces.first != UP && normalFaces.second != UP) lighting.calc.fillLightData(UP)
lighting.updateWithCornerAo(quad) { vertex ->
val isUp = vertex.y > 0.5f
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, vertex) }
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
}
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
}
class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
lighting.calc.fillLightData(face)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, 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
* one of the opposite 4 corners
*/
fun nearestCornerOnFace(pos: Double3, face: Direction): Int? {
val cornerUndir = AoSideHelper.getCornerUndir(pos.x, pos.y, pos.z)
return AoSideHelper.boxCornersDirFromUndir[face.ordinal][cornerUndir]
}
/**
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
* and is approximately perpendicular to the third, returns the 2 directions
* the quad normal points towards.
* Returns null otherwise.
*/
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
else -> return null
}
// the other two must be of similar magnitude
val diff = when(zeroAxis) {
Axis.X -> abs(abs(normal.y) - abs(normal.z))
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON) 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)
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
}
}
fun faceDistance(face: Direction, pos: Double3) = when(face) {
WEST -> pos.x; EAST -> 1.0 - pos.x
DOWN -> pos.y; UP -> 1.0 - pos.y
NORTH -> pos.z; SOUTH -> 1.0 - pos.z
}

View File

@@ -1,146 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.*
import java.awt.Color
typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite?
typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit
@Suppress("NOTHING_TO_INLINE")
class RenderVertex {
var x: Double = 0.0
var y: Double = 0.0
var z: Double = 0.0
var u: Double = 0.0
var v: Double = 0.0
var brightness: Int = 0
var red: Float = 0.0f
var green: Float = 0.0f
var blue: Float = 0.0f
val rawData = IntArray(7)
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
val result = vertex.xyz.rotate(rot) + trans
x = result.x; y = result.y; z = result.z
return this
}
fun init(vertex: Vertex): RenderVertex {
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
u = vertex.uv.u; v = vertex.uv.v
return this
}
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
fun rotate(rot: Rotation): RenderVertex {
if (rot === Rotation.identity) return this
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
x = rotX; y = rotY; z = rotZ
return this
}
inline fun rotateUV(n: Int): RenderVertex {
when (n % 4) {
1 -> { val t = v; v = -u; u = t; return this }
2 -> { u = -u; v = -v; return this }
3 -> { val t = -v; v = u; u = t; return this }
else -> { return this }
}
}
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
if (mirrorU) u = -u
if (mirrorV) v = -v
}
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
return this
}
inline fun setGrey(level: Float) {
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
red = grey; green = grey; blue = grey
}
inline fun multiplyColor(color: Int) {
red *= (color shr 16 and 255) / 256.0f
green *= (color shr 8 and 255) / 256.0f
blue *= (color and 255) / 256.0f
}
inline fun setColor(color: Int) {
red = (color shr 16 and 255) / 256.0f
green = (color shr 8 and 255) / 256.0f
blue = (color and 255) / 256.0f
}
}
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
var brightnessComponents = listOf(20, 4)
/** Multiply the components of this packed brightness value with the given [Float]. */
infix fun Int.brMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
var result = 0
brightnessComponents.forEach { shift ->
val raw = (this shr shift) and 15
val weighted = (raw) * weight / 256
result = result or (weighted shl shift)
}
return result
}
/** Multiply the components of this packed color value with the given [Float]. */
infix fun Int.colorMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
val red = (this shr 16 and 255) * weight / 256
val green = (this shr 8 and 255) * weight / 256
val blue = (this and 255) * weight / 256
return (red shl 16) or (green shl 8) or blue
}
/** Sum the components of all packed brightness values given. */
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
val sum = Array(brightnessComponents.size) { 0 }
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
val comp = (br shr shift) and 15
sum[idx] += comp
} }
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp = if (multiplier == null)
((sum[idx]) shl shift)
else
((sum[idx].toFloat() * multiplier).toInt() shl shift)
result = result or comp
}
return result
}
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
val w1int = (weight1 * 256.0f + 0.5f).toInt()
val w2int = (weight2 * 256.0f + 0.5f).toInt()
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp1 = (br1 shr shift) and 15
val comp2 = (br2 shr shift) and 15
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
result = result or ((compWeighted and 15) shl shift)
}
return result
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness)
}

View File

@@ -1,116 +0,0 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.canRenderInCutout
import mods.betterfoliage.render.isCutout
import mods.betterfoliage.render.lighting.DefaultLightingCtx
import mods.betterfoliage.render.lighting.LightingCtx
import mods.betterfoliage.render.lighting.PostProcessLambda
import mods.betterfoliage.render.lighting.QuadIconResolver
import mods.betterfoliage.render.lighting.RenderVertex
import mods.octarinecore.BufferBuilder_setSprite
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.plus
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.Vector3f
import net.minecraft.client.renderer.Vector4f
import net.minecraft.fluid.Fluids
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.LightType
import net.minecraft.world.level.ColorResolver
class CombinedContext(
val blockCtx: BlockCtx, val renderCtx: RenderCtx, val lightingCtx: DefaultLightingCtx
) : BlockCtx by blockCtx, LightingCtx by lightingCtx {
var hasRendered = false
fun render(force: Boolean = false) = renderCtx.let {
if (force || RenderTypeLookup.canRenderInLayer(state, it.layer) || (state.canRenderInCutout() && it.layer.isCutout)) {
it.render(blockCtx)
hasRendered = true
}
Unit
}
fun exchange(moddedOffset: Int3, targetOffset: Int3) = CombinedContext(
BasicBlockCtx(OffsetEnvBlockReader(blockCtx.world, pos + moddedOffset, pos + targetOffset), pos),
renderCtx,
lightingCtx
)
val isCutout = renderCtx.layer.isCutout
/** Get the centerpoint of the block being rendered. */
val blockCenter: Double3 get() = Double3((pos.x and 15) + 0.5, (pos.y and 15) + 0.5, (pos.z and 15) + 0.5)
/** Holds final vertex data before it goes to the [Tessellator]. */
val temp = RenderVertex()
fun render(
model: Model,
rotation: Rotation = Rotation.identity,
translation: Double3 = blockCenter,
forceFlat: Boolean = false,
quadFilter: (Int, Quad) -> Boolean = { _, _ -> true },
icon: QuadIconResolver,
postProcess: PostProcessLambda = noPost
) {
val cameraTransform = renderCtx.matrixStack.last
lightingCtx.modelRotation = rotation
model.quads.forEachIndexed { quadIdx, quad ->
if (quadFilter(quadIdx, quad)) {
val normal = quad.normal.let { Vector3f(it.x.toFloat(), it.y.toFloat(), it.z.toFloat()) }
normal.transform(cameraTransform.normal)
val drawIcon = icon(this, quadIdx, quad)
if (drawIcon != null) {
// let OptiFine know the texture we're using, so it can
// transform UV coordinates to quad-relative
BufferBuilder_setSprite.invoke(renderCtx.renderBuffer, drawIcon)
quad.verts.forEachIndexed { vertIdx, vert ->
temp.init(vert).rotate(lightingCtx.modelRotation)
.translate(translation)
val vertex = temp.let { Vector4f(it.x.toFloat(), it.y.toFloat(), it.z.toFloat(), 0.0F) }
.apply { transform(cameraTransform.matrix) }
val shader = if (lightingCtx.aoEnabled && !forceFlat) vert.aoShader else vert.flatShader
shader.shade(lightingCtx, temp)
temp.postProcess(this, quadIdx, quad, vertIdx, vert)
temp.setIcon(drawIcon)
renderCtx.renderBuffer
.pos(temp.x, temp.y, temp.z)
// .pos(vertex.x.toDouble(), vertex.y.toDouble(), vertex.z.toDouble())
.color(temp.red, temp.green, temp.blue, 1.0f)
.tex(temp.u.toFloat(), temp.v.toFloat())
.lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535)
.normal(quad.normal.x.toFloat(), quad.normal.y.toFloat(), quad.normal.z.toFloat())
// .normal(normal.x, normal.y, normal.z)
.endVertex()
}
}
}
}
hasRendered = true
}
}
val allFaces: (Direction) -> Boolean = { true }
val topOnly: (Direction) -> Boolean = { it == Direction.UP }
/** Perform no post-processing */
val noPost: PostProcessLambda = { _, _, _, _, _ -> }
object NonNullWorld : ILightReader {
override fun getBlockState(pos: BlockPos) = Blocks.AIR.defaultState
override fun getLightFor(type: LightType, pos: BlockPos) = 0
override fun getFluidState(pos: BlockPos) = Fluids.EMPTY.defaultState
override fun getTileEntity(pos: BlockPos) = null
override fun getLightManager() = null
override fun getBlockColor(p0: BlockPos, p1: ColorResolver) = 0
}

View File

@@ -0,0 +1,125 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.directionsAndNull
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
data class HalfBakedQuad(
val raw: Quad,
val baked: BakedQuad
)
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, ISpecialRenderModel {
val baseQuads = baseModel.unbakeQuads()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.render(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: ISpecialRenderModel): IBakedModel by baseModel, ISpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
baseModel.render(ctx, noDecorations)
}
}
abstract class HalfBakedWrapKey : ModelBakeKey, HasLogger() {
override fun replace(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? {
val baseModel = super.replace(location, unbaked, transform, bakery, spriteGetter)
val halfBaked = when(baseModel) {
is SimpleBakedModel -> HalfBakedSimpleModelWrapper(baseModel)
else -> null
}
return if (halfBaked == null) baseModel else replace(halfBaked)
}
abstract fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel
}
fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
if (quad.sprite == null) throw IllegalStateException("Quad must have a texture assigned before baking")
val builder = BakedQuadBuilder(quad.sprite)
builder.setApplyDiffuseLighting(applyDiffuseLighting)
builder.setQuadOrientation(quad.face())
builder.setQuadTint(quad.colorIndex)
quad.verts.forEach { vertex ->
DefaultVertexFormats.BLOCK.elements.forEachIndexed { idx, element ->
when {
element.usage == VertexFormatElement.Usage.POSITION -> builder.put(idx,
(vertex.xyz.x + 0.5).toFloat(),
(vertex.xyz.y + 0.5).toFloat(),
(vertex.xyz.z + 0.5).toFloat(),
1.0f
)
// don't fill lightmap UV coords
element.usage == VertexFormatElement.Usage.UV && element.type == VertexFormatElement.Type.FLOAT -> builder.put(idx,
quad.sprite.minU + (quad.sprite.maxU - quad.sprite.minU) * (vertex.uv.u + 0.5).toFloat(),
quad.sprite.minV + (quad.sprite.maxV - quad.sprite.minV) * (vertex.uv.v + 0.5).toFloat(),
0.0f, 1.0f
)
element.usage == VertexFormatElement.Usage.COLOR -> builder.put(idx,
(vertex.color.red and 255).toFloat() / 255.0f,
(vertex.color.green and 255).toFloat() / 255.0f,
(vertex.color.blue and 255).toFloat() / 255.0f,
(vertex.color.alpha and 255).toFloat() / 255.0f
)
element.usage == VertexFormatElement.Usage.NORMAL -> builder.put(idx,
(vertex.normal ?: quad.normal).x.toFloat(),
(vertex.normal ?: quad.normal).y.toFloat(),
(vertex.normal ?: quad.normal).z.toFloat(),
0.0f
)
else -> builder.put(idx)
}
}
}
HalfBakedQuad(quad, builder.build())
}
fun BakedQuad.unbake(): HalfBakedQuad {
val size = DefaultVertexFormats.BLOCK.integerSize
val verts = Array(4) { vIdx ->
val x = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 0])
val y = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 1])
val z = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 2])
val color = vertexData[vIdx * size + 3]
val u = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 4])
val v = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 5])
Vertex(Double3(x, y, z), UV(u.toDouble(), v.toDouble()), Color(color))
}
val unbaked = Quad(
verts[0], verts[1], verts[2], verts[3],
colorIndex = if (hasTintIndex()) tintIndex else -1,
face = face
)
return HalfBakedQuad(unbaked, this)
}
fun SimpleBakedModel.unbakeQuads() = directionsAndNull.flatMap { face ->
getQuads(null, face, Random()).map { it.unbake() }
}

View File

@@ -1,23 +1,31 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.lighting.ModelLighter
import mods.betterfoliage.render.lighting.NoLighting
import mods.betterfoliage.render.lighting.ShaderFactory
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minmax
import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.replace
import mods.betterfoliage.util.rotate
import mods.betterfoliage.util.times
import mods.betterfoliage.util.toImmutableList
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.client.renderer.vertex.VertexFormat
import net.minecraft.client.renderer.vertex.VertexFormatElement
import net.minecraft.client.renderer.vertex.VertexFormatElement.Type
import net.minecraft.client.renderer.vertex.VertexFormatElement.Usage
import net.minecraft.util.Direction
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
import java.lang.Math.max
import java.lang.Math.min
import java.util.Random
import kotlin.math.cos
import kotlin.math.sin
/**
* Vertex UV coordinates
@@ -34,7 +42,7 @@ data class UV(val u: Double, val v: Double) {
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) {
fun rotate(n: Int) = when (n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
@@ -55,105 +63,153 @@ data class UV(val u: Double, val v: Double) {
* @param[aoShader] [ModelLighter] instance to use with AO rendering
* @param[flatShader] [ModelLighter] instance to use with non-AO rendering
*/
data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val aoShader: ModelLighter = NoLighting,
val flatShader: ModelLighter = NoLighting
data class Vertex(
val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
val normal: Double3? = null
)
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(
combined shr 24 and 255,
combined shr 16 and 255,
combined shr 8 and 255,
combined and 255
)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = java.awt.Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
}
/**
* Model quad
* Intermediate representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) {
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: TextureAtlasSprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad =
Quad(trans(v1, 0), trans(v2, 1), trans(v3, 2), trans(v4, 3))
inline fun transformV(trans: (Vertex) -> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int) -> Vertex): Quad = copy(
v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3)
)
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotate(rot: Rotation) = transformV {
it.copy(xyz = it.xyz.rotate(rot), aoShader = it.aoShader.rotate(rot), flatShader = it.flatShader.rotate(rot))
fun scale(scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale(scale: Double3) =
transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun rotate(rot: Rotation) =
transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot))
fun rotateZ(angle: Double) = transformV {
it.copy(
xyz = Double3(
it.xyz.x * cos(angle) + it.xyz.z * sin(angle),
it.xyz.y,
it.xyz.z * cos(angle) - it.xyz.x * sin(angle)
),
normal = it.normal?.let { normal ->
Double3(
normal.x * cos(angle) + normal.z * sin(angle),
normal.y,
normal.z * cos(angle) - normal.x * sin(angle)
)
}
)
}
fun scaleUV(scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) }
fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun setAoShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(aoShader = factory(this@Quad, vertex))
}
fun setFlatShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex))
}
fun setFlatShader(shader: ModelLighter) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) }
val flipped: Quad get() = Quad(v4, v3, v2, v1)
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun cycleVertices(n: Int) = when(n % 4) {
fun sprite(sprite: TextureAtlasSprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Color?) = color(color ?: Color.white).colorIndex(if (color == null) 0 else -1)
fun face() = face ?: nearestAngle(normal, Direction.values().toList()) { it.vec }.first
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when (n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
}
/**
* Model. The basic unit of rendering blocks with OctarineCore.
*
* The model should be positioned so that (0,0,0) is the block center.
* The block extends to (-0.5, 0.5) in all directions (inclusive).
*/
class Model() {
constructor(other: List<Quad>) : this() { quads.addAll(other) }
val quads = mutableListOf<Quad>()
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex) -> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
fun Quad.add() = quads.add(this)
fun Iterable<Quad>.addAll() = forEach { quads.add(it) }
fun transformQ(trans: (Quad)-> Quad) = quads.replace(trans)
fun transformV(trans: (Vertex)-> Vertex) = quads.replace{ it.transformV(trans) }
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2); val xMax = max(x1, x2)
val zMin = min(z1, z2); val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2);
val xMax = max(x1, x2)
val zMin = min(z1, z2);
val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
Vertex(base + top + left, UV.topLeft),
Vertex(base - top + left, UV.bottomLeft),
Vertex(base - top - left, UV.bottomRight),
Vertex(base + top - left, UV.topRight)
)
)
}
}
}
val fullCube = Model().apply {
allDirections.forEach {
faceQuad(it)
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
.add()
}
}

View File

@@ -1,23 +0,0 @@
@file:JvmName("RendererHolder")
package mods.betterfoliage.render.old
import mods.betterfoliage.resource.ResourceHandler
import net.minecraft.block.BlockState
import net.minecraftforge.eventbus.api.IEventBus
abstract class RenderDecorator(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) {
open val renderOnCutout: Boolean get() = true
open val onlyOnCutout: Boolean get() = false
// ============================
// Custom rendering
// ============================
abstract fun isEligible(ctx: CombinedContext): Boolean
abstract fun render(ctx: CombinedContext)
}
data class BlockData(val state: BlockState, val color: Int, val brightness: Int)

View File

@@ -0,0 +1,43 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import net.minecraft.block.Block
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
abstract class RenderCtxBase(
world: ILightReader,
pos: BlockPos,
val matrixStack: MatrixStack,
val checkSides: Boolean,
val random: Random,
val modelData: IModelData
) : BlockCtx by BasicBlockCtx(world, pos) {
var hasRendered = false
val blockModelShapes = Minecraft.getInstance().blockRendererDispatcher.blockModelShapes
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldSideBeRendered(state, world, pos, this)
protected abstract fun renderQuad(quad: HalfBakedQuad)
abstract fun renderFallback(model: IBakedModel)
fun render(quads: Iterable<HalfBakedQuad>) {
quads.forEach { quad ->
if (quad.raw.face.shouldRender()) {
renderQuad(quad)
hasRendered = true
}
}
}
abstract fun renderMasquerade(offset: Int3, func: ()->Unit)
}

View File

@@ -0,0 +1,77 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.directionsAndNull
import mods.betterfoliage.util.get
import mods.octarinecore.VertexLighterFlat_blockInfo
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
world: ILightReader,
pos: BlockPos,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighterAccess {
val blockInfo = lighter[VertexLighterFlat_blockInfo]
override var vertexLighter: ForgeVertexLighter
get() = (lighter as ForgeVertexLighterAccess).vertexLighter
set(value) { (lighter as ForgeVertexLighterAccess).vertexLighter = value }
override fun renderQuad(quad: HalfBakedQuad) { quad.baked.pipe(lighter) }
override fun renderFallback(model: IBakedModel) {
directionsAndNull.forEach { face ->
model.getQuads(state, null, random, modelData).forEach { quad ->
if (quad.face.shouldRender()) {
quad.pipe(lighter)
hasRendered = true
}
}
}
}
override fun renderMasquerade(offset: Int3, func: () -> Unit) {
TODO("Not yet implemented")
}
companion object {
@JvmStatic
fun render(
lighter: VertexLighterFlat,
world: ILightReader,
model: ISpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
checkSides: Boolean,
rand: Random, seed: Long,
modelData: IModelData
): Boolean {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
rand.setSeed(seed)
lighter.updateBlockInfo()
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
model.render(it, false)
lighter.resetBlockInfo()
it.hasRendered
}
}
}
}

View File

@@ -0,0 +1,90 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
import mods.betterfoliage.render.lighting.VanillaVertexLighter
import mods.betterfoliage.render.lighting.VanillaQuadLighting
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
world: ILightReader,
pos: BlockPos,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val seed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
private val blockColors = renderer.blockColors
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
override fun renderQuad(quad: HalfBakedQuad) {
lightingData.let { lighting ->
vertexLighter.updateLightmapAndColor(quad, lighting)
buffer.addQuad(
matrixStack.last, quad.baked,
lighting.colorMultiplier,
lighting.tint[0], lighting.tint[1], lighting.tint[2],
lighting.packedLight, combinedOverlay, true
)
}
}
override fun renderFallback(model: IBakedModel) {
if (useAO) renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, seed, combinedOverlay, modelData)
else renderer.renderModelFlat(world, model, state, pos, matrixStack, buffer, checkSides, random, seed, combinedOverlay, modelData)
}
override fun renderMasquerade(offset: Int3, func: () -> Unit) {
lightingData.calc.blockPos += offset
func()
lightingData.calc.blockPos = pos
}
companion object {
@JvmStatic
fun render(
renderer: BlockModelRenderer,
world: ILightReader,
model: ISpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
rand: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
random.setSeed(rand)
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
lightingData.apply {
calc.reset(ctx)
blockColors = renderer.blockColors
}
model.render(ctx, false)
return ctx.hasRendered
}
val lightingData by ThreadLocalDelegate { VanillaQuadLighting() }
}
}

View File

@@ -1,10 +0,0 @@
package mods.betterfoliage.resource
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
typealias Identifier = ResourceLocation
typealias ModelIdentifier = ModelResourceLocation
typealias Sprite = TextureAtlasSprite

View File

@@ -1,151 +0,0 @@
package mods.betterfoliage.resource
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.completedVoid
import mods.betterfoliage.util.sink
import mods.octarinecore.client.resource.AsyncSpriteProvider
import mods.octarinecore.client.resource.AtlasFuture
import mods.octarinecore.client.resource.StitchPhases
import net.minecraft.resources.IResourceManager
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.IWorld
import net.minecraft.world.gen.SimplexNoiseGenerator
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
// ============================
// Resource types
// ============================
interface IConfigChangeListener { fun onConfigChange() }
interface IWorldLoadListener { fun onWorldLoad(world: IWorld) }
/**
* Base class for declarative resource handling.
*
* Resources are automatically reloaded/recalculated when the appropriate events are fired.
*
* @param[modId] mod ID associated with this handler (used to filter config change events)
*/
open class ResourceHandler(
val modId: String,
val modBus: IEventBus,
val targetAtlas: Atlas = Atlas.BLOCKS
) {
val resources = mutableListOf<Any>()
// ============================
// Self-registration
// ============================
init { modBus.register(this) }
// ============================
// Resource declarations
// ============================
fun sprite(id: Identifier) = sprite { id }
fun sprite(idFunc: ()->Identifier) = AsyncSpriteDelegate(idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
fun spriteSet(idFunc: (Int)->Identifier) = AsyncSpriteSet(targetAtlas, idFunc).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
fun spriteSetTransformed(check: (Int)->Identifier, register: (Identifier)->Identifier) =
AsyncSpriteSet(targetAtlas, check, register).apply { BetterFoliage.getSpriteManager(targetAtlas).providers.add(this) }
fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) }
fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) }
fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
fun simplexNoise() = SimplexNoise().apply { resources.add(this) }
// ============================
// Event registration
// ============================
@SubscribeEvent
fun handleModConfigChange(event: ModConfig.ModConfigEvent) {
resources.forEach { (it as? IConfigChangeListener)?.onConfigChange() }
}
@SubscribeEvent
fun handleWorldLoad(event: WorldEvent.Load) =
resources.forEach { (it as? IWorldLoadListener)?.onWorldLoad(event.world) }
}
// ============================
// Resource container classes
// ============================
class AsyncSpriteDelegate(val idFunc: ()->Identifier) : ReadOnlyProperty<Any, Sprite>, AsyncSpriteProvider<Any> {
protected lateinit var value: Sprite
override fun getValue(thisRef: Any, property: KProperty<*>) = value
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
sourceF.thenRun {
val sprite = atlas.sprite(idFunc())
atlas.runAfter {
sprite.handle { sprite, error -> value = sprite ?: atlas.missing.get()!! }
}
}
return StitchPhases(completedVoid(), completedVoid())
}
}
interface SpriteSet {
val num: Int
operator fun get(idx: Int): Sprite
}
class AsyncSpriteSet(val targetAtlas: Atlas = Atlas.BLOCKS, val idFunc: (Int)->Identifier, val transform: (Identifier)->Identifier = { it }) :
AsyncSpriteProvider<Any> {
var num = 0
protected set
protected var sprites: List<Sprite> = emptyList()
override fun setup(manager: IResourceManager, sourceF: CompletableFuture<Any>, atlas: AtlasFuture): StitchPhases {
var list: List<CompletableFuture<Sprite>> = emptyList()
return StitchPhases(
discovery = sourceF.sink {
list = (0 until 16).map { idFunc(it) }
.filter { manager.hasResource( targetAtlas.wrap(it)) }
.map { transform(it) }
.map { atlas.sprite(it) }
},
cleanup = atlas.runAfter {
sprites = list.filter { !it.isCompletedExceptionally }.map { it.get() }
if (sprites.isEmpty()) sprites = listOf(atlas.missing.get()!!)
num = sprites.size
}
)
}
operator fun get(idx: Int) = sprites[idx % num]
}
class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
var model: Model = Model()
override fun onConfigChange() { model = Model().apply(init) }
}
class ModelSet(val num: Int, val init: Model.(Int)->Unit): IConfigChangeListener {
val models = Array(num) { Model() }
override fun onConfigChange() { (0 until num).forEach { models[it] = Model().apply{ init(it) } } }
operator fun get(idx: Int) = models[idx % num]
}
class VectorSet(val num: Int, val init: (Int)-> Double3): IConfigChangeListener {
val models = Array(num) { Double3.zero }
override fun onConfigChange() { (0 until num).forEach { models[it] = init(it) } }
operator fun get(idx: Int) = models[idx % num]
}
class SimplexNoise : IWorldLoadListener {
var noise = SimplexNoiseGenerator(Random())
override fun onWorldLoad(world: IWorld) { noise = SimplexNoiseGenerator(Random(world.worldInfo.seed))
}
operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0)
operator fun get(pos: Int3) = get(pos.x, pos.z)
operator fun get(pos: BlockPos) = get(pos.x, pos.z)
}

View File

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

View File

@@ -0,0 +1,126 @@
package mods.betterfoliage.resource.discovery
import mods.betterfoliage.ModelDefinitionsLoadedEvent
import mods.betterfoliage.render.bakeSpecial
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.Invalidator
import mods.betterfoliage.util.SimpleInvalidator
import mods.betterfoliage.util.asBlockMaterial
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.VariantList
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.ModelBakeEvent
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.ForgeConfig
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.loading.progress.StartupMessageManager
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.LogManager
import java.lang.ref.WeakReference
import java.util.function.Function
interface ModelDiscovery {
fun onModelsLoaded(
bakery: ModelBakery,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
)
}
@FunctionalInterface
interface ModelBakeKey {
fun replace(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? = unbaked.bakeModel(bakery, spriteGetter, transform, location)
}
interface ModelWrapperKey : ModelBakeKey {
override fun replace(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? {
val baked = super.replace(location, unbaked, transform, bakery, spriteGetter) ?: return null
val sprites = { res: ResourceLocation -> spriteGetter.apply(res.asBlockMaterial) }
return replace(location, baked, sprites)
}
fun replace(
location: ResourceLocation,
wrapped: IBakedModel,
sprites: (ResourceLocation) -> TextureAtlasSprite
) = replace(wrapped)
fun replace(wrapped: IBakedModel) = wrapped
}
object BakeWrapperManager : Invalidator, HasLogger() {
val discoverers = mutableListOf<ModelDiscovery>()
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
val modelsValid = SimpleInvalidator()
val spritesValid = SimpleInvalidator()
private val replacements = mutableMapOf<ResourceLocation, ModelBakeKey>()
private val sprites = mutableSetOf<ResourceLocation>()
@SubscribeEvent
fun handleModelLoad(event: ModelDefinitionsLoadedEvent) {
modelsValid.invalidate()
StartupMessageManager.addModMessage("BetterFoliage: discovering models")
logger.log(INFO, "starting model discovery (${discoverers.size} listeners)")
discoverers.forEach { listener ->
val replacementsLocal = mutableMapOf<ResourceLocation, ModelBakeKey>()
listener.onModelsLoaded(event.bakery, sprites, replacementsLocal)
replacements.putAll(replacementsLocal)
}
}
@SubscribeEvent
fun handleStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation == Atlas.BLOCKS.resourceId) {
logger.log(INFO, "Adding ${sprites.size} sprites to block atlas")
spritesValid.invalidate()
sprites.forEach { event.addSprite(it) }
sprites.clear()
}
}
@SubscribeEvent
fun handleModelBake(event: ModelBakeEvent) {
replacements.clear()
}
fun onBake(
unbaked: IUnbakedModel,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>,
transform: IModelTransform,
location: ResourceLocation
): IBakedModel? {
// bake replacement if available
replacements[location]?.let { replacement ->
detailLogger.log(INFO, "Baking replacement for [${unbaked::class.java.simpleName}] $location -> $replacement")
return replacement.replace(location, unbaked, transform, bakery, spriteGetter)
}
// container model support
if (unbaked is VariantList) unbaked.bakeSpecial(bakery, spriteGetter)?.let {
detailLogger.log(INFO, "Wrapping container [${unbaked::class.java.simpleName}] $location")
return it
}
return unbaked.bakeModel(bakery, spriteGetter, transform, location)
}
}

View File

@@ -0,0 +1,19 @@
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, ModelBakeKey>
) {
Client.blockTypes = BlockTypeCache()
}
}
}

View File

@@ -1,143 +0,0 @@
package mods.betterfoliage.resource.discovery
import com.google.common.base.Joiner
import mods.betterfoliage.render.old.BlockCtx
import mods.octarinecore.client.resource.AsyncSpriteProvider
import mods.octarinecore.client.resource.AtlasFuture
import mods.octarinecore.client.resource.StitchPhases
import mods.betterfoliage.util.Int3
import mods.betterfoliage.config.IBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.findFirst
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.sinkAsync
import mods.betterfoliage.util.stripStart
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelShapes
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.ModelResourceLocation
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader
import net.minecraftforge.registries.ForgeRegistries
import java.util.concurrent.CompletableFuture
interface ModelRenderRegistry<T> {
operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos)
operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset)
operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
}
abstract class ModelRenderRegistryRoot<T> : ModelRenderRegistry<T> {
val registries = mutableListOf<ModelRenderRegistry<T>>()
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] }
}
/**
* Information about a single BlockState and all the IUnbakedModel it could render as.
*/
class ModelDiscoveryContext(
bakery: ModelBakery,
val state: BlockState,
val modelId: ModelResourceLocation
) {
val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId)
.filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) }
fun ModelBakery.unwrapVariants(modelAndLoc: Pair<IUnbakedModel, ResourceLocation>): List<Pair<IUnbakedModel, ResourceLocation>> = when(val model = modelAndLoc.first) {
is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) }
is BlockModel -> listOf(modelAndLoc)
else -> emptyList()
}
}
abstract class ModelDiscovery<T> : HasLogger, AsyncSpriteProvider<ModelBakery>, ModelRenderRegistry<T> {
var modelData: Map<BlockState, T> = emptyMap()
protected set
override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state]
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>?
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
val modelDataTemp = mutableMapOf<BlockState, CompletableFuture<T>>()
return StitchPhases(
discovery = bakeryF.sinkAsync { bakery ->
var errors = 0
bakery.iterateModels { ctx ->
try {
processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it }
} catch (e: Exception) {
errors++
}
}
log("${modelDataTemp.size} BlockStates discovered, $errors errors")
},
cleanup = atlas.runAfter {
modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() }
val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size
log("${modelData.size} BlockStates loaded, $errors errors")
}
)
}
fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) {
ForgeRegistries.BLOCKS.flatMap { block ->
block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) }
}.forEach { (state, stateModelResource) ->
func(ModelDiscoveryContext(this, state, stateModelResource))
}
}
}
abstract class ConfigurableModelDiscovery<T> : ModelDiscovery<T>() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(state: BlockState, textures: List<ResourceLocation>, atlas: AtlasFuture): CompletableFuture<T>?
override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture<T>? {
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
log("block state ${ctx.state}")
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
if (ctx.models.isEmpty()) {
log(" no models found")
return null
}
ctx.models.filter { it.first is BlockModel }.forEach { (model, location) ->
model as BlockModel
val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) }
if (modelMatch != null) {
log(" model ${model} matches ${modelMatch.modelLocation}")
val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it).textureLocation }
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
log(" sprites [$texMapString]")
if (textures.all { it.second != MissingTextureSprite.getLocation() }) {
// found a valid model (all required textures exist)
return processModel(ctx.state, textures.map { it.second}, atlas)
}
}
}
return null
}
}
fun Pair<BlockModel, ResourceLocation>.derivesFrom(targetLocation: ResourceLocation): Boolean {
if (second.stripStart("models/") == targetLocation) return true
if (first.parent != null && first.parentLocation != null)
return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation)
return false
}

View File

@@ -0,0 +1,121 @@
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
import org.apache.logging.log4j.Level
abstract class ModelReplacer : HasLogger(), ModelDiscovery {
override fun onModelsLoaded(
bakery: ModelBakery,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
) {
ForgeRegistries.BLOCKS
.flatMap { block -> block.stateContainer.validStates }
.forEach { state ->
val location = BlockModelShapes.getModelLocation(state)
try {
val hasReplaced = processModel(bakery, state, location, sprites, replacements)
} 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, ModelBakeKey>
): Boolean {
// 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
}
is Multipart -> model.variants.fold(false) { hasReplaced, variantList ->
variantList.variantList.fold(false) { hasReplaced, variant ->
processModel(bakery, state, variant.modelLocation, sprites, replacements) || hasReplaced
} || hasReplaced
}
else -> false
}
}
}
abstract class ConfigurableModelReplacer : ModelReplacer() {
abstract val matchClasses: IBlockMatcher
abstract val modelTextures: List<ModelTextureList>
abstract fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel) {
val matchClass = matchClasses.matchingClass(state.block) ?: return false
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}")
modelTextures
.filter { matcher -> bakery.modelDerivesFrom(model, location, matcher.modelLocation) }
.forEach { match ->
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}" })
detailLogger.log(Level.INFO, " sprites [$texMapString]")
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
}
}
}
return super.processModel(bakery, state, location, sprites, replacements)
}
}
fun ModelBakery.modelDerivesFrom(model: BlockModel, location: ResourceLocation, target: ResourceLocation): Boolean =
if (location == target) true
else model.parentLocation
?.let { getUnbakedModel(it) as? BlockModel }
?.let { parent -> modelDerivesFrom(parent, model.parentLocation!!, target) }
?: false

View File

@@ -1,19 +1,19 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.texture.loadSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.bytes
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
import java.lang.Math.max
data class CenteredSprite(val sprite: Identifier, val atlas: Atlas = Atlas.BLOCKS, val aspectHeight: Int = 1, val aspectWidth: Int = 1) {
data class CenteredSprite(val sprite: ResourceLocation, val aspectHeight: Int = 1, val aspectWidth: Int = 1, val atlas: Atlas = Atlas.BLOCKS) {
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val baseTexture = resourceManager.loadSprite(atlas.file(sprite))
val frameWidth = baseTexture.width
val frameHeight = baseTexture.width * aspectHeight / aspectWidth

View File

@@ -1,14 +1,13 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.texture.blendRGB
import mods.betterfoliage.texture.loadSprite
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.bytes
import mods.betterfoliage.util.get
import mods.betterfoliage.util.set
import mods.octarinecore.client.resource.*
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
import java.awt.image.BufferedImage
/**
@@ -17,13 +16,13 @@ import java.awt.image.BufferedImage
*
* @param[domain] Resource domain of generator
*/
data class GeneratedGrass(val sprite: Identifier, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
constructor(sprite: String, isSnowed: Boolean) : this(Identifier(sprite), isSnowed)
data class GeneratedGrass(val baseSprite: ResourceLocation, val isSnowed: Boolean, val atlas: Atlas = Atlas.BLOCKS) {
constructor(sprite: String, isSnowed: Boolean) : this(ResourceLocation(sprite), isSnowed)
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
val graphics = result.createGraphics()
@@ -47,7 +46,7 @@ data class GeneratedGrass(val sprite: Identifier, val isSnowed: Boolean, val atl
// blend with white if snowed
if (isSnowed) {
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
for (x in 0 until result.width) for (y in 0 until result.height) {
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
}
}

View File

@@ -8,7 +8,6 @@ import mods.betterfoliage.util.get
import mods.betterfoliage.util.loadImage
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.set
import mods.octarinecore.client.resource.*
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
import net.minecraft.util.ResourceLocation
@@ -22,12 +21,12 @@ import java.awt.image.BufferedImage
*
* @param[domain] Resource domain of generator
*/
data class GeneratedLeaf(val sprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
data class GeneratedLeaf(val baseSprite: ResourceLocation, val leafType: String, val atlas: Atlas = Atlas.BLOCKS) {
fun register(pack: GeneratedBlockTexturePack) = pack.register(this, this::draw)
fun register(pack: GeneratedTexturePack) = pack.register(atlas, this, this::draw)
fun draw(resourceManager: IResourceManager): ByteArray {
val baseTexture = resourceManager.loadSprite(atlas.wrap(sprite))
val baseTexture = resourceManager.loadSprite(atlas.file(baseSprite))
val size = baseTexture.width
val frames = baseTexture.height / size

View File

@@ -1,24 +1,16 @@
package mods.betterfoliage.resource.generated
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.completedVoid
import mods.betterfoliage.util.map
import mods.octarinecore.client.resource.AsyncSpriteProvider
import mods.octarinecore.client.resource.AtlasFuture
import mods.octarinecore.client.resource.StitchPhases
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.ClientResourcePackInfo
import net.minecraft.resources.*
import net.minecraft.resources.ResourcePackType.CLIENT_RESOURCES
import net.minecraft.resources.data.IMetadataSectionSerializer
import net.minecraft.util.ResourceLocation
import net.minecraft.util.text.StringTextComponent
import org.apache.logging.log4j.Logger
import java.io.IOException
import org.apache.logging.log4j.Level.INFO
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.function.Predicate
import java.util.function.Supplier
@@ -28,57 +20,46 @@ import java.util.function.Supplier
* @param[name] Name of the resource pack
* @param[generators] List of resource generators
*/
class GeneratedBlockTexturePack(val nameSpace: String, val packName: String, override val logger: Logger) : HasLogger, IResourcePack,
AsyncSpriteProvider<ModelBakery> {
class GeneratedTexturePack(
val nameSpace: String, val packName: String
) : IResourcePack {
val logger = BetterFoliageMod.detailLogger(this)
override fun getName() = packName
override fun getResourceNamespaces(type: ResourcePackType) = setOf(nameSpace)
override fun <T : Any?> getMetadata(deserializer: IMetadataSectionSerializer<T>) = null
override fun getRootResourceStream(id: String) = null
override fun getAllResourceLocations(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<Identifier>()
override fun getAllResourceLocations(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate<String>) = emptyList<ResourceLocation>()
override fun close() {}
protected var manager: CompletableFuture<IResourceManager>? = null
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, Identifier>())
val resources = Collections.synchronizedMap(mutableMapOf<Identifier, CompletableFuture<ByteArray>>())
protected var manager: IResourceManager = Minecraft.getInstance().resourceManager
val identifiers = Collections.synchronizedMap(mutableMapOf<Any, ResourceLocation>())
val resources = Collections.synchronizedMap(mutableMapOf<ResourceLocation, ByteArray>())
fun register(key: Any, func: (IResourceManager)->ByteArray): Identifier {
if (manager == null) throw IllegalStateException("Cannot register resources unless block textures are being reloaded")
fun register(atlas: Atlas, key: Any, func: (IResourceManager)->ByteArray): ResourceLocation {
identifiers[key]?.let { return it }
val id = Identifier(nameSpace, UUID.randomUUID().toString())
val resource = manager!!.map { func(it) }
val id = ResourceLocation(nameSpace, UUID.randomUUID().toString())
val fileName = atlas.file(id)
val resource = func(manager)
identifiers[key] = id
resources[Atlas.BLOCKS.wrap(id)] = resource
log("generated resource $key -> $id")
resources[fileName] = resource
logger.log(INFO, "generated resource $key -> $fileName")
return id
}
override fun getResourceStream(type: ResourcePackType, id: Identifier) =
if (type != CLIENT_RESOURCES) null else
try { resources[id]!!.get().inputStream() }
catch (e: ExecutionException) { (e.cause as? IOException)?.let { throw it } } // rethrow wrapped IOException if present
override fun getResourceStream(type: ResourcePackType, id: ResourceLocation) =
if (type != CLIENT_RESOURCES) null else resources[id]?.inputStream()
override fun resourceExists(type: ResourcePackType, id: Identifier) =
override fun resourceExists(type: ResourcePackType, id: ResourceLocation) =
type == CLIENT_RESOURCES && resources.containsKey(id)
override fun setup(manager: IResourceManager, bakeryF: CompletableFuture<ModelBakery>, atlas: AtlasFuture): StitchPhases {
this.manager = CompletableFuture.completedFuture(manager)
return StitchPhases(
completedVoid(),
atlas.runAfter {
this.manager = null
identifiers.clear()
resources.clear()
}
)
}
val finder = object : IPackFinder {
val packInfo = ClientResourcePackInfo(
packName, true, Supplier { this@GeneratedBlockTexturePack },
packName, true, Supplier { this@GeneratedTexturePack },
StringTextComponent(packName),
StringTextComponent("Generated block textures resource pack"),
PackCompatibility.COMPATIBLE, ResourcePackInfo.Priority.TOP, true, null, true
@@ -87,4 +68,4 @@ class GeneratedBlockTexturePack(val nameSpace: String, val packName: String, ove
(nameToPackMap as MutableMap<String, ResourcePackInfo>).put(packName, packInfo)
}
}
}
}

View File

@@ -0,0 +1,83 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.resourceManager
import net.minecraft.client.renderer.texture.MissingTextureSprite
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface SpriteSet {
val num: Int
operator fun get(idx: Int): TextureAtlasSprite
}
class FixedSpriteSet(val sprites: List<TextureAtlasSprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
}
class SpriteDelegate(val atlas: Atlas, val idFunc: () -> ResourceLocation) : ReadOnlyProperty<Any, TextureAtlasSprite> {
private lateinit var id: ResourceLocation
private var value: TextureAtlasSprite? = null
init {
BetterFoliageMod.bus.register(this)
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
id = idFunc(); value = null
event.addSprite(id)
}
override fun getValue(thisRef: Any, property: KProperty<*>): TextureAtlasSprite {
value?.let { return it }
synchronized(this) {
value?.let { return it }
atlas[id].let { value = it; return it }
}
}
}
class SpriteSetDelegate(
val atlas: Atlas,
val idRegister: (ResourceLocation) -> ResourceLocation = { it },
val idFunc: (Int) -> ResourceLocation
) : ReadOnlyProperty<Any, SpriteSet> {
private var idList: List<ResourceLocation> = emptyList()
private var spriteSet: SpriteSet? = null
init {
BetterFoliageMod.bus.register(this)
}
@SubscribeEvent
fun handlePreStitch(event: TextureStitchEvent.Pre) {
if (event.map.textureLocation != Atlas.BLOCKS.resourceId) return
spriteSet = null
idList = (0 until 16)
.map(idFunc)
.filter { resourceManager.hasResource(atlas.file(it)) }
.map(idRegister)
idList.forEach { event.addSprite(it) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): SpriteSet {
spriteSet?.let { return it }
synchronized(this) {
spriteSet?.let { return it }
spriteSet = FixedSpriteSet(
idList
.ifEmpty { listOf(MissingTextureSprite.getLocation()) }
.map { atlas[it] }
)
return spriteSet!!
}
}
}

View File

@@ -0,0 +1,129 @@
package mods.betterfoliage.resource.model
import mods.betterfoliage.render.block.vanilla.getColorOverride
import mods.betterfoliage.render.old.Color
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.bake
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.random
import mods.betterfoliage.util.randomB
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.randomI
import mods.betterfoliage.util.rot
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import kotlin.math.cos
import kotlin.math.sin
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx.toDouble() / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
data class TuftShapeKey(
val size: Double,
val height: Double,
val offset: Double3,
val flipU1: Boolean,
val flipU2: Boolean
)
fun tuftShapeSet(size: Double, heightMin: Double, heightMax: Double, hOffset: Double): Array<TuftShapeKey> {
return Array(64) { idx ->
TuftShapeKey(
size,
randomD(heightMin, heightMax),
xzDisk(idx) * randomD(hOffset / 2.0, hOffset),
randomB(),
randomB()
)
}
}
fun tuftQuadSingle(size: Double, height: Double, flipU: Boolean) =
Quad.verticalRectangle(
x1 = -0.5 * size,
z1 = 0.5 * size,
x2 = 0.5 * size,
z2 = -0.5 * size,
yBottom = 0.5,
yTop = 0.5 + height
)
.mirrorUV(flipU, false)
fun tuftModelSet(shapes: Array<TuftShapeKey>, overrideColor: Color?, spriteGetter: (Int) -> TextureAtlasSprite) =
shapes.mapIndexed { idx, shape ->
listOf(
tuftQuadSingle(shape.size, shape.height, shape.flipU1),
tuftQuadSingle(shape.size, shape.height, shape.flipU2).rotate(rot(UP))
).map { it.move(shape.offset) }
.map { it.colorAndIndex(overrideColor) }
.map { it.sprite(spriteGetter(idx)) }
}
fun fullCubeTextured(
spriteLocation: ResourceLocation,
overrideColor: Color?,
scrambleUV: Boolean = true
): List<HalfBakedQuad> {
val sprite = Atlas.BLOCKS[spriteLocation]
return allDirections.map { Quad.faceQuad(it) }
.map { if (!scrambleUV) it else it.rotateUV(randomI(max = 4)) }
.map { it.sprite(sprite) }
.map { it.colorAndIndex(overrideColor) }
.bake(true)
}
fun fullCubeTinted(spriteLocation: ResourceLocation, threshold: Double, scrambleUV: Boolean = true): List<HalfBakedQuad> {
val overrideColor = Atlas.BLOCKS[spriteLocation].getColorOverride(threshold)
return fullCubeTextured(spriteLocation, overrideColor, scrambleUV)
}
fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array<List<Quad>> {
return Array(num) { 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)
.rotate(rot(UP))
).map { it.scale(size) }
.map { it.move(xzDisk(idx) * hOffset) }
.map { it.move(UP.vec * randomD(-1.0, 1.0) * vOffset) }
}
}
fun crossModelSingle(base: List<Quad>, sprite: TextureAtlasSprite, overrideColor: Color?, scrambleUV: Boolean) =
base.map { if (scrambleUV) it.scrambleUV(random, canFlipU = true, canFlipV = true, canRotate = true) else it }
.map { it.colorAndIndex(overrideColor) }
.mapIndexed { idx, quad -> quad.sprite(sprite) }
.withOpposites()
.bake(false)
fun crossModelsTextured(
leafBase: Array<List<Quad>>,
overrideColor: Color?,
scrambleUV: Boolean,
spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf ->
crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], overrideColor, scrambleUV)
}.toTypedArray()
fun crossModelsTinted(
leafBase: Array<List<Quad>>,
threshold: Double,
scrambleUV: Boolean = true,
spriteGetter: (Int) -> ResourceLocation
) = leafBase.mapIndexed { idx, leaf ->
val sprite = Atlas.BLOCKS[spriteGetter(idx)]
val overrideColor = sprite.getColorOverride(threshold)
crossModelSingle(leaf, sprite, overrideColor, scrambleUV)
}
fun List<Quad>.withOpposites() = flatMap { listOf(it, it.flipped) }
fun List<List<Quad>>.buildTufts(applyDiffuseLighting: Boolean = false) =
map { it.withOpposites().bake(applyDiffuseLighting) }.toTypedArray()

View File

@@ -1,68 +0,0 @@
package mods.betterfoliage.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.lighting.HSB
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelRenderRegistryRoot
import mods.octarinecore.client.resource.*
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.util.averageColor
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import org.apache.logging.log4j.Level
import java.lang.Math.min
import java.util.concurrent.CompletableFuture
const val defaultGrassColor = 0
/** Rendering-related information for a grass block. */
class GrassInfo(
/** Top texture of the grass block. */
val grassTopTexture: TextureAtlasSprite,
/**
* Color to use for Short Grass rendering instead of the biome color.
*
* Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit),
* the average color of the texture otherwise.
*/
val overrideColor: Int?
)
object GrassRegistry : ModelRenderRegistryRoot<GrassInfo>()
object AsyncGrassDiscovery : ConfigurableModelDiscovery<GrassInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture): CompletableFuture<GrassInfo> {
val textureName = textures[0]
val spriteF = atlas.sprite(textureName)
logger.log(Level.DEBUG, "$logName: texture $textureName")
return atlas.mapAfter {
val sprite = spriteF.get()
logger.log(Level.DEBUG, "$logName: block state $state")
logger.log(Level.DEBUG, "$logName: texture $textureName")
val hsb = HSB.fromColor(sprite.averageColor)
val overrideColor = if (hsb.saturation >= Config.shortGrass.saturationThreshold) {
logger.log(Level.DEBUG, "$logName: brightness ${hsb.brightness}")
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} >= ${Config.shortGrass.saturationThreshold}, using texture color")
hsb.copy(brightness = min(0.9f, hsb.brightness * 2.0f)).asColor
} else {
logger.log(Level.DEBUG, "$logName: saturation ${hsb.saturation} < ${Config.shortGrass.saturationThreshold}, using block color")
null
}
GrassInfo(sprite, overrideColor)
}
}
fun init() {
GrassRegistry.registries.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}

View File

@@ -1,69 +1,49 @@
package mods.betterfoliage.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.Sprite
import mods.betterfoliage.resource.SpriteSet
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.get
import mods.betterfoliage.util.getLines
import mods.betterfoliage.util.resourceManager
import mods.betterfoliage.util.sinkAsync
import mods.betterfoliage.util.stripStart
import mods.octarinecore.client.resource.AsyncSpriteProvider
import mods.octarinecore.client.resource.AtlasFuture
import mods.octarinecore.client.resource.StitchPhases
import net.minecraft.client.particle.ParticleManager
import net.minecraft.resources.IResourceManager
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.ResourceLocation
import java.util.concurrent.CompletableFuture
class FixedSpriteSet(val sprites: List<Sprite>) : SpriteSet {
override val num = sprites.size
override fun get(idx: Int) = sprites[idx % num]
}
object LeafParticleRegistry : AsyncSpriteProvider<ParticleManager> {
object LeafParticleRegistry {
val targetAtlas = Atlas.PARTICLES
val typeMappings = TextureMatcher()
val particles = hashMapOf<String, SpriteSet>()
// val particles = hashMapOf<String, SpriteSet>()
operator fun get(type: String) = particles[type] ?: particles["default"]!!
val futures = mutableMapOf<String, List<CompletableFuture<TextureAtlasSprite>>>()
override fun setup(manager: IResourceManager, particleF: CompletableFuture<ParticleManager>, atlasFuture: AtlasFuture): StitchPhases {
particles.clear()
val futures = mutableMapOf<String, List<CompletableFuture<Sprite>>>()
// operator fun get(type: String) = particles[type] ?: particles["default"]!!
return StitchPhases(
discovery = particleF.sinkAsync {
typeMappings.loadMappings(Identifier(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
val ids = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
val wids = ids.map { Atlas.PARTICLES.wrap(it) }
futures[leafType] = (0 until 16).map { idx -> Identifier(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
.filter { manager.hasResource(Atlas.PARTICLES.wrap(it)) }
.map { atlasFuture.sprite(it) }
}
},
cleanup = atlasFuture.runAfter {
futures.forEach { leafType, spriteFutures ->
val sprites = spriteFutures.filter { !it.isCompletedExceptionally }.map { it.get() }
if (sprites.isNotEmpty()) particles[leafType] = FixedSpriteSet(sprites)
}
if (particles["default"] == null) particles["default"] = FixedSpriteSet(listOf(atlasFuture.missing.get()!!))
}
)
fun discovery() {
typeMappings.loadMappings(ResourceLocation(BetterFoliageMod.MOD_ID, "leaf_texture_mappings.cfg"))
(typeMappings.mappings.map { it.type } + "default").distinct().forEach { leafType ->
val ids = (0 until 16).map { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
val wids = ids.map { Atlas.PARTICLES.file(it) }
// futures[leafType] = (0 until 16).map { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "falling_leaf_${leafType}_$idx") }
// .filter { manager.hasResource(Atlas.PARTICLES.wrap(it)) }
// .map { atlasFuture.sprite(it) }
}
}
fun init() {
BetterFoliage.particleSprites.providers.add(this)
fun cleanup() {
// futures.forEach { leafType, spriteFutures ->
// val sprites = spriteFutures.filter { !it.isCompletedExceptionally }.map { it.get() }
// if (sprites.isNotEmpty()) particles[leafType] = FixedSpriteSet(sprites)
// }
// if (particles["default"] == null) particles["default"] = FixedSpriteSet(listOf(atlasFuture.missing.get()!!))
}
}
class TextureMatcher {
data class Mapping(val domain: String?, val path: String, val type: String) {
fun matches(iconLocation: Identifier): Boolean {
fun matches(iconLocation: ResourceLocation): Boolean {
return (domain == null || domain == iconLocation.namespace) &&
iconLocation.path.stripStart("blocks/").contains(path, ignoreCase = true)
}
@@ -71,10 +51,9 @@ class TextureMatcher {
val mappings: MutableList<Mapping> = mutableListOf()
fun getType(resource: Identifier) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun getType(iconName: String) = Identifier(iconName).let { getType(it) }
fun getType(resource: ResourceLocation) = mappings.filter { it.matches(resource) }.map { it.type }.firstOrNull()
fun loadMappings(mappingLocation: Identifier) {
fun loadMappings(mappingLocation: ResourceLocation) {
mappings.clear()
resourceManager[mappingLocation]?.getLines()?.let { lines ->
lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line ->

View File

@@ -1,62 +0,0 @@
package mods.betterfoliage.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.SpriteSet
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelRenderRegistryRoot
import mods.betterfoliage.resource.generated.GeneratedLeaf
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.averageColor
import mods.octarinecore.client.resource.*
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import java.util.concurrent.CompletableFuture
const val defaultLeafColor = 0
/** Rendering-related information for a leaf block. */
class LeafInfo(
/** The generated round leaf texture. */
val roundLeafTexture: TextureAtlasSprite,
/** Type of the leaf block (configurable by user). */
val leafType: String,
/** Average color of the round leaf texture. */
val averageColor: Int
) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: SpriteSet get() = LeafParticleRegistry[leafType]
}
object LeafRegistry : ModelRenderRegistryRoot<LeafInfo>()
object AsyncLeafDiscovery : ConfigurableModelDiscovery<LeafInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture) = defaultRegisterLeaf(textures[0], atlas)
fun init() {
LeafRegistry.registries.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}
fun HasLogger.defaultRegisterLeaf(sprite: Identifier, atlas: AtlasFuture): CompletableFuture<LeafInfo> {
val leafType = LeafParticleRegistry.typeMappings.getType(sprite) ?: "default"
val generated = GeneratedLeaf(sprite, leafType).register(BetterFoliage.asyncPack)
val roundLeaf = atlas.sprite(generated)
val leafTex = atlas.sprite(sprite)
log(" leaf texture $sprite")
log(" particle $leafType")
return atlas.mapAfter {
LeafInfo(roundLeaf.get(), leafType, leafTex.get().averageColor)
}
}

View File

@@ -1,10 +1,10 @@
@file:JvmName("Utils")
package mods.betterfoliage.texture
import mods.betterfoliage.resource.Identifier
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 {
@@ -12,8 +12,8 @@ fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
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).toInt()
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b)
return result
}
fun IResourceManager.loadSprite(id: Identifier) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
fun IResourceManager.loadSprite(id: ResourceLocation) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")

View File

@@ -0,0 +1,9 @@
package mods.betterfoliage.util
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW }
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT)

View File

@@ -17,6 +17,10 @@ interface Invalidator {
}
}
class SimpleInvalidator : Invalidator {
override val callbacks = mutableListOf<WeakReference<() -> Unit>>()
}
class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty<Any, V> {
init { invalidator.onInvalidate { value = null } }
@@ -31,7 +35,7 @@ class LazyInvalidatable<V>(invalidator: Invalidator, val valueFactory: ()->V): R
}
}
class LazyMap<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
open class LazyMapInvalidatable<K, V>(val invalidator: Invalidator, val valueFactory: (K)->V) {
init { invalidator.onInvalidate { values.clear() } }
val values = mutableMapOf<K, V>()

View File

@@ -1,5 +1,6 @@
package mods.betterfoliage.util
import com.google.common.collect.ImmutableList
import java.util.*
/**
@@ -27,6 +28,8 @@ inline fun <T, C: Comparable<C>> Triple<T, T, T>.maxValueBy(func: (T)->C): C {
return result
}
inline fun <reified T, reified R> Array<T>.mapArray(func: (T)->R) = Array<R>(size) { idx -> func(get(idx)) }
@Suppress("UNCHECKED_CAST")
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
@@ -61,3 +64,8 @@ inline fun <T> MutableList<T>.exchange(idx1: Int, idx2: Int) {
/** Return a random element from the array using the provided random generator */
inline operator fun <T> Array<T>.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size)
fun <T> Iterable<T>.toImmutableList() = ImmutableList.builder<T>().let { builder ->
forEach { builder.add(it) }
builder.build()
}

View File

@@ -7,6 +7,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
// ================================
// Axes and directions
// ================================
@@ -22,8 +24,19 @@ val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
Z to POSITIVE -> SOUTH; else -> NORTH;
}
val Direction.perpendiculars: List<Direction> get() =
axes.filter { it != this.axis }.cross(axisDirs).map { it.face }
val directionsAndNull = arrayOf(DOWN, UP, NORTH, SOUTH, WEST, EAST, null)
val Direction.perpendiculars: Array<Direction> get() =
axes.filter { it != this.axis }.flatMap { listOf((it to POSITIVE).face, (it to NEGATIVE).face) }.toTypedArray()
val perpendiculars: Array<Array<Direction>> = Direction.values().map { dir ->
axes.filter { it != dir.axis }
.flatMap { listOf(
(it to POSITIVE).face,
(it to NEGATIVE).face
) }.toTypedArray()
}.toTypedArray()
val Direction.offset: Int3 get() = allDirOffsets[ordinal]
/** Old ForgeDirection rotation matrix yanked from 1.7.10 */
@@ -172,6 +185,14 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
// Forge rotation matrix is left-hand
val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) }
val identity = Rotation(allDirections, allDirections)
val fromUp = arrayOf(
rot90[EAST.ordinal] * 2,
identity,
rot90[WEST.ordinal],
rot90[EAST.ordinal],
rot90[SOUTH.ordinal],
rot90[NORTH.ordinal]
)
}
}

View File

@@ -1,6 +1,7 @@
@file:Suppress("NOTHING_TO_INLINE")
package mods.betterfoliage.util
import mods.betterfoliage.BetterFoliageMod
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
@@ -49,6 +50,11 @@ fun nextPowerOf2(x: Int): Int {
return 1 shl (if (x == 0) 0 else 32 - Integer.numberOfLeadingZeros(x - 1))
}
abstract class HasLogger {
val logger = BetterFoliageMod.logger(this)
val detailLogger = BetterFoliageMod.detailLogger(this)
}
/**
* Check if the Chunk containing the given [BlockPos] is loaded.
* Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances.
@@ -60,15 +66,6 @@ fun nextPowerOf2(x: Int): Int {
// else -> false
//}
interface HasLogger {
val logger: Logger
val logName: String get() = this::class.simpleName!!
fun log(msg: String) = log(Level.INFO, msg)
fun log(level: Level, msg: String) = logger.log(level, "[$logName] $msg")
fun log(msg: String, e: Throwable) = log(Level.WARN, msg, e)
fun log(level: Level, msg: String, e: Throwable) = logger.log(level, "[$logName] $msg", e)
}
//fun textComponent(msg: String, color: Formatting = Formatting.GRAY): LiteralText {
// val style = Style().apply { this.color = color }
// return LiteralText(msg).apply { this.style = style }

View File

@@ -1,6 +1,8 @@
package mods.betterfoliage.util
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.resources.IReloadableResourceManager
import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager
@@ -13,6 +15,15 @@ val resourceManager: IReloadableResourceManager
/** Append a string to the [ResourceLocation]'s path. */
operator fun ResourceLocation.plus(str: String) = ResourceLocation(namespace, path + str)
/** Prepend a string to the [ResourceLocation]'s path. */
fun ResourceLocation.prependLocation(basePath: String) =
ResourceLocation(namespace, basePath.stripEnd("/").let { "$it/$path" })
val ResourceLocation.asBlockMaterial: Material get() = Material(
AtlasTexture.LOCATION_BLOCKS_TEXTURE,
this
)
/** Index operator to get a resource. */
operator fun IResourceManager.get(domain: String, path: String): IResource? = get(ResourceLocation(domain, path))
/** Index operator to get a resource. */

View File

@@ -1,7 +1,8 @@
package mods.betterfoliage.util
import mods.betterfoliage.render.lighting.HSB
import mods.betterfoliage.render.old.HSB
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.texture.AtlasTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.resources.IResource
@@ -15,29 +16,33 @@ import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
enum class Atlas(val basePath: String, val resourceId: ResourceLocation) {
BLOCKS("textures", AtlasTexture.LOCATION_BLOCKS_TEXTURE),
PARTICLES("textures", AtlasTexture.LOCATION_PARTICLES_TEXTURE);
enum class Atlas(val resourceId: ResourceLocation) {
BLOCKS(AtlasTexture.LOCATION_BLOCKS_TEXTURE),
PARTICLES(AtlasTexture.LOCATION_PARTICLES_TEXTURE);
/** Get the fully-qualified resource name for sprites belonging to this atlas*/
fun wrap(resource: ResourceLocation) = ResourceLocation(resource.namespace, "$basePath/${resource.path}.png")
/** Get the short resource name for sprites belonging to this atlas*/
fun unwrap(resource: ResourceLocation) = resource.stripStart("$basePath/").stripEnd(".png")
/** Get the fully-qualified resource name for sprites belonging to this atlas */
fun file(resource: ResourceLocation) = ResourceLocation(resource.namespace, "textures/${resource.path}.png")
/** Reference to the atlas itself */
val atlas: AtlasTexture get() = Minecraft.getInstance().textureManager.getTexture(resourceId) as AtlasTexture
private val atlas: AtlasTexture get() = Minecraft.getInstance().textureManager.getTexture(resourceId) as AtlasTexture
/** Get a sprite from this atlas */
operator fun get(location: ResourceLocation) = atlas.getSprite(location)
}
val Material.atlas: Atlas get() = Atlas.values().find { it.resourceId == atlasLocation } ?: Atlas.BLOCKS
inline operator fun AtlasTexture.get(res: ResourceLocation): TextureAtlasSprite? = this.getSprite(res)
inline operator fun AtlasTexture.get(name: String): TextureAtlasSprite? = get(ResourceLocation(name))
fun IResourceManager.loadSprite(id: ResourceLocation) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
fun IResourceManager.loadSprite(id: ResourceLocation) =
this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id")
fun IResource.loadImage(): BufferedImage? = ImageIO.read(this.inputStream)
/** Index operator to get the RGB value of a pixel. */
operator fun BufferedImage.get(x: Int, y: Int) = this.getRGB(x, y)
/** Index operator to set the RGB value of a pixel. */
operator fun BufferedImage.set(x: Int, y: Int, value: Int) = this.setRGB(x, y, value)
@@ -50,30 +55,31 @@ val BufferedImage.bytes: ByteArray get() =
* Only non-transparent pixels are considered. Averages are taken in the HSB color space (note: Hue is a circular average),
* and the result transformed back to the RGB color space.
*/
val TextureAtlasSprite.averageColor: Int get() {
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0 until width)
for (y in 0 until height) {
val pixel = getPixelRGBA(0, x, y)
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
val TextureAtlasSprite.averageColor: HSB
get() {
var numOpaque = 0
var sumHueX = 0.0
var sumHueY = 0.0
var sumSaturation = 0.0f
var sumBrightness = 0.0f
for (x in 0 until width)
for (y in 0 until height) {
val pixel = getPixelRGBA(0, x, y)
val alpha = (pixel shr 24) and 255
val hsb = HSB.fromColor(pixel)
if (alpha == 255) {
numOpaque++
sumHueX += cos((hsb.hue.toDouble() - 0.5) * PI2)
sumHueY += sin((hsb.hue.toDouble() - 0.5) * PI2)
sumSaturation += hsb.saturation
sumBrightness += hsb.brightness
}
}
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat()).asColor
}
// circular average - transform sum vector to polar angle
val avgHue = (atan2(sumHueY, sumHueX) / PI2 + 0.5).toFloat()
return HSB(avgHue, sumSaturation / numOpaque.toFloat(), sumBrightness / numOpaque.toFloat())
}
/** Weighted blend of 2 packed RGB colors */
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {

View File

@@ -1,10 +1,14 @@
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace <init>
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b #vertexColorMultiplier
public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c #vertexBrightness
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace <init>
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b #vertexColorMultiplier
#public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c #vertexBrightness
public net.minecraft.block.BlockState$Cache
#public net.minecraft.block.BlockState$Cache
public net.minecraft.client.renderer.chunk.ChunkRenderCache field_212408_i #world
public net.minecraft.client.renderer.texture.AtlasTexture$SheetData field_217808_d # sprites
#public net.minecraft.client.renderer.texture.AtlasTexture$SheetData field_217808_d # sprites
public net.minecraft.client.renderer.BlockModelRenderer$Cache
public net.minecraft.client.renderer.BlockModelRenderer field_210267_b
public net.minecraft.client.renderer.BlockModelRenderer field_187499_a

View File

@@ -9,10 +9,11 @@
"client": [
"MixinBlock",
"MixinBlockState",
"MixinChunkRender",
"MixinBlockModelRenderer",
"MixinClientWorld",
"MixinModelBakery",
"MixinParticleManager"
"MixinForgeBlockModelRenderer",
"MixinForgeCustomVertexLighting"
],
"server": [
],

View File

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

View File

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