Best-effort untested Fabric model builders

- Also fix crash buffering fluids in BakedModelBufferer#bufferMultiBlock (Forge)
- The mesh order in models created by model builders is currently incorrect and will be fixed later
This commit is contained in:
PepperCode1 2024-04-26 22:31:29 -07:00 committed by Jozufozu
parent 9ae4065c1c
commit b9490fe11a
19 changed files with 853 additions and 219 deletions

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
@ -14,25 +15,26 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ApiStatus.NonExtendable
public abstract class BakedModelBuilder { public abstract class BakedModelBuilder {
protected final BakedModel bakedModel; final BakedModel bakedModel;
@Nullable @Nullable
protected BlockAndTintGetter level; BlockAndTintGetter level;
@Nullable @Nullable
protected BlockState blockState; BlockState blockState;
@Nullable @Nullable
protected PoseStack poseStack; PoseStack poseStack;
@Nullable @Nullable
protected BiFunction<RenderType, Boolean, Material> materialFunc; BiFunction<RenderType, Boolean, Material> materialFunc;
BakedModelBuilder(BakedModel bakedModel) {
this.bakedModel = bakedModel;
}
public static BakedModelBuilder create(BakedModel bakedModel) { public static BakedModelBuilder create(BakedModel bakedModel) {
return FlwLibXplat.INSTANCE.createBakedModelBuilder(bakedModel); return FlwLibXplat.INSTANCE.createBakedModelBuilder(bakedModel);
} }
protected BakedModelBuilder(BakedModel bakedModel) {
this.bakedModel = bakedModel;
}
public BakedModelBuilder level(BlockAndTintGetter level) { public BakedModelBuilder level(BlockAndTintGetter level) {
this.level = level; this.level = level;
return this; return this;

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
@ -13,23 +14,24 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@ApiStatus.NonExtendable
public abstract class BlockModelBuilder { public abstract class BlockModelBuilder {
protected final BlockState state; final BlockState state;
@Nullable @Nullable
protected BlockAndTintGetter level; BlockAndTintGetter level;
@Nullable @Nullable
protected PoseStack poseStack; PoseStack poseStack;
@Nullable @Nullable
protected BiFunction<RenderType, Boolean, Material> materialFunc; BiFunction<RenderType, Boolean, Material> materialFunc;
BlockModelBuilder(BlockState state) {
this.state = state;
}
public static BlockModelBuilder create(BlockState state) { public static BlockModelBuilder create(BlockState state) {
return FlwLibXplat.INSTANCE.createBlockModelBuilder(state); return FlwLibXplat.INSTANCE.createBlockModelBuilder(state);
} }
protected BlockModelBuilder(BlockState state) {
this.state = state;
}
public BlockModelBuilder level(BlockAndTintGetter level) { public BlockModelBuilder level(BlockAndTintGetter level) {
this.level = level; this.level = level;
return this; return this;

View file

@ -1,119 +0,0 @@
package com.jozufozu.flywheel.lib.model.baked;
import org.jetbrains.annotations.Nullable;
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 {
protected final BufferBuilder bufferBuilder;
private final RenderType renderType;
private boolean lastQuadWasShaded;
private boolean seenFirstQuad;
@Nullable
private MeshEmitter.ResultConsumer resultConsumer;
MeshEmitter(BufferBuilder bufferBuilder, RenderType renderType) {
this.bufferBuilder = bufferBuilder;
this.renderType = renderType;
}
public void begin(ResultConsumer resultConsumer) {
this.resultConsumer = resultConsumer;
begin();
}
public void end() {
emit();
seenFirstQuad = false;
resultConsumer = null;
}
private void begin() {
bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
}
private void emit() {
var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty();
if (renderedBuffer != null) {
if (resultConsumer != null) {
resultConsumer.accept(renderType, lastQuadWasShaded, renderedBuffer);
}
renderedBuffer.release();
}
}
protected void observeQuadAndEmitIfNecessary(BakedQuad quad) {
if (seenFirstQuad && lastQuadWasShaded != quad.isShade()) {
emit();
begin();
}
seenFirstQuad = true;
lastQuadWasShaded = quad.isShade();
}
@Override
public void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
observeQuadAndEmitIfNecessary(quad);
bufferBuilder.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv(float u, float v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer overlayCoords(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer uv2(int u, int v) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public VertexConsumer normal(float x, float y, float z) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void endVertex() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void defaultColor(int red, int green, int blue, int alpha) {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
@Override
public void unsetDefaultColor() {
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
}
public interface ResultConsumer {
void accept(RenderType renderType, boolean shaded, BufferBuilder.RenderedBuffer data);
}
}

View file

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
@ -13,24 +14,25 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockAndTintGetter;
@ApiStatus.NonExtendable
public abstract class MultiBlockModelBuilder { public abstract class MultiBlockModelBuilder {
protected final BlockAndTintGetter level; final BlockAndTintGetter level;
protected final Iterable<BlockPos> positions; final Iterable<BlockPos> positions;
@Nullable @Nullable
protected PoseStack poseStack; PoseStack poseStack;
protected boolean renderFluids = false; boolean renderFluids = false;
@Nullable @Nullable
protected BiFunction<RenderType, Boolean, Material> materialFunc; BiFunction<RenderType, Boolean, Material> materialFunc;
MultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions) {
this.level = level;
this.positions = positions;
}
public static MultiBlockModelBuilder create(BlockAndTintGetter level, Iterable<BlockPos> positions) { public static MultiBlockModelBuilder create(BlockAndTintGetter level, Iterable<BlockPos> positions) {
return FlwLibXplat.INSTANCE.createMultiBlockModelBuilder(level, positions); return FlwLibXplat.INSTANCE.createMultiBlockModelBuilder(level, positions);
} }
protected MultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions) {
this.level = level;
this.positions = positions;
}
public MultiBlockModelBuilder poseStack(PoseStack poseStack) { public MultiBlockModelBuilder poseStack(PoseStack poseStack) {
this.poseStack = poseStack; this.poseStack = poseStack;
return this; return this;

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import org.jetbrains.annotations.UnknownNullability;
import org.joml.Matrix3f; import org.joml.Matrix3f;
import org.joml.Matrix4f; import org.joml.Matrix4f;
@ -8,7 +9,9 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
class TransformingVertexConsumer implements VertexConsumer { class TransformingVertexConsumer implements VertexConsumer {
@UnknownNullability
private VertexConsumer delegate; private VertexConsumer delegate;
@UnknownNullability
private PoseStack poseStack; private PoseStack poseStack;
public void prepare(VertexConsumer delegate, PoseStack poseStack) { public void prepare(VertexConsumer delegate, PoseStack poseStack) {

View file

@ -15,7 +15,7 @@ import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.LightLayer;
@Mixin(ClientChunkCache.class) @Mixin(ClientChunkCache.class)
public class ClientChunkCacheMixin { abstract class ClientChunkCacheMixin {
@Shadow @Shadow
@Final @Final
ClientLevel level; ClientLevel level;

View file

@ -0,0 +1,154 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.Iterator;
import org.jetbrains.annotations.Nullable;
import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer;
import com.mojang.blaze3d.vertex.PoseStack;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
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.RenderShape;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
final class BakedModelBufferer {
private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new);
private static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length;
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
private BakedModelBufferer() {
}
public static void bufferSingle(ModelBlockRenderer blockRenderer, BlockAndTintGetter level, BakedModel model, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
RandomSource random = objects.random;
MeshEmitter[] emitters = objects.emitters;
UniversalMeshEmitter universalEmitter = objects.universalEmitter;
for (MeshEmitter emitter : emitters) {
emitter.prepare(resultConsumer);
}
RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state);
universalEmitter.prepare(defaultLayer);
model = universalEmitter.wrapModel(model);
poseStack.pushPose();
blockRenderer.tesselateBlock(level, model, state, BlockPos.ZERO, poseStack, universalEmitter, false, random, 42L, OverlayTexture.NO_OVERLAY);
poseStack.popPose();
universalEmitter.clear();
for (MeshEmitter emitter : emitters) {
emitter.end();
}
}
public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ResultConsumer resultConsumer) {
if (state.getRenderShape() != RenderShape.MODEL) {
return;
}
bufferSingle(renderDispatcher.getModelRenderer(), level, renderDispatcher.getBlockModel(state), state, poseStack, resultConsumer);
}
public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, boolean renderFluids, ResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
RandomSource random = objects.random;
MeshEmitter[] emitters = objects.emitters;
Reference2ReferenceMap<RenderType, MeshEmitter> emitterMap = objects.emitterMap;
UniversalMeshEmitter universalEmitter = objects.universalEmitter;
TransformingVertexConsumer transformingWrapper = objects.transformingWrapper;
for (MeshEmitter emitter : emitters) {
emitter.prepare(resultConsumer);
}
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
ModelBlockRenderer.enableCaching();
while (posIterator.hasNext()) {
BlockPos pos = posIterator.next();
BlockState state = level.getBlockState(pos);
if (renderFluids) {
FluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty()) {
RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState);
transformingWrapper.prepare(emitterMap.get(renderType).getBuffer(true), 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();
}
}
if (state.getRenderShape() == RenderShape.MODEL) {
long seed = state.getSeed(pos);
BakedModel model = renderDispatcher.getBlockModel(state);
RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state);
universalEmitter.prepare(defaultLayer);
model = universalEmitter.wrapModel(model);
poseStack.pushPose();
poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
blockRenderer.tesselateBlock(level, model, state, pos, poseStack, universalEmitter, 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);
}
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<RenderType, MeshEmitter> emitterMap = new Reference2ReferenceOpenHashMap<>();
public final UniversalMeshEmitter universalEmitter;
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);
}
}
}

View file

@ -0,0 +1,77 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
public final class FabricBakedModelBuilder extends BakedModelBuilder {
public FabricBakedModelBuilder(BakedModel bakedModel) {
super(bakedModel);
}
@Override
public FabricBakedModelBuilder level(BlockAndTintGetter level) {
super.level(level);
return this;
}
@Override
public FabricBakedModelBuilder blockState(BlockState blockState) {
super.blockState(blockState);
return this;
}
@Override
public FabricBakedModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public FabricBakedModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
@Override
public SimpleModel build() {
if (level == null) {
level = VirtualEmptyBlockGetter.INSTANCE;
}
if (blockState == null) {
blockState = Blocks.AIR.defaultBlockState();
}
if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder();
BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), level, bakedModel, blockState, poseStack, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh));
}
});
return new SimpleModel(out.build());
}
}

View file

@ -0,0 +1,66 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
public final class FabricBlockModelBuilder extends BlockModelBuilder {
public FabricBlockModelBuilder(BlockState state) {
super(state);
}
@Override
public FabricBlockModelBuilder level(BlockAndTintGetter level) {
super.level(level);
return this;
}
@Override
public FabricBlockModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public FabricBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
@Override
public SimpleModel build() {
if (level == null) {
level = VirtualEmptyBlockGetter.INSTANCE;
}
if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder();
BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, level, state, poseStack, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh));
}
});
return new SimpleModel(out.build());
}
}

View file

@ -0,0 +1,63 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.VertexView;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
public final class FabricMultiBlockModelBuilder extends MultiBlockModelBuilder {
public FabricMultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions) {
super(level, positions);
}
@Override
public FabricMultiBlockModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public FabricMultiBlockModelBuilder enableFluidRendering() {
super.enableFluidRendering();
return this;
}
@Override
public FabricMultiBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
@Override
public SimpleModel build() {
if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder();
BakedModelBufferer.bufferMultiBlock(ModelUtil.VANILLA_RENDERER, positions.iterator(), level, poseStack, renderFluids, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
VertexView vertexView = new NoOverlayVertexView();
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView);
var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh));
}
});
return new SimpleModel(out.build());
}
}

