diff --git a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java index ae72a2fb3..35d04b9b7 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/material/Material.java @@ -1,5 +1,7 @@ package dev.engine_room.flywheel.api.material; +import org.jetbrains.annotations.Nullable; + import net.minecraft.resources.ResourceLocation; public interface Material { @@ -52,4 +54,49 @@ public interface Material { * @return The cardinal lighting mode. */ CardinalLightingMode cardinalLightingMode(); + + /** + * Whether this material should receive ambient occlusion from nearby chunk geometry. + * + * @return {@code true} if this material should receive ambient occlusion. + */ + default boolean ambientOcclusion() { + return true; + } + + /** + * Check for field-wise equality between this Material and another. + * + * @param other The nullable material to check equality against. + * @return True if the materials represent the same configuration. + */ + default boolean equals(@Nullable Material other) { + if (this == other) { + return true; + } + + if (other == null) { + return false; + } + + // @formatter:off + return this.blur() == other.blur() + && this.mipmap() == other.mipmap() + && this.backfaceCulling() == other.backfaceCulling() + && this.polygonOffset() == other.polygonOffset() + && this.depthTest() == other.depthTest() + && this.transparency() == other.transparency() + && this.writeMask() == other.writeMask() + && this.useOverlay() == other.useOverlay() + && this.useLight() == other.useLight() + && this.cardinalLightingMode() == other.cardinalLightingMode() + && this.ambientOcclusion() == other.ambientOcclusion() + && this.shaders().fragmentSource().equals(other.shaders().fragmentSource()) + && this.shaders().vertexSource().equals(other.shaders().vertexSource()) + && this.fog().source().equals(other.fog().source()) + && this.cutout().source().equals(other.cutout().source()) + && this.light().source().equals(other.light().source()) + && this.texture().equals(other.texture()); + // @formatter:on + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java index 52dab8ad2..28ee88019 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialEncoder.java @@ -21,6 +21,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_LENGTH = 1; private static final int USE_LIGHT_LENGTH = 1; private static final int CARDINAL_LIGHTING_MODE_LENGTH = Mth.ceillog2(CardinalLightingMode.values().length); + private static final int AMBIENT_OCCLUSION_LENGTH = 1; // The bit offset of each property private static final int BLUR_OFFSET = 0; @@ -33,6 +34,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_OFFSET = WRITE_MASK_OFFSET + WRITE_MASK_LENGTH; private static final int USE_LIGHT_OFFSET = USE_OVERLAY_OFFSET + USE_OVERLAY_LENGTH; private static final int CARDINAL_LIGHTING_MODE_OFFSET = USE_LIGHT_OFFSET + USE_LIGHT_LENGTH; + private static final int AMBIENT_OCCLUSION_OFFSET = CARDINAL_LIGHTING_MODE_OFFSET + CARDINAL_LIGHTING_MODE_LENGTH; // The bit mask for each property private static final int BLUR_MASK = bitMask(BLUR_LENGTH, BLUR_OFFSET); @@ -45,6 +47,7 @@ public final class MaterialEncoder { private static final int USE_OVERLAY_MASK = bitMask(USE_OVERLAY_LENGTH, USE_OVERLAY_OFFSET); private static final int USE_LIGHT_MASK = bitMask(USE_LIGHT_LENGTH, USE_LIGHT_OFFSET); private static final int CARDINAL_LIGHTING_MODE_MASK = bitMask(CARDINAL_LIGHTING_MODE_LENGTH, CARDINAL_LIGHTING_MODE_OFFSET); + private static final int AMBIENT_OCCLUSION_MASK = bitMask(AMBIENT_OCCLUSION_LENGTH, AMBIENT_OCCLUSION_OFFSET); private MaterialEncoder() { } @@ -60,7 +63,7 @@ public static int packUberShader(Material material) { } // Packed format: - // cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] + // ambientOcclusion[1] | cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] public static int packProperties(Material material) { int bits = 0; @@ -75,6 +78,7 @@ public static int packProperties(Material material) { if (material.useLight()) bits |= USE_LIGHT_MASK; bits |= (material.cardinalLightingMode() .ordinal() << CARDINAL_LIGHTING_MODE_OFFSET) & CARDINAL_LIGHTING_MODE_MASK; + if (material.ambientOcclusion()) bits |= AMBIENT_OCCLUSION_MASK; return bits; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java index 74ddc681d..5d4eec2b2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/MaterialRenderState.java @@ -188,7 +188,7 @@ public static boolean materialEquals(Material lhs, Material rhs) { return true; } - // Not here because ubershader: useLight, useOverlay, diffuse, fog shader + // Not here because ubershader: useLight, useOverlay, diffuse, fog shader, ambient occlusion // Everything in the comparator should be here. // @formatter:off return lhs.blur() == rhs.blur() diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl index 990129b54..041d87b1e 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/material.glsl @@ -34,4 +34,5 @@ struct FlwMaterial { bool useOverlay; bool useLight; uint cardinalLightingMode; + bool ambientOcclusion; }; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl index 55c53a135..1493031b1 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/packed_material.glsl @@ -9,6 +9,7 @@ const uint _FLW_WRITE_MASK_LENGTH = 2u; const uint _FLW_USE_OVERLAY_LENGTH = 1u; const uint _FLW_USE_LIGHT_LENGTH = 1u; const uint _FLW_CARDINAL_LIGHTING_MODE_LENGTH = 2u; +const uint _FLW_AMBIENT_OCCLUSION_LENGTH = 1u; // The bit offset of each property const uint _FLW_BLUR_OFFSET = 0u; @@ -21,6 +22,7 @@ const uint _FLW_WRITE_MASK_OFFSET = _FLW_TRANSPARENCY_OFFSET + _FLW_TRANSPARENCY const uint _FLW_USE_OVERLAY_OFFSET = _FLW_WRITE_MASK_OFFSET + _FLW_WRITE_MASK_LENGTH; const uint _FLW_USE_LIGHT_OFFSET = _FLW_USE_OVERLAY_OFFSET + _FLW_USE_OVERLAY_LENGTH; const uint _FLW_CARDINAL_LIGHTING_MODE_OFFSET = _FLW_USE_LIGHT_OFFSET + _FLW_USE_LIGHT_LENGTH; +const uint _FLW_AMBIENT_OCCLUSION_OFFSET = _FLW_CARDINAL_LIGHTING_MODE_OFFSET + _FLW_CARDINAL_LIGHTING_MODE_LENGTH; // The bit mask for each property const uint _FLW_BLUR_MASK = ((1u << _FLW_BLUR_LENGTH) - 1u) << _FLW_BLUR_OFFSET; @@ -33,9 +35,10 @@ const uint _FLW_WRITE_MASK_MASK = ((1u << _FLW_WRITE_MASK_LENGTH) - 1u) << _FLW_ const uint _FLW_USE_OVERLAY_MASK = ((1u << _FLW_USE_OVERLAY_LENGTH) - 1u) << _FLW_USE_OVERLAY_OFFSET; const uint _FLW_USE_LIGHT_MASK = ((1u << _FLW_USE_LIGHT_LENGTH) - 1u) << _FLW_USE_LIGHT_OFFSET; const uint _FLW_CARDINAL_LIGHTING_MODE_MASK = ((1u << _FLW_CARDINAL_LIGHTING_MODE_LENGTH) - 1u) << _FLW_CARDINAL_LIGHTING_MODE_OFFSET; +const uint _FLW_AMBIENT_OCCLUSION_MASK = ((1u << _FLW_AMBIENT_OCCLUSION_LENGTH) - 1u) << _FLW_AMBIENT_OCCLUSION_OFFSET; // Packed format: -// cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] +// ambientOcclusion[1] | cardinalLightingMode[2] | useLight[1] | useOverlay[1] | writeMask[2] | transparency[3] | depthTest[4] | polygonOffset[1] | backfaceCulling[1] | mipmap[1] | blur[1] void _flw_unpackMaterialProperties(uint p, out FlwMaterial m) { m.blur = (p & _FLW_BLUR_MASK) != 0u; m.mipmap = (p & _FLW_MIPMAP_MASK) != 0u; @@ -47,6 +50,7 @@ void _flw_unpackMaterialProperties(uint p, out FlwMaterial m) { m.useOverlay = (p & _FLW_USE_OVERLAY_MASK) != 0u; m.useLight = (p & _FLW_USE_LIGHT_MASK) != 0u; m.cardinalLightingMode = (p & _FLW_CARDINAL_LIGHTING_MODE_MASK) >> _FLW_CARDINAL_LIGHTING_MODE_OFFSET; + m.ambientOcclusion = (p & _FLW_AMBIENT_OCCLUSION_MASK) != 0; } void _flw_unpackUint2x16(uint s, out uint hi, out uint lo) { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java index 4f56e36db..932191dd4 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java @@ -10,15 +10,14 @@ public final class Materials { public static final Material SOLID_BLOCK = SimpleMaterial.builder() .build(); - public static final Material SOLID_UNSHADED_BLOCK = SimpleMaterial.builder() + public static final Material SOLID_UNSHADED_BLOCK = SimpleMaterial.builderOf(SOLID_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); public static final Material CUTOUT_MIPPED_BLOCK = SimpleMaterial.builder() .cutout(CutoutShaders.HALF) .build(); - public static final Material CUTOUT_MIPPED_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.HALF) + public static final Material CUTOUT_MIPPED_UNSHADED_BLOCK = SimpleMaterial.builderOf(CUTOUT_MIPPED_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); @@ -26,17 +25,14 @@ public final class Materials { .cutout(CutoutShaders.ONE_TENTH) .mipmap(false) .build(); - public static final Material CUTOUT_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.ONE_TENTH) - .mipmap(false) + public static final Material CUTOUT_UNSHADED_BLOCK = SimpleMaterial.builderOf(CUTOUT_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); public static final Material TRANSLUCENT_BLOCK = SimpleMaterial.builder() .transparency(Transparency.ORDER_INDEPENDENT) .build(); - public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builder() - .transparency(Transparency.ORDER_INDEPENDENT) + public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builderOf(TRANSLUCENT_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); @@ -44,9 +40,7 @@ public final class Materials { .cutout(CutoutShaders.ONE_TENTH) .transparency(Transparency.ORDER_INDEPENDENT) .build(); - public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builder() - .cutout(CutoutShaders.ONE_TENTH) - .transparency(Transparency.ORDER_INDEPENDENT) + public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builderOf(TRIPWIRE_BLOCK) .cardinalLightingMode(CardinalLightingMode.OFF) .build(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java index 70e137d74..46aab259b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java @@ -32,6 +32,8 @@ public class SimpleMaterial implements Material { protected final boolean useLight; protected final CardinalLightingMode cardinalLightingMode; + protected final boolean ambientOcclusion; + protected SimpleMaterial(Builder builder) { shaders = builder.shaders(); fog = builder.fog(); @@ -48,6 +50,7 @@ protected SimpleMaterial(Builder builder) { useOverlay = builder.useOverlay(); useLight = builder.useLight(); cardinalLightingMode = builder.cardinalLightingMode(); + ambientOcclusion = builder.ambientOcclusion(); } public static Builder builder() { @@ -133,6 +136,11 @@ public CardinalLightingMode cardinalLightingMode() { return cardinalLightingMode; } + @Override + public boolean ambientOcclusion() { + return ambientOcclusion; + } + public static class Builder implements Material { protected MaterialShaders shaders; protected FogShader fog; @@ -153,6 +161,8 @@ public static class Builder implements Material { protected boolean useLight; protected CardinalLightingMode cardinalLightingMode; + protected boolean ambientOcclusion; + public Builder() { shaders = StandardMaterialShaders.DEFAULT; fog = FogShaders.LINEAR; @@ -169,6 +179,7 @@ public Builder() { useOverlay = true; useLight = true; cardinalLightingMode = CardinalLightingMode.ENTITY; + ambientOcclusion = true; } public Builder(Material material) { @@ -191,6 +202,7 @@ public Builder copyFrom(Material material) { useOverlay = material.useOverlay(); useLight = material.useLight(); cardinalLightingMode = material.cardinalLightingMode(); + ambientOcclusion = material.ambientOcclusion(); return this; } @@ -277,6 +289,11 @@ public Builder cardinalLightingMode(CardinalLightingMode value) { return this; } + public Builder ambientOcclusion(boolean ambientOcclusion) { + this.ambientOcclusion = ambientOcclusion; + return this; + } + @Override public MaterialShaders shaders() { return shaders; @@ -352,6 +369,11 @@ public CardinalLightingMode cardinalLightingMode() { return cardinalLightingMode; } + @Override + public boolean ambientOcclusion() { + return ambientOcclusion; + } + public SimpleMaterial build() { return new SimpleMaterial(this); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java index 48acb921f..874095110 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java @@ -6,11 +6,13 @@ import org.joml.Vector3f; import org.joml.Vector4f; +import dev.engine_room.flywheel.api.material.CardinalLightingMode; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.vertex.VertexList; import dev.engine_room.flywheel.lib.material.Materials; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.memory.MemoryBlock; import dev.engine_room.flywheel.lib.vertex.PosVertexView; import net.minecraft.client.renderer.RenderType; @@ -19,32 +21,62 @@ public final class ModelUtil { private static final float BOUNDING_SPHERE_EPSILON = 1e-4f; + private static final RenderType[] CHUNK_LAYERS = new RenderType[]{RenderType.solid(), RenderType.cutoutMipped(), RenderType.cutout(), RenderType.translucent(), RenderType.tripwire()}; + + // Array of chunk materials to make lookups easier. + // Index by (renderTypeIdx * 4 + shaded * 2 + ambientOcclusion). + private static final Material[] CHUNK_MATERIALS = new Material[20]; + + static { + Material[] baseChunkMaterials = new Material[]{Materials.SOLID_BLOCK, Materials.CUTOUT_MIPPED_BLOCK, Materials.CUTOUT_BLOCK, Materials.TRANSLUCENT_BLOCK, Materials.TRIPWIRE_BLOCK,}; + for (int chunkLayerIdx = 0; chunkLayerIdx < CHUNK_LAYERS.length; chunkLayerIdx++) { + int baseMaterialIdx = chunkLayerIdx * 4; + Material baseChunkMaterial = baseChunkMaterials[chunkLayerIdx]; + + // shaded: false, ambientOcclusion: false + CHUNK_MATERIALS[baseMaterialIdx] = SimpleMaterial.builderOf(baseChunkMaterial) + .cardinalLightingMode(CardinalLightingMode.OFF) + .ambientOcclusion(false) + .build(); + // shaded: false, ambientOcclusion: true + CHUNK_MATERIALS[baseMaterialIdx + 1] = SimpleMaterial.builderOf(baseChunkMaterial) + .cardinalLightingMode(CardinalLightingMode.OFF) + .build(); + // shaded: true, ambientOcclusion: false + CHUNK_MATERIALS[baseMaterialIdx + 2] = SimpleMaterial.builderOf(baseChunkMaterial) + .ambientOcclusion(false) + .build(); + // shaded: true, ambientOcclusion: true + CHUNK_MATERIALS[baseMaterialIdx + 3] = baseChunkMaterial; + } + } + private ModelUtil() { } @Nullable public static Material getMaterial(RenderType chunkRenderType, boolean shaded) { - if (chunkRenderType == RenderType.solid()) { - return shaded ? Materials.SOLID_BLOCK : Materials.SOLID_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.cutoutMipped()) { - return shaded ? Materials.CUTOUT_MIPPED_BLOCK : Materials.CUTOUT_MIPPED_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.cutout()) { - return shaded ? Materials.CUTOUT_BLOCK : Materials.CUTOUT_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.translucent()) { - return shaded ? Materials.TRANSLUCENT_BLOCK : Materials.TRANSLUCENT_UNSHADED_BLOCK; - } - if (chunkRenderType == RenderType.tripwire()) { - return shaded ? Materials.TRIPWIRE_BLOCK : Materials.TRIPWIRE_UNSHADED_BLOCK; + return getMaterial(chunkRenderType, shaded, true); + } + + @Nullable + public static Material getMaterial(RenderType chunkRenderType, boolean shaded, boolean ambientOcclusion) { + for (int chunkLayerIdx = 0; chunkLayerIdx < CHUNK_LAYERS.length; ++chunkLayerIdx) { + if (chunkRenderType == CHUNK_LAYERS[chunkLayerIdx]) { + int shadedIdx = shaded ? 1 : 0; + int ambientOcclusionIdx = ambientOcclusion ? 1 : 0; + + int materialIdx = chunkLayerIdx * 4 + shadedIdx * 2 + ambientOcclusionIdx; + + return CHUNK_MATERIALS[materialIdx]; + } } return null; } @Nullable public static Material getItemMaterial(RenderType renderType) { - var chunkMaterial = getMaterial(renderType, true); + var chunkMaterial = getMaterial(renderType, true, false); if (chunkMaterial != null) { return chunkMaterial; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java index 3eed07db2..2e6130fb5 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBuilder.java @@ -25,7 +25,7 @@ public abstract class BakedModelBuilder { @Nullable PoseStack poseStack; @Nullable - BiFunction materialFunc; + BlockMaterialFunction materialFunc; BakedModelBuilder(BakedModel bakedModel) { this.bakedModel = bakedModel; @@ -50,7 +50,17 @@ public BakedModelBuilder poseStack(@Nullable PoseStack poseStack) { return this; } - public BakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public BakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + if (materialFunc != null) { + this.materialFunc = (chunkRenderType, shaded, ambientOcclusion) -> materialFunc.apply(chunkRenderType, shaded); + } else { + this.materialFunc = null; + } + return this; + } + + public BakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { this.materialFunc = materialFunc; return this; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java new file mode 100644 index 000000000..c8b177ec8 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockMaterialFunction.java @@ -0,0 +1,11 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.material.Material; +import net.minecraft.client.renderer.RenderType; + +public interface BlockMaterialFunction { + @Nullable + Material apply(RenderType chunkRenderType, boolean shaded, boolean ambientOcclusion); +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java index 67f2eb0a6..060168d2d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BlockModelBuilder.java @@ -22,7 +22,7 @@ public abstract class BlockModelBuilder { PoseStack poseStack; boolean renderFluids = false; @Nullable - BiFunction materialFunc; + BlockMaterialFunction materialFunc; BlockModelBuilder(BlockAndTintGetter level, Iterable positions) { this.level = level; @@ -43,7 +43,17 @@ public BlockModelBuilder renderFluids(boolean renderFluids) { return this; } - public BlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public BlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + if (materialFunc != null) { + this.materialFunc = (chunkRenderType, shaded, ambientOcclusion) -> materialFunc.apply(chunkRenderType, shaded); + } else { + this.materialFunc = null; + } + return this; + } + + public BlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { this.materialFunc = materialFunc; return this; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java new file mode 100644 index 000000000..a419b6663 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BufferBuilderStack.java @@ -0,0 +1,30 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import com.mojang.blaze3d.vertex.BufferBuilder; + +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; + +class BufferBuilderStack { + private static final int INITIAL_CAPACITY_VERTICES = 256; + + private int nextBufferBuilderIndex = 0; + private final ReferenceArrayList bufferBuilders = new ReferenceArrayList<>(); + + BufferBuilder getOrCreateBufferBuilder() { + BufferBuilder bufferBuilder; + if (nextBufferBuilderIndex < bufferBuilders.size()) { + bufferBuilder = bufferBuilders.get(nextBufferBuilderIndex); + } else { + // Need to allocate at least some memory up front, as BufferBuilder internally + // only calls `ensureCapacity` after writing a vertex. + bufferBuilder = new BufferBuilder(INITIAL_CAPACITY_VERTICES); + bufferBuilders.add(bufferBuilder); + } + nextBufferBuilderIndex++; + return bufferBuilder; + } + + public void reset() { + nextBufferBuilderIndex = 0; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java new file mode 100644 index 000000000..c0e040847 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java @@ -0,0 +1,113 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.Arrays; + +import org.jetbrains.annotations.UnknownNullability; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; + +class MeshEmitter { + private static final int INITIAL_CAPACITY = 1; + + private final BufferBuilderStack bufferBuilderStack; + + private Material @UnknownNullability [] materials = new Material[INITIAL_CAPACITY]; + private BufferBuilder @UnknownNullability [] bufferBuilders = new BufferBuilder[INITIAL_CAPACITY]; + + // The number of valid elements in the above parallel arrays. + private int numBufferBuildersPopulated = 0; + + @UnknownNullability + BlockMaterialFunction blockMaterialFunction; + + private int currentIndex = 0; + + MeshEmitter(BufferBuilderStack bufferBuilderStack) { + this.bufferBuilderStack = bufferBuilderStack; + } + + public void prepare(BlockMaterialFunction blockMaterialFunction) { + this.blockMaterialFunction = blockMaterialFunction; + } + + public void prepareForBlock() { + // Quad render order within blocks must be preserved for correctness, however between blocks we should try to + // reduce the number of generated meshes as much as possible. Here we reset the head index without flushing + // any buffers, so that the next block can start over scanning through the parallel arrays looking for a + // matching Material/BufferBuilder pair. + currentIndex = 0; + } + + public void end(ImmutableList.Builder out) { + for (int index = 0; index < numBufferBuildersPopulated; index++) { + var renderedBuffer = bufferBuilders[index] + .endOrDiscardIfEmpty(); + + if (renderedBuffer != null) { + Material material = materials[index]; + Mesh mesh = MeshHelper.blockVerticesToMesh(renderedBuffer, "source=ModelBuilder" + ",material=" + material); + out.add(new Model.ConfiguredMesh(material, mesh)); + renderedBuffer.release(); + } + } + + // Not strictly necessary to clear the arrays, but best not to hold on to references for too long here. + Arrays.fill(bufferBuilders, 0, numBufferBuildersPopulated, null); + Arrays.fill(materials, 0, numBufferBuildersPopulated, null); + + currentIndex = 0; + numBufferBuildersPopulated = 0; + blockMaterialFunction = null; + } + + public BufferBuilder getBuffer(Material material) { + // First, scan through and try to find a matching Material. + while (currentIndex < numBufferBuildersPopulated) { + if (material.equals(materials[currentIndex])) { + // Return the matching BufferBuilder, but do not increment as we + // may still be able to use the same BufferBuilder in the next quad. + return bufferBuilders[currentIndex]; + } + ++currentIndex; + } + + // Nothing matched so we need to grab a new BufferBuilder. + // Make sure we have room to represent it in the arrays. + if (currentIndex >= materials.length) { + // Only technically need to grow one at a time here, but doubling is + // fine and should reduce the number of reallocations. + resize(materials.length * 2); + } + + BufferBuilder bufferBuilder = bufferBuilderStack.getOrCreateBufferBuilder(); + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); + + // currentIndex == numBufferBuildersPopulated here. + materials[currentIndex] = material; + bufferBuilders[currentIndex] = bufferBuilder; + + // Again, do not increment currentIndex so we can re-use the new + // BufferBuilder for the next quad if it matches. + ++numBufferBuildersPopulated; + + return bufferBuilder; + } + + private void resize(int capacity) { + BufferBuilder[] newBufferBuilders = new BufferBuilder[capacity]; + Material[] newMaterials = new Material[capacity]; + + System.arraycopy(bufferBuilders, 0, newBufferBuilders, 0, numBufferBuildersPopulated); + System.arraycopy(materials, 0, newMaterials, 0, numBufferBuildersPopulated); + + bufferBuilders = newBufferBuilders; + materials = newMaterials; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java new file mode 100644 index 000000000..949eb5cc2 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitterManager.java @@ -0,0 +1,75 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.function.BiFunction; + +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.BufferBuilder; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.SimpleModel; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import net.minecraft.client.renderer.RenderType; + +class MeshEmitterManager { + private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); + + private final Reference2ReferenceMap emitterMap = new Reference2ReferenceArrayMap<>(); + private final BufferBuilderStack bufferBuilderStack = new BufferBuilderStack(); + + @UnknownNullability + private BlockMaterialFunction blockMaterialFunction; + + MeshEmitterManager(BiFunction meshEmitterFactory) { + for (RenderType renderType : CHUNK_LAYERS) { + T emitter = meshEmitterFactory.apply(bufferBuilderStack, renderType); + emitterMap.put(renderType, emitter); + } + } + + public T getEmitter(RenderType renderType) { + return emitterMap.get(renderType); + } + + public void prepare(BlockMaterialFunction blockMaterialFunction) { + this.blockMaterialFunction = blockMaterialFunction; + bufferBuilderStack.reset(); + + for (MeshEmitter emitter : emitterMap.values()) { + emitter.prepare(blockMaterialFunction); + } + } + + public void prepareForBlock() { + for (MeshEmitter emitter : emitterMap.values()) { + emitter.prepareForBlock(); + } + } + + public SimpleModel end() { + blockMaterialFunction = null; + bufferBuilderStack.reset(); + + ImmutableList.Builder meshes = ImmutableList.builder(); + + for (MeshEmitter emitter : emitterMap.values()) { + emitter.end(meshes); + } + + return new SimpleModel(meshes.build()); + } + + @Nullable + public BufferBuilder getBuffer(RenderType renderType, boolean shade, boolean ao) { + Material key = blockMaterialFunction.apply(renderType, shade, ao); + if (key != null) { + return emitterMap.get(renderType).getBuffer(key); + } else { + return null; + } + } +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl index 4844308d9..c481ae6d7 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl +++ b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth.glsl @@ -3,6 +3,8 @@ void flw_shaderLight() { if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, light)) { flw_fragLight = max(flw_fragLight, light.light); - flw_fragColor.rgb *= light.ao; + if (flw_material.ambientOcclusion) { + flw_fragColor.rgb *= light.ao; + } } } diff --git a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl index 035c6e9ad..439eac214 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl +++ b/common/src/lib/resources/assets/flywheel/flywheel/light/smooth_when_embedded.glsl @@ -4,7 +4,9 @@ void flw_shaderLight() { if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, light)) { flw_fragLight = max(flw_fragLight, light.light); - flw_fragColor.rgb *= light.ao; + if (flw_material.ambientOcclusion) { + flw_fragColor.rgb *= light.ao; + } } #endif } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index e97c5154e..721c1bda9 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -4,11 +4,10 @@ import org.jetbrains.annotations.Nullable; -import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -24,84 +23,79 @@ import net.minecraft.world.level.material.FluidState; final class BakedModelBufferer { - static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length; - private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) { + public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; - UniversalMeshEmitter universalEmitter = objects.universalEmitter; + FabricMeshEmitterManager emitters = objects.emitters; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(blockMaterialFunction); long seed = state.getSeed(pos); RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state); - universalEmitter.prepare(defaultLayer); - model = universalEmitter.wrapModel(model); + boolean useAo = Minecraft.useAmbientOcclusion(); + // See ModelBlockRenderer#tesselateBlock + boolean defaultAo = useAo && state.getLightEmission() == 0 && model.useAmbientOcclusion(); + model = emitters.prepareForModel(model, defaultLayer, useAo, defaultAo); poseStack.pushPose(); Minecraft.getInstance() .getBlockRenderer() .getModelRenderer() - .tesselateBlock(level, model, state, pos, poseStack, universalEmitter, false, random, seed, OverlayTexture.NO_OVERLAY); + .tesselateBlock(level, model, state, pos, poseStack, emitters, false, random, seed, OverlayTexture.NO_OVERLAY); poseStack.popPose(); - universalEmitter.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } + return emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ResultConsumer resultConsumer) { + public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; - Reference2ReferenceMap emitterMap = objects.emitterMap; - UniversalMeshEmitter universalEmitter = objects.universalEmitter; + FabricMeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(blockMaterialFunction); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); - ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer.enableCaching(); + boolean useAo = Minecraft.useAmbientOcclusion(); + while (posIterator.hasNext()) { BlockPos pos = posIterator.next(); BlockState state = level.getBlockState(pos); + emitters.prepareForBlock(); + if (renderFluids) { FluidState fluidState = state.getFluidState(); if (!fluidState.isEmpty()) { RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState); - transformingWrapper.prepare(emitterMap.get(renderType).getBuffer(true), poseStack); + BufferBuilder bufferBuilder = emitters.getBuffer(renderType, true, false); + + if (bufferBuilder != null) { + transformingWrapper.prepare(bufferBuilder, poseStack); - poseStack.pushPose(); - poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); - renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); - poseStack.popPose(); + poseStack.pushPose(); + poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); + renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); + poseStack.popPose(); + } } } @@ -110,46 +104,28 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett BakedModel model = renderDispatcher.getBlockModel(state); RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state); - universalEmitter.prepare(defaultLayer); - model = universalEmitter.wrapModel(model); + + // See ModelBlockRenderer#tesselateBlock + boolean defaultAo = useAo && state.getLightEmission() == 0 && model.useAmbientOcclusion(); + model = emitters.prepareForModel(model, defaultLayer, useAo, defaultAo); poseStack.pushPose(); poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); - blockRenderer.tesselateBlock(level, model, state, pos, poseStack, universalEmitter, true, random, seed, OverlayTexture.NO_OVERLAY); + blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitters, true, random, seed, OverlayTexture.NO_OVERLAY); poseStack.popPose(); } } ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - universalEmitter.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } - } - - public interface ResultConsumer { - void accept(RenderType renderType, boolean shaded, RenderedBuffer data); + return emitters.end(); } private static class ThreadLocalObjects { public final PoseStack identityPoseStack = new PoseStack(); public final RandomSource random = RandomSource.createNewThreadLocalInstance(); - public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; - public final Reference2ReferenceMap emitterMap = new Reference2ReferenceOpenHashMap<>(); - public final UniversalMeshEmitter universalEmitter; + public final FabricMeshEmitterManager emitters = new FabricMeshEmitterManager(); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); - - { - for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = CHUNK_LAYERS[layerIndex]; - MeshEmitter emitter = new MeshEmitter(renderType); - emitters[layerIndex] = emitter; - emitterMap.put(renderType, emitter); - } - universalEmitter = new UniversalMeshEmitter(emitterMap); - } } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java deleted file mode 100644 index eae9dad15..000000000 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -import net.minecraft.client.renderer.RenderType; - -class ChunkLayerSortedListBuilder { - private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(ChunkLayerSortedListBuilder::new); - - @SuppressWarnings("unchecked") - private final ObjectArrayList[] lists = new ObjectArrayList[BakedModelBufferer.CHUNK_LAYER_AMOUNT]; - private final Reference2ReferenceMap> map = new Reference2ReferenceOpenHashMap<>(); - - private ChunkLayerSortedListBuilder() { - for (int layerIndex = 0; layerIndex < BakedModelBufferer.CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = BakedModelBufferer.CHUNK_LAYERS[layerIndex]; - ObjectArrayList list = new ObjectArrayList<>(); - lists[layerIndex] = list; - map.put(renderType, list); - } - } - - @SuppressWarnings("unchecked") - public static ChunkLayerSortedListBuilder getThreadLocal() { - return (ChunkLayerSortedListBuilder) THREAD_LOCAL.get(); - } - - public void add(RenderType renderType, T obj) { - List list = map.get(renderType); - if (list == null) { - throw new IllegalArgumentException("RenderType '" + renderType + "' is not a chunk layer"); - } - list.add(obj); - } - - @SuppressWarnings("unchecked") - public ImmutableList build() { - int size = 0; - for (ObjectArrayList list : lists) { - size += list.size(); - } - - T[] array = (T[]) new Object[size]; - int destPos = 0; - for (ObjectArrayList list : lists) { - System.arraycopy(list.elements(), 0, array, destPos, list.size()); - destPos += list.size(); - list.clear(); - } - - return ImmutableList.copyOf(array); - } -} diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java index acf01934c..3b9f4f46b 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBakedModelBuilder.java @@ -2,11 +2,11 @@ import java.util.function.BiFunction; +import org.jetbrains.annotations.Nullable; + import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -21,25 +21,32 @@ public FabricBakedModelBuilder(BakedModel bakedModel) { } @Override - public FabricBakedModelBuilder level(BlockAndTintGetter level) { + public FabricBakedModelBuilder level(@Nullable BlockAndTintGetter level) { super.level(level); return this; } @Override - public FabricBakedModelBuilder pos(BlockPos pos) { + public FabricBakedModelBuilder pos(@Nullable BlockPos pos) { super.pos(pos); return this; } @Override - public FabricBakedModelBuilder poseStack(PoseStack poseStack) { + public FabricBakedModelBuilder poseStack(@Nullable PoseStack poseStack) { super.poseStack(poseStack); return this; } @Override - public FabricBakedModelBuilder materialFunc(BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public FabricBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public FabricBakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -57,16 +64,6 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + return BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, materialFunc); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java index 6e3bb0911..32c6b2904 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricBlockModelBuilder.java @@ -7,8 +7,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -33,7 +31,14 @@ public FabricBlockModelBuilder renderFluids(boolean renderFluids) { } @Override - public FabricBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public FabricBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public FabricBlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -44,16 +49,6 @@ public SimpleModel build() { materialFunc = ModelUtil::getMaterial; } - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + return BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, renderFluids, materialFunc); } } diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java similarity index 63% rename from fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java rename to fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java index feb560bcc..2aab5561a 100644 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/UniversalMeshEmitter.java +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricMeshEmitterManager.java @@ -9,11 +9,12 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.resources.model.BakedModel; @@ -22,108 +23,136 @@ import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -class UniversalMeshEmitter implements VertexConsumer { - private final Reference2ReferenceMap emitterMap; +class FabricMeshEmitterManager extends MeshEmitterManager implements VertexConsumer { private final WrapperModel wrapperModel = new WrapperModel(); @UnknownNullability private RenderType defaultLayer; - @UnknownNullability + private boolean useAo; + private boolean defaultAo; + @Nullable private BufferBuilder currentDelegate; - UniversalMeshEmitter(Reference2ReferenceMap emitterMap) { - this.emitterMap = emitterMap; - } + FabricMeshEmitterManager() { + super((bufferBuilderSupplier, renderType) -> new MeshEmitter(bufferBuilderSupplier)); + } - public void prepare(RenderType defaultLayer) { + public BakedModel prepareForModel(BakedModel model, RenderType defaultLayer, boolean useAo, boolean defaultAo) { this.defaultLayer = defaultLayer; + this.useAo = useAo; + this.defaultAo = defaultAo; + wrapperModel.setWrapped(model); + return wrapperModel; } - public void clear() { + @Override + public SimpleModel end() { wrapperModel.setWrapped(null); - } - - public BakedModel wrapModel(BakedModel model) { - wrapperModel.setWrapped(model); - return wrapperModel; + return super.end(); } private void prepareForGeometry(RenderMaterial material) { BlendMode blendMode = material.blendMode(); RenderType layer = blendMode == BlendMode.DEFAULT ? defaultLayer : blendMode.blockRenderLayer; boolean shade = !material.disableDiffuse(); - currentDelegate = emitterMap.get(layer).getBuffer(shade); + TriState aoMode = material.ambientOcclusion(); + boolean ao = useAo && aoMode.orElse(defaultAo); + currentDelegate = getBuffer(layer, shade, ao); } @Override public VertexConsumer vertex(double x, double y, double z) { - currentDelegate.vertex(x, y, z); + if (currentDelegate != null) { + currentDelegate.vertex(x, y, z); + } return this; } @Override public VertexConsumer color(int red, int green, int blue, int alpha) { - currentDelegate.color(red, green, blue, alpha); + if (currentDelegate != null) { + currentDelegate.color(red, green, blue, alpha); + } return this; } @Override public VertexConsumer uv(float u, float v) { - currentDelegate.uv(u, v); + if (currentDelegate != null) { + currentDelegate.uv(u, v); + } return this; } @Override public VertexConsumer overlayCoords(int u, int v) { - currentDelegate.overlayCoords(u, v); + if (currentDelegate != null) { + currentDelegate.overlayCoords(u, v); + } return this; } @Override public VertexConsumer uv2(int u, int v) { - currentDelegate.uv2(u, v); + if (currentDelegate != null) { + currentDelegate.uv2(u, v); + } return this; } @Override public VertexConsumer normal(float x, float y, float z) { - currentDelegate.normal(x, y, z); + if (currentDelegate != null) { + currentDelegate.normal(x, y, z); + } return this; } @Override public void endVertex() { - currentDelegate.endVertex(); + if (currentDelegate != null) { + currentDelegate.endVertex(); + } } @Override public void defaultColor(int red, int green, int blue, int alpha) { - currentDelegate.defaultColor(red, green, blue, alpha); + if (currentDelegate != null) { + currentDelegate.defaultColor(red, green, blue, alpha); + } } @Override public void unsetDefaultColor() { - currentDelegate.unsetDefaultColor(); + if (currentDelegate != null) { + currentDelegate.unsetDefaultColor(); + } } @Override public void vertex(float x, float y, float z, float red, float green, float blue, float alpha, float u, float v, int overlay, int light, float normalX, float normalY, float normalZ) { - currentDelegate.vertex(x, y, z, red, green, blue, alpha, u, v, overlay, light, normalX, normalY, normalZ); + if (currentDelegate != null) { + currentDelegate.vertex(x, y, z, red, green, blue, alpha, u, v, overlay, light, normalX, normalY, normalZ); + } } @Override public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { - currentDelegate.putBulkData(pose, quad, red, green, blue, light, overlay); + if (currentDelegate != null) { + currentDelegate.putBulkData(pose, quad, red, green, blue, light, overlay); + } } @Override public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { - currentDelegate.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + if (currentDelegate != null) { + currentDelegate.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + } } private class WrapperModel extends ForwardingBakedModel { private final RenderContext.QuadTransform quadTransform = quad -> { - UniversalMeshEmitter.this.prepareForGeometry(quad.material()); + FabricMeshEmitterManager.this.prepareForGeometry(quad.material()); return true; }; diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java deleted file mode 100644 index 13bdfc521..000000000 --- a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.UnknownNullability; - -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.VertexFormat; - -import net.minecraft.client.renderer.RenderType; - -class MeshEmitter { - private final RenderType renderType; - private final BufferBuilder bufferBuilder; - - private BakedModelBufferer.@UnknownNullability ResultConsumer resultConsumer; - private boolean currentShade; - - MeshEmitter(RenderType renderType) { - this.renderType = renderType; - this.bufferBuilder = new BufferBuilder(renderType.bufferSize()); - } - - public void prepare(BakedModelBufferer.ResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; - } - - public void end() { - if (bufferBuilder.building()) { - emit(); - } - resultConsumer = null; - } - - public BufferBuilder getBuffer(boolean shade) { - prepareForGeometry(shade); - return bufferBuilder; - } - - private void prepareForGeometry(boolean shade) { - if (!bufferBuilder.building()) { - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } else if (shade != currentShade) { - emit(); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } - - currentShade = shade; - } - - private void emit() { - var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty(); - - if (renderedBuffer != null) { - resultConsumer.accept(renderType, currentShade, renderedBuffer); - renderedBuffer.release(); - } - } -} diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 220ff8b50..45d75cffb 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -119,6 +119,7 @@ loom { forge { mixinConfig("flywheel.backend.mixins.json") mixinConfig("flywheel.impl.mixins.json") + mixinConfig("flywheel.impl.forge.mixins.json") } runs { diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 996144805..9351aa4bc 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -5,9 +5,10 @@ import org.jetbrains.annotations.Nullable; -import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; +import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; @@ -25,81 +26,87 @@ import net.minecraftforge.client.model.data.ModelData; final class BakedModelBufferer { - static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new); - static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length; - private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); private BakedModelBufferer() { } - public static void bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) { + public static SimpleModel bufferModel(BakedModel model, BlockPos pos, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; + MeshEmitterManager emitters = objects.emitters; + + emitters.prepare(blockMaterialFunction); + + ModelBlockRenderer blockRenderer = Minecraft.getInstance() + .getBlockRenderer() + .getModelRenderer(); long seed = state.getSeed(pos); modelData = model.getModelData(level, pos, state, modelData); random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); - ModelBlockRenderer blockRenderer = Minecraft.getInstance() - .getBlockRenderer() - .getModelRenderer(); + // See ModelBlockRenderer#tesselateBlock + boolean defaultAo = Minecraft.useAmbientOcclusion() && state.getLightEmission(level, pos) == 0; for (RenderType renderType : renderTypes) { - int layerIndex = renderType.getChunkLayerId(); - MeshEmitter emitter = emitters[layerIndex]; + boolean defaultAoLayer = defaultAo && model.useAmbientOcclusion(state, renderType); - emitter.prepare(resultConsumer); + ForgeMeshEmitter emitter = emitters.getEmitter(renderType); + emitter.prepareForModelLayer(defaultAoLayer); poseStack.pushPose(); blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitter, false, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); poseStack.popPose(); - - emitter.end(); } + + return emitters.end(); } - public static void bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) { + public static SimpleModel bufferBlocks(Iterator posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function modelDataLookup, boolean renderFluids, BlockMaterialFunction blockMaterialFunction) { ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); if (poseStack == null) { poseStack = objects.identityPoseStack; } RandomSource random = objects.random; - MeshEmitter[] emitters = objects.emitters; + MeshEmitterManager emitters = objects.emitters; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; - for (MeshEmitter emitter : emitters) { - emitter.prepare(resultConsumer); - } + emitters.prepare(blockMaterialFunction); BlockRenderDispatcher renderDispatcher = Minecraft.getInstance() .getBlockRenderer(); - ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer.enableCaching(); + boolean useAo = Minecraft.useAmbientOcclusion(); + while (posIterator.hasNext()) { BlockPos pos = posIterator.next(); BlockState state = level.getBlockState(pos); + emitters.prepareForBlock(); + if (renderFluids) { FluidState fluidState = state.getFluidState(); if (!fluidState.isEmpty()) { RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState); - int layerIndex = renderType.getChunkLayerId(); - transformingWrapper.prepare(emitters[layerIndex].unwrap(true), poseStack); + BufferBuilder bufferBuilder = emitters.getBuffer(renderType, true, false); - poseStack.pushPose(); - poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); - renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); - poseStack.popPose(); + if (bufferBuilder != null) { + transformingWrapper.prepare(bufferBuilder, poseStack); + + poseStack.pushPose(); + poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); + renderDispatcher.renderLiquid(pos, level, transformingWrapper, state, fluidState); + poseStack.popPose(); + } } } @@ -111,12 +118,18 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett random.setSeed(seed); ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, random, modelData); + // See ModelBlockRenderer#tesselateBlock + boolean defaultAo = useAo && state.getLightEmission(level, pos) == 0; + for (RenderType renderType : renderTypes) { - int layerIndex = renderType.getChunkLayerId(); + boolean defaultAoLayer = defaultAo && model.useAmbientOcclusion(state, renderType); + + ForgeMeshEmitter emitter = emitters.getEmitter(renderType); + emitter.prepareForModelLayer(defaultAoLayer); poseStack.pushPose(); poseStack.translate(pos.getX(), pos.getY(), pos.getZ()); - blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitters[layerIndex], true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); + blockRenderer.tesselateBlock(level, model, state, pos, poseStack, emitter, true, random, seed, OverlayTexture.NO_OVERLAY, modelData, renderType); poseStack.popPose(); } } @@ -124,28 +137,14 @@ public static void bufferBlocks(Iterator posIterator, BlockAndTintGett ModelBlockRenderer.clearCache(); transformingWrapper.clear(); - - for (MeshEmitter emitter : emitters) { - emitter.end(); - } - } - - public interface ResultConsumer { - void accept(RenderType renderType, boolean shaded, RenderedBuffer data); + return emitters.end(); } private static class ThreadLocalObjects { public final PoseStack identityPoseStack = new PoseStack(); public final RandomSource random = RandomSource.createNewThreadLocalInstance(); - public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; + public final MeshEmitterManager emitters = new MeshEmitterManager<>(ForgeMeshEmitter::new); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); - - { - for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { - RenderType renderType = CHUNK_LAYERS[layerIndex]; - emitters[layerIndex] = new MeshEmitter(renderType); - } - } } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java deleted file mode 100644 index 4026ff008..000000000 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ChunkLayerSortedListBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.minecraft.client.renderer.RenderType; - -class ChunkLayerSortedListBuilder { - private static final ThreadLocal> THREAD_LOCAL = ThreadLocal.withInitial(ChunkLayerSortedListBuilder::new); - - @SuppressWarnings("unchecked") - private final ObjectArrayList[] lists = new ObjectArrayList[BakedModelBufferer.CHUNK_LAYER_AMOUNT]; - - private ChunkLayerSortedListBuilder() { - for (int layerIndex = 0; layerIndex < BakedModelBufferer.CHUNK_LAYER_AMOUNT; layerIndex++) { - ObjectArrayList list = new ObjectArrayList<>(); - lists[layerIndex] = list; - } - } - - @SuppressWarnings("unchecked") - public static ChunkLayerSortedListBuilder getThreadLocal() { - return (ChunkLayerSortedListBuilder) THREAD_LOCAL.get(); - } - - public void add(RenderType renderType, T obj) { - int layerIndex = renderType.getChunkLayerId(); - if (layerIndex == -1) { - throw new IllegalArgumentException("RenderType '" + renderType + "' is not a chunk layer"); - } - List list = lists[layerIndex]; - list.add(obj); - } - - @SuppressWarnings("unchecked") - public ImmutableList build() { - int size = 0; - for (ObjectArrayList list : lists) { - size += list.size(); - } - - T[] array = (T[]) new Object[size]; - int destPos = 0; - for (ObjectArrayList list : lists) { - System.arraycopy(list.elements(), 0, array, destPos, list.size()); - destPos += list.size(); - list.clear(); - } - - return ImmutableList.copyOf(array); - } -} diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java index 2162dd634..8632001a9 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBakedModelBuilder.java @@ -7,8 +7,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -46,7 +44,14 @@ public ForgeBakedModelBuilder poseStack(@Nullable PoseStack poseStack) { } @Override - public ForgeBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public ForgeBakedModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public ForgeBakedModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -73,16 +78,6 @@ public SimpleModel build() { } BlockState blockState = level.getBlockState(pos); - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + return BakedModelBufferer.bufferModel(bakedModel, pos, level, blockState, poseStack, modelData, materialFunc); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java index 06da266a4..ca0064288 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeBlockModelBuilder.java @@ -8,8 +8,6 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.model.ModelUtil; import dev.engine_room.flywheel.lib.model.SimpleModel; import net.minecraft.client.renderer.RenderType; @@ -39,7 +37,14 @@ public ForgeBlockModelBuilder renderFluids(boolean renderFluids) { } @Override - public ForgeBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + @Deprecated(forRemoval = true) + public ForgeBlockModelBuilder materialFunc(@Nullable BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public ForgeBlockModelBuilder materialFunc(@Nullable BlockMaterialFunction materialFunc) { super.materialFunc(materialFunc); return this; } @@ -61,16 +66,6 @@ public SimpleModel build() { }; } - var builder = ChunkLayerSortedListBuilder.getThreadLocal(); - - BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, (renderType, shaded, data) -> { - Material material = materialFunc.apply(renderType, shaded); - if (material != null) { - Mesh mesh = MeshHelper.blockVerticesToMesh(data, "source=BlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); - builder.add(renderType, new Model.ConfiguredMesh(material, mesh)); - } - }); - - return new SimpleModel(builder.build()); + return BakedModelBufferer.bufferBlocks(positions.iterator(), level, poseStack, modelDataLookup, renderFluids, materialFunc); } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java new file mode 100644 index 000000000..c8c24394c --- /dev/null +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeMeshEmitter.java @@ -0,0 +1,130 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import dev.engine_room.flywheel.api.material.Material; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.ModelBlockRenderer; +import net.minecraft.client.renderer.block.model.BakedQuad; + +@ApiStatus.Internal +public class ForgeMeshEmitter extends MeshEmitter implements VertexConsumer { + private final RenderType renderType; + + private boolean defaultAo; + + ForgeMeshEmitter(BufferBuilderStack bufferBuilderStack, RenderType renderType) { + super(bufferBuilderStack); + this.renderType = renderType; + } + + /** + * Some mods, like FramedBlocks, have custom hooks to determine the default AO. This method is invoked a second time + * from within a mixin to {@link ModelBlockRenderer} after the accurate value is computed, so we don't need to + * support those custom hooks manually. It is possible that the mixin injector will never run (primarily due to + * implementations of Fabric Renderer API on Forge, like Indigo in Forgified Fabric API), so we always compute the + * value manually beforehand too. + */ + public void prepareForModelLayer(boolean defaultAo) { + this.defaultAo = defaultAo; + } + + @Nullable + private BufferBuilder getBuffer(boolean shade, boolean ao) { + Material key = blockMaterialFunction.apply(renderType, shade, ao); + if (key != null) { + return getBuffer(key); + } else { + return null; + } + } + + @Nullable + private BufferBuilder getBuffer(BakedQuad quad) { + boolean shade = quad.isShade(); + boolean ao = quad.hasAmbientOcclusion() && defaultAo; + return getBuffer(shade, ao); + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, red, green, blue, light, overlay); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, float alpha, int light, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, red, green, blue, alpha, light, overlay, readExistingColor); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); + } + } + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, float alpha, int[] lights, int overlay, boolean readExistingColor) { + BufferBuilder bufferBuilder = getBuffer(quad); + if (bufferBuilder != null) { + bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, alpha, lights, overlay, readExistingColor); + } + } + + @Override + public VertexConsumer vertex(double x, double y, double z) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer uv(float u, float v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer overlayCoords(int u, int v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer uv2(int u, int v) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public VertexConsumer normal(float x, float y, float z) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void endVertex() { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void defaultColor(int red, int green, int blue, int alpha) { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } + + @Override + public void unsetDefaultColor() { + throw new UnsupportedOperationException("ForgeMeshEmitter only supports putBulkData!"); + } +} diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java deleted file mode 100644 index e878ae95c..000000000 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java +++ /dev/null @@ -1,134 +0,0 @@ -package dev.engine_room.flywheel.lib.model.baked; - -import org.jetbrains.annotations.UnknownNullability; - -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.blaze3d.vertex.VertexFormat; - -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedQuad; - -class MeshEmitter implements VertexConsumer { - private final RenderType renderType; - private final BufferBuilder bufferBuilder; - - private BakedModelBufferer.@UnknownNullability ResultConsumer resultConsumer; - private boolean currentShade; - - MeshEmitter(RenderType renderType) { - this.renderType = renderType; - this.bufferBuilder = new BufferBuilder(renderType.bufferSize()); - } - - public void prepare(BakedModelBufferer.ResultConsumer resultConsumer) { - this.resultConsumer = resultConsumer; - } - - public void end() { - if (bufferBuilder.building()) { - emit(); - } - resultConsumer = null; - } - - public BufferBuilder unwrap(boolean shade) { - prepareForGeometry(shade); - return bufferBuilder; - } - - private void prepareForGeometry(boolean shade) { - if (!bufferBuilder.building()) { - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } else if (shade != currentShade) { - emit(); - bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - } - - currentShade = shade; - } - - private void prepareForGeometry(BakedQuad quad) { - prepareForGeometry(quad.isShade()); - } - - private void emit() { - var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty(); - - if (renderedBuffer != null) { - resultConsumer.accept(renderType, currentShade, renderedBuffer); - renderedBuffer.release(); - } - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int light, int overlay) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, red, green, blue, light, overlay); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, float alpha, int light, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, red, green, blue, alpha, light, overlay, readExistingColor); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, int[] lights, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, lights, overlay, readExistingColor); - } - - @Override - public void putBulkData(PoseStack.Pose pose, BakedQuad quad, float[] brightnesses, float red, float green, float blue, float alpha, int[] lights, int overlay, boolean readExistingColor) { - prepareForGeometry(quad); - bufferBuilder.putBulkData(pose, quad, brightnesses, red, green, blue, alpha, lights, overlay, readExistingColor); - } - - @Override - public VertexConsumer vertex(double x, double y, double z) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer color(int red, int green, int blue, int alpha) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer uv(float u, float v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer overlayCoords(int u, int v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer uv2(int u, int v) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public VertexConsumer normal(float x, float y, float z) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void endVertex() { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void defaultColor(int red, int green, int blue, int alpha) { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } - - @Override - public void unsetDefaultColor() { - throw new UnsupportedOperationException("MeshEmitter only supports putBulkData!"); - } -} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java new file mode 100644 index 000000000..b69dad0c2 --- /dev/null +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/forge/ModelBlockRendererMixin.java @@ -0,0 +1,30 @@ +package dev.engine_room.flywheel.impl.mixin.forge; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import dev.engine_room.flywheel.lib.model.baked.ForgeMeshEmitter; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.ModelBlockRenderer; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.client.model.data.ModelData; + +@Mixin(ModelBlockRenderer.class) +abstract class ModelBlockRendererMixin { + @Inject(method = "tesselateBlock(Lnet/minecraft/world/level/BlockAndTintGetter;Lnet/minecraft/client/resources/model/BakedModel;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;ZLnet/minecraft/util/RandomSource;JILnet/minecraftforge/client/model/data/ModelData;Lnet/minecraft/client/renderer/RenderType;)V", at = @At(value = "INVOKE", target = "net/minecraft/world/level/block/state/BlockState.getOffset(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;"), locals = LocalCapture.CAPTURE_FAILSOFT, require = 0) + private void onTesselateBlock(BlockAndTintGetter level, BakedModel model, BlockState state, BlockPos pos, PoseStack poseStack, VertexConsumer consumer, boolean checkSides, RandomSource random, long seed, int packedOverlay, ModelData modelData, RenderType renderType, CallbackInfo ci, boolean ao) { + if (consumer instanceof ForgeMeshEmitter meshEmitter) { + meshEmitter.prepareForModelLayer(ao); + } + } +} diff --git a/forge/src/main/resources/flywheel.impl.forge.mixins.json b/forge/src/main/resources/flywheel.impl.forge.mixins.json new file mode 100644 index 000000000..c918f0fd6 --- /dev/null +++ b/forge/src/main/resources/flywheel.impl.forge.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.engine_room.flywheel.impl.mixin.forge", + "compatibilityLevel": "JAVA_17", + "refmap": "flywheel.refmap.json", + "client": [ + "ModelBlockRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}