diff --git a/.gitignore b/.gitignore index 8713f47..42a6791 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ run/ build/ classes/ temp/ +logs diff --git a/build.gradle.kts b/build.gradle.kts index 25bbe4a..96e01b4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") + } } diff --git a/gradle.properties b/gradle.properties index 80390a0..90df1b7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/mods/betterfoliage/MixinConnector.java b/src/main/java/mods/betterfoliage/MixinConnector.java index ee57666..add3c1a 100644 --- a/src/main/java/mods/betterfoliage/MixinConnector.java +++ b/src/main/java/mods/betterfoliage/MixinConnector.java @@ -14,6 +14,5 @@ public class MixinConnector implements IMixinConnector { } catch (ClassNotFoundException e) { Mixins.addConfiguration("betterfoliage.vanilla.mixins.json"); } - } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinBlockModelRenderer.java b/src/main/java/mods/betterfoliage/mixin/MixinBlockModelRenderer.java new file mode 100644 index 0000000..e052a03 --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinBlockModelRenderer.java @@ -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); + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java b/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java deleted file mode 100644 index 8b0f28b..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinChunkRender.java +++ /dev/null @@ -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()); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java b/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java deleted file mode 100644 index 173a4c0..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinChunkRenderVanilla.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinForgeBlockModelRenderer.java b/src/main/java/mods/betterfoliage/mixin/MixinForgeBlockModelRenderer.java new file mode 100644 index 0000000..00a2d9a --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinForgeBlockModelRenderer.java @@ -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); + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinForgeCustomVertexLighting.java b/src/main/java/mods/betterfoliage/mixin/MixinForgeCustomVertexLighting.java new file mode 100644 index 0000000..f3703ca --- /dev/null +++ b/src/main/java/mods/betterfoliage/mixin/MixinForgeCustomVertexLighting.java @@ -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; + } +} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java b/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java index 24e2d1d..855d3cc 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinModelBakery.java @@ -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 idStream, IProfiler profiler, int maxMipmapLevel) { - Set idSetIn = idStream.collect(Collectors.toSet()); - Set 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 spriteGetter, + IModelTransform transform, + ResourceLocation locationIn + ) { + return BakeWrapperManager.INSTANCE.onBake(unbaked, bakery, spriteGetter, transform, locationIn); } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java b/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java index c144287..bfdddbb 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinOptifineBlockUtils.java @@ -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;"; diff --git a/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java b/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java index e06d41a..b90e420 100644 --- a/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java +++ b/src/main/java/mods/betterfoliage/mixin/MixinOptifineChunkRender.java @@ -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]); +// } } diff --git a/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java b/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java deleted file mode 100644 index fbd3346..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinParticleManager.java +++ /dev/null @@ -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 idStream, IProfiler profiler, int maxMipmapLevel) { - Set idSetIn = idStream.collect(Collectors.toSet()); - Set 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); - } -} diff --git a/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java b/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java deleted file mode 100644 index 31a463f..0000000 --- a/src/main/java/mods/betterfoliage/mixin/MixinShadersBlockModelRenderer.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/net/optifine/util/BlockUtils.java b/src/main/java/net/optifine/util/BlockUtils.java deleted file mode 100644 index 0f9706c..0000000 --- a/src/main/java/net/optifine/util/BlockUtils.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.optifine.util; - -public class BlockUtils { - // whyyyy? -} diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt b/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt index d1701cd..7f9d460 100644 --- a/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt +++ b/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt @@ -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("bf-blocks-extra") - val particleSprites = AsnycSpriteProviderManager("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 - - 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") - } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt b/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt index bb16ed3..3b3afd0 100644 --- a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt +++ b/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt @@ -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() } diff --git a/src/main/kotlin/mods/betterfoliage/Client.kt b/src/main/kotlin/mods/betterfoliage/Client.kt index c55d8c8..a392bef 100644 --- a/src/main/kotlin/mods/betterfoliage/Client.kt +++ b/src/main/kotlin/mods/betterfoliage/Client.kt @@ -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() - var configListeners = emptyList() + val asyncPack = GeneratedTexturePack("bf_gen", "Better Foliage generated assets") + var blockTypes = BlockTypeCache() val suppressRenderErrors = mutableSetOf() 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() - configListeners.forEach { it.onConfigChange() } } } diff --git a/src/main/kotlin/mods/betterfoliage/CommonRefs.kt b/src/main/kotlin/mods/betterfoliage/CommonRefs.kt index eb7a38d..a3344c1 100644 --- a/src/main/kotlin/mods/betterfoliage/CommonRefs.kt +++ b/src/main/kotlin/mods/betterfoliage/CommonRefs.kt @@ -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("java.lang.String") -val Map = ClassRef>("java.util.Map") val List = ClassRef>("java.util.List") val Random = ClassRef("java.util.Random") +fun mapRef() = ClassRef>("java.util.Map") +fun mapRefMutable() = ClassRef>("java.util.Map") // Minecraft val IBlockReader = ClassRef("net.minecraft.world.IBlockReader") @@ -38,6 +45,19 @@ val BlockRendererDispatcher = ClassRef("net.minecraft.c val ChunkRenderCache = ClassRef("net.minecraft.client.renderer.chunk.ChunkRenderCache") val ResourceLocation = ClassRef("net.minecraft.util.ResourceLocation") val BakedQuad = ClassRef("net.minecraft.client.renderer.model.BakedQuad") +val BlockModelRenderer = ClassRef("net.minecraft.client.renderer.BlockModelRenderer") + +val VertexLighterFlat = ClassRef("net.minecraftforge.client.model.pipeline.VertexLighterFlat") +val BlockInfo = ClassRef("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("net.minecraft.client.renderer.model.ModelBakery") { + val unbakedModels = FieldRef(this, "unbakedModels", mapRefMutable()) + val topUnbakedModels = FieldRef(this, "topUnbakedModels", mapRefMutable()) +} // Optifine val OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer") diff --git a/src/main/kotlin/mods/betterfoliage/Events.kt b/src/main/kotlin/mods/betterfoliage/Events.kt new file mode 100644 index 0000000..b638d1f --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/Events.kt @@ -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() \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/Hooks.kt b/src/main/kotlin/mods/betterfoliage/Hooks.kt index 139c558..f984317 100644 --- a/src/main/kotlin/mods/betterfoliage/Hooks.kt +++ b/src/main/kotlin/mods/betterfoliage/Hooks.kt @@ -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) = - 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() diff --git a/src/main/kotlin/mods/betterfoliage/render/old/BlockContext.kt b/src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt similarity index 55% rename from src/main/kotlin/mods/betterfoliage/render/old/BlockContext.kt rename to src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt index 08e6c04..59fe1bd 100644 --- a/src/main/kotlin/mods/betterfoliage/render/old/BlockContext.kt +++ b/src/main/kotlin/mods/betterfoliage/chunk/BlockContext.kt @@ -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 = 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(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) -} - diff --git a/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt b/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt index f415ca0..7c8f208 100644 --- a/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt +++ b/src/main/kotlin/mods/betterfoliage/chunk/Overlay.kt @@ -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 diff --git a/src/main/kotlin/mods/betterfoliage/config/Config.kt b/src/main/kotlin/mods/betterfoliage/config/Config.kt index edad71d..14f44c8 100644 --- a/src/main/kotlin/mods/betterfoliage/config/Config.kt +++ b/src/main/kotlin/mods/betterfoliage/config/Config.kt @@ -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) { diff --git a/src/main/kotlin/mods/betterfoliage/config/Matchers.kt b/src/main/kotlin/mods/betterfoliage/config/Matchers.kt index db32abc..f58a1c6 100644 --- a/src/main/kotlin/mods/betterfoliage/config/Matchers.kt +++ b/src/main/kotlin/mods/betterfoliage/config/Matchers.kt @@ -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>() val whiteList = mutableListOf>() @@ -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() 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))) diff --git a/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt b/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt deleted file mode 100644 index 719fcda..0000000 --- a/src/main/kotlin/mods/betterfoliage/integration/ForestryIntegration.kt +++ /dev/null @@ -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("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("forestry.arboriculture.tiles.TileLeaves") -val TileLeaves_getLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", ResourceLocation, boolean) -val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.PropertyWoodType") -val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType") -val IWoodType_barkTex = MethodRef(IWoodType, "getBarkTexture", String) -val IWoodType_heartTex = MethodRef(IWoodType, "getHeartTexture", String) - -val PropertyTreeType = ClassRef("forestry.arboriculture.blocks.PropertyTreeType") -val IAlleleTreeSpecies = ClassRef("forestry.api.arboriculture.IAlleleTreeSpecies") -val ILeafSpriteProvider = ClassRef("forestry.api.arboriculture.ILeafSpriteProvider") -val TreeDefinition = ClassRef("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, ModelRenderRegistry { - override val logger = BetterFoliage.logDetail - var idToValue = emptyMap() - - 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, atlasFuture: AtlasFuture): StitchPhases { - val futures = mutableMapOf>() - - 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() { - override val logger = BetterFoliage.logDetail - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - // 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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/integration/OptifineCustomColors.kt b/src/main/kotlin/mods/betterfoliage/integration/OptifineCustomColors.kt index c3dd072..906ca70 100644 --- a/src/main/kotlin/mods/betterfoliage/integration/OptifineCustomColors.kt +++ b/src/main/kotlin/mods/betterfoliage/integration/OptifineCustomColors.kt @@ -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("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 } } diff --git a/src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt b/src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt deleted file mode 100644 index 5c6c467..0000000 --- a/src/main/kotlin/mods/betterfoliage/integration/RubberIntegration.kt +++ /dev/null @@ -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("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("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 -) : 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() { - override val logger = BetterFoliage.logDetail - - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - // 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 ?: 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() { - override val logger = BetterFoliage.logDetail - - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - // 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 }.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 - } -} - */ diff --git a/src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt b/src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt index 52cb22d..1aeae12 100644 --- a/src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/integration/ShadersModIntegration.kt @@ -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) } diff --git a/src/main/kotlin/mods/betterfoliage/render/EntityFallingLeavesFX.kt b/src/main/kotlin/mods/betterfoliage/render/EntityFallingLeavesFX.kt index bc01cdb..3afd824 100644 --- a/src/main/kotlin/mods/betterfoliage/render/EntityFallingLeavesFX.kt +++ b/src/main/kotlin/mods/betterfoliage/render/EntityFallingLeavesFX.kt @@ -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) diff --git a/src/main/kotlin/mods/betterfoliage/render/EntityRisingSoulFX.kt b/src/main/kotlin/mods/betterfoliage/render/EntityRisingSoulFX.kt index b26727f..fbbb563 100644 --- a/src/main/kotlin/mods/betterfoliage/render/EntityRisingSoulFX.kt +++ b/src/main/kotlin/mods/betterfoliage/render/EntityRisingSoulFX.kt @@ -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")) -} \ No newline at end of file +//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")) +//} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/ModelColumn.kt b/src/main/kotlin/mods/betterfoliage/render/ModelColumn.kt deleted file mode 100644 index 1bc13a4..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/ModelColumn.kt +++ /dev/null @@ -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) - } -} diff --git a/src/main/kotlin/mods/betterfoliage/render/SpecialRenderModels.kt b/src/main/kotlin/mods/betterfoliage/render/SpecialRenderModels.kt new file mode 100644 index 0000000..480b099 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/SpecialRenderModels.kt @@ -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): 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, 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) +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/Utils.kt b/src/main/kotlin/mods/betterfoliage/render/Utils.kt deleted file mode 100644 index 24f66fe..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/Utils.kt +++ /dev/null @@ -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()) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt new file mode 100644 index 0000000..e03239e --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Dirt.kt @@ -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, + replacements: MutableMap + ): 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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt new file mode 100644 index 0000000..c6b7fd1 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Grass.kt @@ -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 get() = BlockConfig.grassModels.modelList + + override fun processModel( + state: BlockState, + location: ResourceLocation, + textureMatch: List, + sprites: MutableSet, + replacements: MutableMap + ): 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) } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt new file mode 100644 index 0000000..eb28e5f --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/Leaf.kt @@ -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 get() = BlockConfig.leafModels.modelList + + override fun processModel( + state: BlockState, + location: ResourceLocation, + textureMatch: List, + sprites: MutableSet, + replacements: MutableMap + ): 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 } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt new file mode 100644 index 0000000..1a48abf --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/block/vanilla/RoundLog.kt @@ -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 { +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderAlgae.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderAlgae.kt deleted file mode 100644 index 5c9f88a..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderAlgae.kt +++ /dev/null @@ -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]] } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCactus.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCactus.kt deleted file mode 100644 index b5ac636..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCactus.kt +++ /dev/null @@ -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() { - 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, atlas: AtlasFuture): CompletableFuture? { - 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)] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderConnectedGrass.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderConnectedGrass.kt deleted file mode 100644 index 9bee8df..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderConnectedGrass.kt +++ /dev/null @@ -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() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCoral.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCoral.kt deleted file mode 100644 index fab7866..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderCoral.kt +++ /dev/null @@ -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)] } - ) - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderGrass.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderGrass.kt deleted file mode 100644 index 858890b..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderGrass.kt +++ /dev/null @@ -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) } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLeaves.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLeaves.kt deleted file mode 100644 index 863d310..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLeaves.kt +++ /dev/null @@ -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 - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLilypad.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLilypad.kt deleted file mode 100644 index 6b301fe..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLilypad.kt +++ /dev/null @@ -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]] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLog.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLog.kt deleted file mode 100644 index a235d9e..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderLog.kt +++ /dev/null @@ -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 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() - -object AsyncLogDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks - override val modelTextures: List get() = BlockConfig.logModels.modelList - - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture { - 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) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderMycelium.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderMycelium.kt deleted file mode 100644 index 712c220..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderMycelium.kt +++ /dev/null @@ -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 - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderNetherrack.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderNetherrack.kt deleted file mode 100644 index 1e4d676..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderNetherrack.kt +++ /dev/null @@ -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]] } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderReeds.kt b/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderReeds.kt deleted file mode 100644 index 2107cb9..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/block/vanillaold/RenderReeds.kt +++ /dev/null @@ -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] } - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/column/AbstractRenderer.kt b/src/main/kotlin/mods/betterfoliage/render/column/AbstractRenderer.kt deleted file mode 100644 index 664943e..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/column/AbstractRenderer.kt +++ /dev/null @@ -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) - } - } - ) - } - } - } -} diff --git a/src/main/kotlin/mods/betterfoliage/render/column/OverlayLayer.kt b/src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt similarity index 81% rename from src/main/kotlin/mods/betterfoliage/render/column/OverlayLayer.kt rename to src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt index bce1321..3831b1a 100644 --- a/src/main/kotlin/mods/betterfoliage/render/column/OverlayLayer.kt +++ b/src/main/kotlin/mods/betterfoliage/render/column/ColumnOverlayLayer.kt @@ -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, @@ -47,7 +55,12 @@ sealed class ColumnLayerData { val quadrantsBottom: Array ) : 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 { - abstract val registry: ModelRenderRegistry - 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 { } 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 { */ 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 } diff --git a/src/main/kotlin/mods/betterfoliage/render/column/RenderData.kt b/src/main/kotlin/mods/betterfoliage/render/column/RenderData.kt deleted file mode 100644 index be71a6d..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/column/RenderData.kt +++ /dev/null @@ -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 -) : 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] - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/AoSideHelper.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/AoSideHelper.kt new file mode 100644 index 0000000..b08b732 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/AoSideHelper.kt @@ -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 +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.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.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 + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/ForgeVertexLighter.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/ForgeVertexLighter.kt new file mode 100644 index 0000000..275b55c --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/ForgeVertexLighter.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/Lighting.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/Lighting.kt deleted file mode 100644 index 1e202b6..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/lighting/Lighting.kt +++ /dev/null @@ -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? = 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) - } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/LightingContext.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/LightingContext.kt deleted file mode 100644 index 097b3d1..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/lighting/LightingContext.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/ModelLighters.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/ModelLighters.kt deleted file mode 100644 index f102db4..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/lighting/ModelLighters.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/PixelFormat.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/PixelFormat.kt deleted file mode 100644 index ba8b3c7..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/lighting/PixelFormat.kt +++ /dev/null @@ -1,3 +0,0 @@ -@file:JvmName("PixelFormat") -package mods.betterfoliage.render.lighting - diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaAoCalculation.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaAoCalculation.kt new file mode 100644 index 0000000..d2eb5e7 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaAoCalculation.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaVertexLighter.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaVertexLighter.kt new file mode 100644 index 0000000..8b92c85 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/lighting/VanillaVertexLighter.kt @@ -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? { + 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 +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/lighting/Vertex.kt b/src/main/kotlin/mods/betterfoliage/render/lighting/Vertex.kt deleted file mode 100644 index 92c19c1..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/lighting/Vertex.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/old/CombinedContext.kt b/src/main/kotlin/mods/betterfoliage/render/old/CombinedContext.kt deleted file mode 100644 index 8156f68..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/old/CombinedContext.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/old/HalfBaked.kt b/src/main/kotlin/mods/betterfoliage/render/old/HalfBaked.kt new file mode 100644 index 0000000..5fad169 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/old/HalfBaked.kt @@ -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 + ): 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.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() } +} + diff --git a/src/main/kotlin/mods/betterfoliage/render/old/Model.kt b/src/main/kotlin/mods/betterfoliage/render/old/Model.kt index 8cda341..6700f14 100644 --- a/src/main/kotlin/mods/betterfoliage/render/old/Model.kt +++ b/src/main/kotlin/mods/betterfoliage/render/old/Model.kt @@ -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) = 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) : this() { quads.addAll(other) } - val quads = mutableListOf() + 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.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() - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/old/RenderDecorator.kt b/src/main/kotlin/mods/betterfoliage/render/old/RenderDecorator.kt deleted file mode 100644 index 64bfe1c..0000000 --- a/src/main/kotlin/mods/betterfoliage/render/old/RenderDecorator.kt +++ /dev/null @@ -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) - - diff --git a/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxBase.kt b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxBase.kt new file mode 100644 index 0000000..91b897a --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxBase.kt @@ -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) { + quads.forEach { quad -> + if (quad.raw.face.shouldRender()) { + renderQuad(quad) + hasRendered = true + } + } + } + + abstract fun renderMasquerade(offset: Int3, func: ()->Unit) +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxForge.kt b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxForge.kt new file mode 100644 index 0000000..b5c3a2c --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxForge.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxVanilla.kt b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxVanilla.kt new file mode 100644 index 0000000..2c49b85 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/render/pipeline/RenderCtxVanilla.kt @@ -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() } + } +} diff --git a/src/main/kotlin/mods/betterfoliage/resource/Aliases.kt b/src/main/kotlin/mods/betterfoliage/resource/Aliases.kt deleted file mode 100644 index 2926f27..0000000 --- a/src/main/kotlin/mods/betterfoliage/resource/Aliases.kt +++ /dev/null @@ -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 diff --git a/src/main/kotlin/mods/betterfoliage/resource/ResourceHandler.kt b/src/main/kotlin/mods/betterfoliage/resource/ResourceHandler.kt deleted file mode 100644 index 24de5db..0000000 --- a/src/main/kotlin/mods/betterfoliage/resource/ResourceHandler.kt +++ /dev/null @@ -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() - // ============================ - // 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, AsyncSpriteProvider { - protected lateinit var value: Sprite - override fun getValue(thisRef: Any, property: KProperty<*>) = value - - override fun setup(manager: IResourceManager, sourceF: CompletableFuture, 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 { - var num = 0 - protected set - protected var sprites: List = emptyList() - - override fun setup(manager: IResourceManager, sourceF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - var list: List> = 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) -} diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/AsyncSpriteProviderManager.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/AsyncSpriteProviderManager.kt deleted file mode 100644 index d3df2ff..0000000 --- a/src/main/kotlin/mods/betterfoliage/resource/discovery/AsyncSpriteProviderManager.kt +++ /dev/null @@ -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(val profilerSection: String) { - - val providers = mutableListOf>() - - /** - * 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, val onComplete: (AtlasTexture.SheetData)->Unit) { - fun complete(sheet: AtlasTexture.SheetData) = onComplete(sheet) - } - - var currentAtlas: AtlasFuture? = null - var currentPhases: List = emptyList() - - @Suppress("UNCHECKED_CAST") - fun prepare(sourceObj: Any, manager: IResourceManager, idList: Iterable, profiler: IProfiler): Set { - profiler.startSection(profilerSection) - - val source = CompletableFuture() - 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) { - val idSet = Collections.synchronizedSet(mutableSetOf().apply { addAll(initial) }) - protected val sheet = CompletableFuture() - protected val finished = CompletableFuture() - - fun complete(sheetData: AtlasTexture.SheetData) { - sheet.complete(sheetData) - finished.complete(null) - } - - fun sprite(id: String) = sprite(Identifier(id)) - fun sprite(id: Identifier): CompletableFuture { - 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 mapAfter(supplier: ()->T): CompletableFuture = finished.map{ supplier() } - fun runAfter(action: ()->Unit): CompletableFuture = finished.thenRun(action) -} - -class StitchPhases( - val discovery: CompletableFuture, - val cleanup: CompletableFuture -) - -interface AsyncSpriteProvider { - fun setup(manager: IResourceManager, source: CompletableFuture, atlas: AtlasFuture): StitchPhases -} diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/BakingLifecycle.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/BakingLifecycle.kt new file mode 100644 index 0000000..6bbfda3 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/BakingLifecycle.kt @@ -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, + replacements: MutableMap + ) +} + +@FunctionalInterface +interface ModelBakeKey { + fun replace( + location: ResourceLocation, + unbaked: IUnbakedModel, + transform: IModelTransform, + bakery: ModelBakery, + spriteGetter: Function + ): IBakedModel? = unbaked.bakeModel(bakery, spriteGetter, transform, location) +} + +interface ModelWrapperKey : ModelBakeKey { + override fun replace( + location: ResourceLocation, + unbaked: IUnbakedModel, + transform: IModelTransform, + bakery: ModelBakery, + spriteGetter: Function + ): 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() + override val callbacks = mutableListOfUnit>>() + + val modelsValid = SimpleInvalidator() + val spritesValid = SimpleInvalidator() + + private val replacements = mutableMapOf() + private val sprites = mutableSetOf() + + @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() + 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, + 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) + } +} diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/BlockTypeCache.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/BlockTypeCache.kt new file mode 100644 index 0000000..0bfa5eb --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/BlockTypeCache.kt @@ -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() + val grass = mutableSetOf() + val dirt = mutableSetOf() + + companion object : ModelDiscovery { + override fun onModelsLoaded(bakery: ModelBakery, sprites: MutableSet, replacements: MutableMap + ) { + Client.blockTypes = BlockTypeCache() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscovery.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscovery.kt deleted file mode 100644 index 465280b..0000000 --- a/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelDiscovery.kt +++ /dev/null @@ -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 { - 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 : ModelRenderRegistry { - val registries = mutableListOf>() - 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): List> = 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 : HasLogger, AsyncSpriteProvider, ModelRenderRegistry { - - var modelData: Map = emptyMap() - protected set - - override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state] - - abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? - - override fun setup(manager: IResourceManager, bakeryF: CompletableFuture, atlas: AtlasFuture): StitchPhases { - val modelDataTemp = mutableMapOf>() - - 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 : ModelDiscovery() { - - abstract val matchClasses: IBlockMatcher - abstract val modelTextures: List - - abstract fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture? - - override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { - 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.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 -} diff --git a/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelReplacer.kt b/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelReplacer.kt new file mode 100644 index 0000000..3f40c05 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/discovery/ModelReplacer.kt @@ -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, + replacements: MutableMap + ) { + 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, + replacements: MutableMap + ): 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 + + abstract fun processModel( + state: BlockState, + location: ResourceLocation, + textureMatch: List, + sprites: MutableSet, + replacements: MutableMap + ): Boolean + + override fun processModel( + bakery: ModelBakery, + state: BlockState, + location: ResourceLocation, + sprites: MutableSet, + replacements: MutableMap + ): 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 diff --git a/src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt index f905b93..ea1e619 100644 --- a/src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/CenteringTextureGenerator.kt @@ -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 diff --git a/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt index 7014a2f..2cdb281 100644 --- a/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedGrass.kt @@ -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) } } diff --git a/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeaf.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeaf.kt index b6fcc8e..39f0bd9 100644 --- a/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeaf.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/GeneratedLeaf.kt @@ -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 diff --git a/src/main/kotlin/mods/betterfoliage/resource/generated/ResourceGeneration.kt b/src/main/kotlin/mods/betterfoliage/resource/generated/ResourceGeneration.kt index a029664..6df2a13 100644 --- a/src/main/kotlin/mods/betterfoliage/resource/generated/ResourceGeneration.kt +++ b/src/main/kotlin/mods/betterfoliage/resource/generated/ResourceGeneration.kt @@ -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 { +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 getMetadata(deserializer: IMetadataSectionSerializer) = null override fun getRootResourceStream(id: String) = null - override fun getAllResourceLocations(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate) = emptyList() + override fun getAllResourceLocations(type: ResourcePackType, namespace:String, path: String, maxDepth: Int, filter: Predicate) = emptyList() override fun close() {} - protected var manager: CompletableFuture? = null - val identifiers = Collections.synchronizedMap(mutableMapOf()) - val resources = Collections.synchronizedMap(mutableMapOf>()) + protected var manager: IResourceManager = Minecraft.getInstance().resourceManager + val identifiers = Collections.synchronizedMap(mutableMapOf()) + val resources = Collections.synchronizedMap(mutableMapOf()) - 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, 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).put(packName, packInfo) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt b/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt new file mode 100644 index 0000000..22e01d5 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/SpriteSets.kt @@ -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) : SpriteSet { + override val num = sprites.size + override fun get(idx: Int) = sprites[idx % num] +} + +class SpriteDelegate(val atlas: Atlas, val idFunc: () -> ResourceLocation) : ReadOnlyProperty { + 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 { + private var idList: List = 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!! + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt b/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt new file mode 100644 index 0000000..689996d --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/resource/model/TuftMeshes.kt @@ -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 { + 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, 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 { + 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 { + val overrideColor = Atlas.BLOCKS[spriteLocation].getColorOverride(threshold) + return fullCubeTextured(spriteLocation, overrideColor, scrambleUV) +} + +fun crossModelsRaw(num: Int, size: Double, hOffset: Double, vOffset: Double): Array> { + 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, 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>, + overrideColor: Color?, + scrambleUV: Boolean, + spriteGetter: (Int) -> ResourceLocation +) = leafBase.mapIndexed { idx, leaf -> + crossModelSingle(leaf, Atlas.BLOCKS[spriteGetter(idx)], overrideColor, scrambleUV) +}.toTypedArray() + +fun crossModelsTinted( + leafBase: Array>, + 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.withOpposites() = flatMap { listOf(it, it.flipped) } +fun List>.buildTufts(applyDiffuseLighting: Boolean = false) = + map { it.withOpposites().bake(applyDiffuseLighting) }.toTypedArray() \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/texture/GrassRegistry.kt b/src/main/kotlin/mods/betterfoliage/texture/GrassRegistry.kt deleted file mode 100644 index 6ce9c22..0000000 --- a/src/main/kotlin/mods/betterfoliage/texture/GrassRegistry.kt +++ /dev/null @@ -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() - -object AsyncGrassDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks - override val modelTextures: List get() = BlockConfig.grassModels.modelList - - override fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture { - 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) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/texture/LeafParticleRegistry.kt b/src/main/kotlin/mods/betterfoliage/texture/LeafParticleRegistry.kt index e6446d0..3521fb7 100644 --- a/src/main/kotlin/mods/betterfoliage/texture/LeafParticleRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/texture/LeafParticleRegistry.kt @@ -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) : SpriteSet { - override val num = sprites.size - override fun get(idx: Int) = sprites[idx % num] -} - -object LeafParticleRegistry : AsyncSpriteProvider { +object LeafParticleRegistry { val targetAtlas = Atlas.PARTICLES val typeMappings = TextureMatcher() - val particles = hashMapOf() +// val particles = hashMapOf() - operator fun get(type: String) = particles[type] ?: particles["default"]!! + val futures = mutableMapOf>>() - override fun setup(manager: IResourceManager, particleF: CompletableFuture, atlasFuture: AtlasFuture): StitchPhases { - particles.clear() - val futures = mutableMapOf>>() +// 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 = 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 -> diff --git a/src/main/kotlin/mods/betterfoliage/texture/LeafRegistry.kt b/src/main/kotlin/mods/betterfoliage/texture/LeafRegistry.kt deleted file mode 100644 index 4d9faed..0000000 --- a/src/main/kotlin/mods/betterfoliage/texture/LeafRegistry.kt +++ /dev/null @@ -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() - -object AsyncLeafDiscovery : ConfigurableModelDiscovery() { - override val logger = BetterFoliage.logDetail - override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks - override val modelTextures: List get() = BlockConfig.leafModels.modelList - - override fun processModel(state: BlockState, textures: List, 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 { - 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) - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/texture/Utils.kt b/src/main/kotlin/mods/betterfoliage/texture/Utils.kt index d2154c4..d26568b 100644 --- a/src/main/kotlin/mods/betterfoliage/texture/Utils.kt +++ b/src/main/kotlin/mods/betterfoliage/texture/Utils.kt @@ -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") \ No newline at end of file +fun IResourceManager.loadSprite(id: ResourceLocation) = this.get(id)?.loadImage() ?: throw IOException("Cannot load resource $id") \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/util/Blocks.kt b/src/main/kotlin/mods/betterfoliage/util/Blocks.kt new file mode 100644 index 0000000..db1136d --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/util/Blocks.kt @@ -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) \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/util/Caching.kt b/src/main/kotlin/mods/betterfoliage/util/Caching.kt index 3180bef..4ba038c 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Caching.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Caching.kt @@ -17,6 +17,10 @@ interface Invalidator { } } +class SimpleInvalidator : Invalidator { + override val callbacks = mutableListOf Unit>>() +} + class LazyInvalidatable(invalidator: Invalidator, val valueFactory: ()->V): ReadOnlyProperty { init { invalidator.onInvalidate { value = null } } @@ -31,7 +35,7 @@ class LazyInvalidatable(invalidator: Invalidator, val valueFactory: ()->V): R } } -class LazyMap(val invalidator: Invalidator, val valueFactory: (K)->V) { +open class LazyMapInvalidatable(val invalidator: Invalidator, val valueFactory: (K)->V) { init { invalidator.onInvalidate { values.clear() } } val values = mutableMapOf() diff --git a/src/main/kotlin/mods/betterfoliage/util/Collections.kt b/src/main/kotlin/mods/betterfoliage/util/Collections.kt index 3ced728..2985ce6 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Collections.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Collections.kt @@ -1,5 +1,6 @@ package mods.betterfoliage.util +import com.google.common.collect.ImmutableList import java.util.* /** @@ -27,6 +28,8 @@ inline fun > Triple.maxValueBy(func: (T)->C): C { return result } +inline fun Array.mapArray(func: (T)->R) = Array(size) { idx -> func(get(idx)) } + @Suppress("UNCHECKED_CAST") inline fun Map.filterValuesNotNull() = filterValues { it != null } as Map @@ -61,3 +64,8 @@ inline fun MutableList.exchange(idx1: Int, idx2: Int) { /** Return a random element from the array using the provided random generator */ inline operator fun Array.get(random: Random) = get(random.nextInt(Int.MAX_VALUE) % size) + +fun Iterable.toImmutableList() = ImmutableList.builder().let { builder -> + forEach { builder.add(it) } + builder.build() +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/util/Geometry.kt b/src/main/kotlin/mods/betterfoliage/util/Geometry.kt index bce4ef6..67f9738 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Geometry.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Geometry.kt @@ -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.face: Direction get() = when(this) { Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN; Z to POSITIVE -> SOUTH; else -> NORTH; } -val Direction.perpendiculars: List 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 get() = + axes.filter { it != this.axis }.flatMap { listOf((it to POSITIVE).face, (it to NEGATIVE).face) }.toTypedArray() + +val perpendiculars: Array> = 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, val reverse: Array) { // 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] + ) } } diff --git a/src/main/kotlin/mods/betterfoliage/util/Misc.kt b/src/main/kotlin/mods/betterfoliage/util/Misc.kt index af90e2b..ddf6779 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Misc.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Misc.kt @@ -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 } diff --git a/src/main/kotlin/mods/betterfoliage/util/Resources.kt b/src/main/kotlin/mods/betterfoliage/util/Resources.kt index d184c44..6656cb3 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Resources.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Resources.kt @@ -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. */ diff --git a/src/main/kotlin/mods/betterfoliage/util/Sprites.kt b/src/main/kotlin/mods/betterfoliage/util/Sprites.kt index 8bb86da..c35cae9 100644 --- a/src/main/kotlin/mods/betterfoliage/util/Sprites.kt +++ b/src/main/kotlin/mods/betterfoliage/util/Sprites.kt @@ -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 { diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 366fe2b..24046b0 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,10 +1,14 @@ -public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace -public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace -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 +#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 \ No newline at end of file diff --git a/src/main/resources/betterfoliage.common.mixins.json b/src/main/resources/betterfoliage.common.mixins.json index 221df73..e1ee813 100644 --- a/src/main/resources/betterfoliage.common.mixins.json +++ b/src/main/resources/betterfoliage.common.mixins.json @@ -9,10 +9,11 @@ "client": [ "MixinBlock", "MixinBlockState", - "MixinChunkRender", + "MixinBlockModelRenderer", "MixinClientWorld", "MixinModelBakery", - "MixinParticleManager" + "MixinForgeBlockModelRenderer", + "MixinForgeCustomVertexLighting" ], "server": [ ], diff --git a/src/main/resources/betterfoliage.optifine.mixins.json b/src/main/resources/betterfoliage.optifine.mixins.json index 28a4e18..6972cc3 100644 --- a/src/main/resources/betterfoliage.optifine.mixins.json +++ b/src/main/resources/betterfoliage.optifine.mixins.json @@ -7,9 +7,6 @@ "mixins": [ ], "client": [ - "MixinOptifineChunkRender", - "MixinShadersBlockModelRenderer", - "MixinOptifineBlockUtils" ], "server": [ ], diff --git a/src/main/resources/betterfoliage.vanilla.mixins.json b/src/main/resources/betterfoliage.vanilla.mixins.json index 4608c01..6972cc3 100644 --- a/src/main/resources/betterfoliage.vanilla.mixins.json +++ b/src/main/resources/betterfoliage.vanilla.mixins.json @@ -7,7 +7,6 @@ "mixins": [ ], "client": [ - "MixinChunkRenderVanilla" ], "server": [ ],