View file

@ -0,0 +1,57 @@
package com.jozufozu.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;
}
void prepareForGeometry(boolean shade) {
if (!bufferBuilder.building()) {
bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
} else if (shade != currentShade) {
emit();
}
currentShade = shade;
}
void emit() {
var renderedBuffer = bufferBuilder.endOrDiscardIfEmpty();
if (renderedBuffer != null) {
resultConsumer.accept(renderType, currentShade, renderedBuffer);
renderedBuffer.release();
}
}
}

View file

@ -0,0 +1,146 @@
package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
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.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
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;
class UniversalMeshEmitter implements VertexConsumer {
private final Reference2ReferenceMap<RenderType, MeshEmitter> emitterMap;
private final WrapperModel wrapperModel = new WrapperModel();
@UnknownNullability
private RenderType defaultLayer;
@UnknownNullability
private BufferBuilder currentDelegate;
UniversalMeshEmitter(Reference2ReferenceMap<RenderType, MeshEmitter> emitterMap) {
this.emitterMap = emitterMap;
}
public void prepare(RenderType defaultLayer) {
this.defaultLayer = defaultLayer;
}
public void clear() {
wrapperModel.setWrapped(null);
}
public BakedModel wrapModel(BakedModel model) {
wrapperModel.setWrapped(model);
return wrapperModel;
}
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);
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
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);
return this;
}
@Override
public VertexConsumer uv(float u, float v) {
currentDelegate.uv(u, v);
return this;
}
@Override
public VertexConsumer overlayCoords(int u, int v) {
currentDelegate.overlayCoords(u, v);
return this;
}
@Override
public VertexConsumer uv2(int u, int v) {
currentDelegate.uv2(u, v);
return this;
}
@Override
public VertexConsumer normal(float x, float y, float z) {
currentDelegate.normal(x, y, z);
return this;
}
@Override
public void endVertex() {
currentDelegate.endVertex();
}
@Override
public void defaultColor(int red, int green, int blue, int alpha) {
currentDelegate.defaultColor(red, green, blue, alpha);
}
@Override
public void unsetDefaultColor() {
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);
}
@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);
}
@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);
}
private class WrapperModel extends ForwardingBakedModel {
private final RenderContext.QuadTransform quadTransform = quad -> {
UniversalMeshEmitter.this.prepareForGeometry(quad.material());
return true;
};
public void setWrapped(@Nullable BakedModel wrapped) {
this.wrapped = wrapped;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter level, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.pushTransform(quadTransform);
super.emitBlockQuads(level, state, pos, randomSupplier, context);
context.popTransform();
}
}
}

View file

@ -5,6 +5,9 @@ import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.lib.internal.FlwLibXplat; import com.jozufozu.flywheel.lib.internal.FlwLibXplat;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBuilder; import com.jozufozu.flywheel.lib.model.baked.BakedModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.BlockModelBuilder; import com.jozufozu.flywheel.lib.model.baked.BlockModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.FabricBakedModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.FabricBlockModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.FabricMultiBlockModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.MultiBlockModelBuilder; import com.jozufozu.flywheel.lib.model.baked.MultiBlockModelBuilder;
import com.jozufozu.flywheel.lib.util.ShadersModHandler; import com.jozufozu.flywheel.lib.util.ShadersModHandler;
@ -25,17 +28,17 @@ public class FlwLibXplatImpl implements FlwLibXplat {
@Override @Override
public BakedModelBuilder createBakedModelBuilder(BakedModel bakedModel) { public BakedModelBuilder createBakedModelBuilder(BakedModel bakedModel) {
return null; return new FabricBakedModelBuilder(bakedModel);
} }
@Override @Override
public BlockModelBuilder createBlockModelBuilder(BlockState state) { public BlockModelBuilder createBlockModelBuilder(BlockState state) {
return null; return new FabricBlockModelBuilder(state);
} }
@Override @Override
public MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions) { public MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions) {
return null; return new FabricMultiBlockModelBuilder(level, positions);
} }
@Override @Override

View file

@ -5,7 +5,6 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
@ -33,13 +32,13 @@ final class BakedModelBufferer {
private BakedModelBufferer() { private BakedModelBufferer() {
} }
public static void bufferSingle(ModelBlockRenderer blockRenderer, BlockAndTintGetter level, BakedModel model, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, MeshEmitter.ResultConsumer resultConsumer) { public static void bufferSingle(ModelBlockRenderer blockRenderer, BlockAndTintGetter level, BakedModel model, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) { if (poseStack == null) {
poseStack = objects.identityPoseStack; poseStack = objects.identityPoseStack;
} }
RandomSource random = objects.random; RandomSource random = objects.random;
var consumers = objects.emitters; MeshEmitter[] emitters = objects.emitters;
modelData = model.getModelData(level, BlockPos.ZERO, state, modelData); modelData = model.getModelData(level, BlockPos.ZERO, state, modelData);
random.setSeed(42L); random.setSeed(42L);
@ -47,19 +46,19 @@ final class BakedModelBufferer {
for (RenderType renderType : renderTypes) { for (RenderType renderType : renderTypes) {
int layerIndex = renderType.getChunkLayerId(); int layerIndex = renderType.getChunkLayerId();
var consumer = consumers[layerIndex]; MeshEmitter emitter = emitters[layerIndex];
consumer.begin(resultConsumer); emitter.prepare(resultConsumer);
poseStack.pushPose(); poseStack.pushPose();
blockRenderer.tesselateBlock(level, model, state, BlockPos.ZERO, poseStack, consumer, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType); blockRenderer.tesselateBlock(level, model, state, BlockPos.ZERO, poseStack, emitter, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType);
poseStack.popPose(); poseStack.popPose();
consumer.end(); emitter.end();
} }
} }
public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, MeshEmitter.ResultConsumer resultConsumer) { public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter level, BlockState state, @Nullable PoseStack poseStack, ModelData modelData, ResultConsumer resultConsumer) {
if (state.getRenderShape() != RenderShape.MODEL) { if (state.getRenderShape() != RenderShape.MODEL) {
return; return;
} }
@ -67,18 +66,17 @@ final class BakedModelBufferer {
bufferSingle(renderDispatcher.getModelRenderer(), level, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer); bufferSingle(renderDispatcher.getModelRenderer(), level, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer);
} }
public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function<BlockPos, ModelData> modelDataLookup, boolean renderFluids, MeshEmitter.ResultConsumer resultConsumer) { public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iterator<BlockPos> posIterator, BlockAndTintGetter level, @Nullable PoseStack poseStack, Function<BlockPos, ModelData> modelDataLookup, boolean renderFluids, ResultConsumer resultConsumer) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) { if (poseStack == null) {
poseStack = objects.identityPoseStack; poseStack = objects.identityPoseStack;
} }
RandomSource random = objects.random; RandomSource random = objects.random;
MeshEmitter[] emitters = objects.emitters;
TransformingVertexConsumer transformingWrapper = objects.transformingWrapper; TransformingVertexConsumer transformingWrapper = objects.transformingWrapper;
var emitters = objects.emitters; for (MeshEmitter emitter : emitters) {
emitter.prepare(resultConsumer);
for (var emitter : emitters) {
emitter.begin(resultConsumer);
} }
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer(); ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
@ -92,10 +90,10 @@ final class BakedModelBufferer {
FluidState fluidState = state.getFluidState(); FluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty()) { if (!fluidState.isEmpty()) {
RenderType layer = ItemBlockRenderTypes.getRenderLayer(fluidState); RenderType renderType = ItemBlockRenderTypes.getRenderLayer(fluidState);
int layerIndex = layer.getChunkLayerId(); int layerIndex = renderType.getChunkLayerId();
transformingWrapper.prepare(emitters[layerIndex], poseStack); transformingWrapper.prepare(emitters[layerIndex].unwrap(true), poseStack);
poseStack.pushPose(); poseStack.pushPose();
poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF)); poseStack.translate(pos.getX() - (pos.getX() & 0xF), pos.getY() - (pos.getY() & 0xF), pos.getZ() - (pos.getZ() & 0xF));
@ -124,12 +122,11 @@ final class BakedModelBufferer {
} }
ModelBlockRenderer.clearCache(); ModelBlockRenderer.clearCache();
transformingWrapper.clear();
for (var emitter : emitters) { for (MeshEmitter emitter : emitters) {
emitter.end(); emitter.end();
} }
transformingWrapper.clear();
} }
public interface ResultConsumer { public interface ResultConsumer {
@ -140,15 +137,13 @@ final class BakedModelBufferer {
public final PoseStack identityPoseStack = new PoseStack(); public final PoseStack identityPoseStack = new PoseStack();
public final RandomSource random = RandomSource.createNewThreadLocalInstance(); public final RandomSource random = RandomSource.createNewThreadLocalInstance();
public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT];
public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer();
public final ForgeMeshEmitter[] emitters = new ForgeMeshEmitter[CHUNK_LAYER_AMOUNT];
{ {
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
var renderType = CHUNK_LAYERS[layerIndex]; RenderType renderType = CHUNK_LAYERS[layerIndex];
var buffer = new BufferBuilder(renderType.bufferSize()); emitters[layerIndex] = new MeshEmitter(renderType);
emitters[layerIndex] = new ForgeMeshEmitter(buffer, renderType);
} }
} }
} }

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -10,14 +12,17 @@ import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel; import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.MeshEmitter.ResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelData; import net.minecraftforge.client.model.data.ModelData;
public class ForgeBakedModelBuilder extends BakedModelBuilder { public final class ForgeBakedModelBuilder extends BakedModelBuilder {
@Nullable @Nullable
private ModelData modelData; private ModelData modelData;
@ -25,11 +30,36 @@ public class ForgeBakedModelBuilder extends BakedModelBuilder {
super(bakedModel); super(bakedModel);
} }
@Override
public ForgeBakedModelBuilder level(BlockAndTintGetter level) {
super.level(level);
return this;
}
@Override
public ForgeBakedModelBuilder blockState(BlockState blockState) {
super.blockState(blockState);
return this;
}
@Override
public ForgeBakedModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public ForgeBakedModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
public ForgeBakedModelBuilder modelData(ModelData modelData) { public ForgeBakedModelBuilder modelData(ModelData modelData) {
this.modelData = modelData; this.modelData = modelData;
return this; return this;
} }
@Override
public SimpleModel build() { public SimpleModel build() {
if (level == null) { if (level == null) {
level = VirtualEmptyBlockGetter.INSTANCE; level = VirtualEmptyBlockGetter.INSTANCE;
@ -37,16 +67,16 @@ public class ForgeBakedModelBuilder extends BakedModelBuilder {
if (blockState == null) { if (blockState == null) {
blockState = Blocks.AIR.defaultBlockState(); blockState = Blocks.AIR.defaultBlockState();
} }
if (modelData == null) {
modelData = ModelData.EMPTY;
}
if (materialFunc == null) { if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial; materialFunc = ModelUtil::getMaterial;
} }
if (modelData == null) {
modelData = ModelData.EMPTY;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
ResultConsumer resultConsumer = (renderType, shaded, data) -> { BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), level, bakedModel, blockState, poseStack, modelData, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded); Material material = materialFunc.apply(renderType, shaded);
if (material != null) { if (material != null) {
VertexView vertexView = new NoOverlayVertexView(); VertexView vertexView = new NoOverlayVertexView();
@ -54,8 +84,7 @@ public class ForgeBakedModelBuilder extends BakedModelBuilder {
var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded); var mesh = new SimpleMesh(vertexView, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh)); out.add(new Model.ConfiguredMesh(material, mesh));
} }
}; });
BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), level, bakedModel, blockState, poseStack, modelData, resultConsumer);
return new SimpleModel(out.build()); return new SimpleModel(out.build());
} }

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -10,13 +12,15 @@ import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel; import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.MeshEmitter.ResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelData; import net.minecraftforge.client.model.data.ModelData;
public class ForgeBlockModelBuilder extends BlockModelBuilder { public final class ForgeBlockModelBuilder extends BlockModelBuilder {
@Nullable @Nullable
private ModelData modelData; private ModelData modelData;
@ -24,25 +28,44 @@ public class ForgeBlockModelBuilder extends BlockModelBuilder {
super(state); super(state);
} }
@Override
public ForgeBlockModelBuilder level(BlockAndTintGetter level) {
super.level(level);
return this;
}
@Override
public ForgeBlockModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public ForgeBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
public ForgeBlockModelBuilder modelData(ModelData modelData) { public ForgeBlockModelBuilder modelData(ModelData modelData) {
this.modelData = modelData; this.modelData = modelData;
return this; return this;
} }
@Override
public SimpleModel build() { public SimpleModel build() {
if (level == null) { if (level == null) {
level = VirtualEmptyBlockGetter.INSTANCE; level = VirtualEmptyBlockGetter.INSTANCE;
} }
if (modelData == null) {
modelData = ModelData.EMPTY;
}
if (materialFunc == null) { if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial; materialFunc = ModelUtil::getMaterial;
} }
if (modelData == null) {
modelData = ModelData.EMPTY;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
ResultConsumer resultConsumer = (renderType, shaded, data) -> { BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, level, state, poseStack, modelData, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded); Material material = materialFunc.apply(renderType, shaded);
if (material != null) { if (material != null) {
VertexView vertexView = new NoOverlayVertexView(); VertexView vertexView = new NoOverlayVertexView();
@ -50,8 +73,7 @@ public class ForgeBlockModelBuilder extends BlockModelBuilder {
var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded); var mesh = new SimpleMesh(vertexView, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh)); out.add(new Model.ConfiguredMesh(material, mesh));
} }
}; });
BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, level, state, poseStack, modelData, resultConsumer);
return new SimpleModel(out.build()); return new SimpleModel(out.build());
} }

View file

@ -1,21 +0,0 @@
package com.jozufozu.flywheel.lib.model.baked;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
class ForgeMeshEmitter extends MeshEmitter {
ForgeMeshEmitter(BufferBuilder bufferBuilder, RenderType renderType) {
super(bufferBuilder, renderType);
}
// Forge has another putBulkData that we need to override
@Override
public void putBulkData(PoseStack.Pose matrixEntry, BakedQuad quad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
observeQuadAndEmitIfNecessary(quad);
bufferBuilder.putBulkData(matrixEntry, quad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
}
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.model.baked; package com.jozufozu.flywheel.lib.model.baked;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -12,14 +13,15 @@ import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil; import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh; import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.SimpleModel; import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.baked.MeshEmitter.ResultConsumer;
import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraftforge.client.model.data.ModelData; import net.minecraftforge.client.model.data.ModelData;
public class ForgeMultiBlockModelBuilder extends MultiBlockModelBuilder { public final class ForgeMultiBlockModelBuilder extends MultiBlockModelBuilder {
@Nullable @Nullable
private Function<BlockPos, ModelData> modelDataLookup; private Function<BlockPos, ModelData> modelDataLookup;
@ -27,22 +29,41 @@ public class ForgeMultiBlockModelBuilder extends MultiBlockModelBuilder {
super(level, positions); super(level, positions);
} }
@Override
public ForgeMultiBlockModelBuilder poseStack(PoseStack poseStack) {
super.poseStack(poseStack);
return this;
}
@Override
public ForgeMultiBlockModelBuilder enableFluidRendering() {
super.enableFluidRendering();
return this;
}
@Override
public ForgeMultiBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
super.materialFunc(materialFunc);
return this;
}
public ForgeMultiBlockModelBuilder modelDataLookup(Function<BlockPos, ModelData> modelDataLookup) { public ForgeMultiBlockModelBuilder modelDataLookup(Function<BlockPos, ModelData> modelDataLookup) {
this.modelDataLookup = modelDataLookup; this.modelDataLookup = modelDataLookup;
return this; return this;
} }
@Override
public SimpleModel build() { public SimpleModel build() {
if (modelDataLookup == null) {
modelDataLookup = pos -> ModelData.EMPTY;
}
if (materialFunc == null) { if (materialFunc == null) {
materialFunc = ModelUtil::getMaterial; materialFunc = ModelUtil::getMaterial;
} }
if (modelDataLookup == null) {
modelDataLookup = pos -> ModelData.EMPTY;
}
var out = ImmutableList.<Model.ConfiguredMesh>builder(); var out = ImmutableList.<Model.ConfiguredMesh>builder();
ResultConsumer resultConsumer = (renderType, shaded, data) -> { BakedModelBufferer.bufferMultiBlock(ModelUtil.VANILLA_RENDERER, positions.iterator(), level, poseStack, modelDataLookup, renderFluids, (renderType, shaded, data) -> {
Material material = materialFunc.apply(renderType, shaded); Material material = materialFunc.apply(renderType, shaded);
if (material != null) { if (material != null) {
VertexView vertexView = new NoOverlayVertexView(); VertexView vertexView = new NoOverlayVertexView();
@ -50,8 +71,7 @@ public class ForgeMultiBlockModelBuilder extends MultiBlockModelBuilder {
var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded); var mesh = new SimpleMesh(vertexView, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded);
out.add(new Model.ConfiguredMesh(material, mesh)); out.add(new Model.ConfiguredMesh(material, mesh));
} }
}; });
BakedModelBufferer.bufferMultiBlock(ModelUtil.VANILLA_RENDERER, positions.iterator(), level, poseStack, modelDataLookup, renderFluids, resultConsumer);
return new SimpleModel(out.build()); return new SimpleModel(out.build());
} }

View file

@ -0,0 +1,133 @@
package com.jozufozu.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();
}
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!");
}
}