Skip to content

Commit

Permalink
feat: Rewrite facade rendering for improved compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
IMS212 authored Mar 2, 2025
1 parent 69b0bd5 commit 397c648
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,83 +1,110 @@
package com.enderio.conduits.client;

import com.enderio.conduits.client.model.conduit.facades.FacadeHelper;
import com.enderio.conduits.common.conduit.block.ConduitBundleBlock;
import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.mojang.blaze3d.vertex.VertexConsumer;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.util.FastColor;
import net.minecraft.core.SectionPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
import net.neoforged.neoforge.client.event.AddSectionGeometryEvent;
import net.neoforged.neoforge.client.model.data.ModelData;
import net.neoforged.neoforge.client.model.pipeline.VertexConsumerWrapper;

@EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME, value = Dist.CLIENT)
public class ConduitFacadeRendering {

private static final ThreadLocal<RandomSource> RANDOM = ThreadLocal
.withInitial(() -> new SingleThreadedRandomSource(42L));

@SubscribeEvent
static void renderFacade(RenderLevelStageEvent event) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRIPWIRE_BLOCKS || FacadeHelper.areFacadesVisible()) {
static void renderFacade(AddSectionGeometryEvent event) {
LongSet blockList = ConduitBundleBlockEntity.CHUNK_FACADES
.getOrDefault(SectionPos.asLong(event.getSectionOrigin()), null);

if (blockList == null) {
return;
}
for (Map.Entry<BlockPos, BlockState> entry : ConduitBundleBlockEntity.FACADES.entrySet()) {
ClientLevel level = Minecraft.getInstance().level;
if (!level.isLoaded(entry.getKey())) {
return;
}
if (level.getBlockState(entry.getKey()).getBlock() instanceof ConduitBundleBlock) {
if (entry.getValue() == null) {
continue;
}

var baseConsumer = Minecraft.getInstance()
.renderBuffers()
.bufferSource()
.getBuffer(Sheets.translucentCullBlockSheet());
var wrappedConsumer = new VertexConsumerWrapper(baseConsumer) {
@Override
public VertexConsumer setColor(int r, int g, int b, int a) {
super.setColor(r, g, b, 85);
return this;
}
};

var cameraPos = event.getCamera().getPosition();
event.getPoseStack().pushPose();
event.getPoseStack()
.translate(entry.getKey().getX() - cameraPos.x, entry.getKey().getY() - cameraPos.y,
entry.getKey().getZ() - cameraPos.z);
Map<BlockPos, BlockState> facades = new Object2ObjectOpenHashMap<>();

for (long entry : blockList) {
facades.put(BlockPos.of(entry), ConduitBundleBlockEntity.FACADES.get(entry));
}

if (facades.isEmpty())
return;

event.addRenderer(new FacadeRenderer(facades, FacadeHelper.areFacadesVisible()));
}

private static class FacadeRenderer implements AddSectionGeometryEvent.AdditionalSectionRenderer {
private final Map<BlockPos, BlockState> facades;
private final boolean opaque;

public FacadeRenderer(Map<BlockPos, BlockState> facades, boolean opaque) {
this.facades = facades;
this.opaque = opaque;
}

@Override
public void render(AddSectionGeometryEvent.SectionRenderingContext context) {
VertexConsumerWrapper wrapper = opaque ? null : new AlphaWrapper(context);

RandomSource random = RANDOM.get();

for (Map.Entry<BlockPos, BlockState> entry : facades.entrySet()) {
context.getPoseStack().pushPose();
context.getPoseStack()
.translate(entry.getKey().getX() & 15, entry.getKey().getY() & 15, entry.getKey().getZ() & 15);

var state = entry.getValue();
var pos = entry.getKey();

random.setSeed(42L);

var model = Minecraft.getInstance()
.getModelManager()
.getBlockModelShaper()
.getBlockModel(entry.getValue());
int color = Minecraft.getInstance().getBlockColors().getColor(entry.getValue(), level, entry.getKey());
for (var renderType : model.getRenderTypes(entry.getValue(), RandomSource.create(), ModelData.EMPTY)) {

var modelData = context.getRegion().getModelData(pos);

modelData = model.getModelData(context.getRegion(), pos, state, modelData);

for (var renderType : model.getRenderTypes(entry.getValue(), random, modelData)) {
VertexConsumer consumer = wrapper == null ? context.getOrCreateChunkBuffer(renderType) : wrapper;
Minecraft.getInstance()
.getBlockRenderer()
.getModelRenderer()
.renderModel(event.getPoseStack().last(), wrappedConsumer, entry.getValue(), model,
FastColor.ARGB32.red(color) / 255.0F, FastColor.ARGB32.green(color) / 255.0F,
FastColor.ARGB32.blue(color) / 255.0F,
LightTexture.pack(level.getBrightness(LightLayer.BLOCK, entry.getKey()),
level.getBrightness(LightLayer.SKY, entry.getKey())),
OverlayTexture.NO_OVERLAY,
model.getModelData(level, entry.getKey(), entry.getValue(), ModelData.EMPTY),
renderType);
.tesselateBlock(context.getRegion(), model, state, pos, context.getPoseStack(), consumer,
true, random, 42L, OverlayTexture.NO_OVERLAY, modelData, renderType);
}
Minecraft.getInstance().renderBuffers().bufferSource().endBatch(Sheets.translucentCullBlockSheet());
event.getPoseStack().popPose();

context.getPoseStack().popPose();
}
}

private static class AlphaWrapper extends VertexConsumerWrapper {
public AlphaWrapper(AddSectionGeometryEvent.SectionRenderingContext context) {
super(context.getOrCreateChunkBuffer(RenderType.translucent()));
}

@Override
public VertexConsumer setColor(int r, int g, int b, int a) {
super.setColor(r, g, b, 85);
return this;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import com.enderio.conduits.api.ConduitNode;
import com.enderio.conduits.api.facade.FacadeType;
import com.enderio.conduits.api.model.ConduitCoreModelModifier;
import com.enderio.conduits.client.ConduitFacadeColor;
import com.enderio.conduits.client.model.conduit.facades.FacadeHelper;
import com.enderio.conduits.client.model.conduit.modifier.ConduitCoreModelModifiers;
import com.enderio.conduits.common.Area;
Expand All @@ -34,7 +33,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
Expand All @@ -50,7 +48,6 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import net.neoforged.neoforge.client.ChunkRenderTypeSet;
Expand All @@ -65,29 +62,17 @@

public class ConduitBlockModel implements IDynamicBakedModel {

private static final ChunkRenderTypeSet CUTOUT_SET = ChunkRenderTypeSet.of(RenderType.cutout());

@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand,
ModelData extraData, @Nullable RenderType renderType) {

List<BakedQuad> quads = new ArrayList<>();
ConduitBundle conduitBundle = extraData.get(ConduitBundleBlockEntity.BUNDLE_MODEL_PROPERTY);
ModelData data = extraData.get(ConduitBundleBlockEntity.FACADE_MODEL_DATA);

if (conduitBundle != null) {
if (FacadeHelper.areFacadesVisible()) {
IQuadTransformer transformer = quad -> quad.tintIndex = ConduitFacadeColor
.moveTintIndex(quad.getTintIndex());
Optional<Block> facadeOpt = conduitBundle.facade();
if (facadeOpt.isPresent()) {
BlockState facade = facadeOpt.get().defaultBlockState();
var model = Minecraft.getInstance().getBlockRenderer().getBlockModel(facade);
var facadeQuads = model.getQuads(facade, side, rand, data, renderType);

if (renderType != null && model.getRenderTypes(facade, rand, data).contains(renderType)) {
quads.addAll(transformer.process(facadeQuads));
}
}

// If the facade should hide the conduits, escape early.
if (conduitBundle.hasFacade()) {
boolean areConduitsHidden = conduitBundle.facadeType()
Expand Down Expand Up @@ -340,12 +325,7 @@ public ItemOverrides getOverrides() {
@Override
public ChunkRenderTypeSet getRenderTypes(@NotNull BlockState state, @NotNull RandomSource rand,
@NotNull ModelData data) {
ChunkRenderTypeSet facadeRenderTypes = data.get(ConduitBundleBlockEntity.FACADE_RENDERTYPE);
ChunkRenderTypeSet renderTypes = ChunkRenderTypeSet.of(RenderType.cutout());
if (facadeRenderTypes != null) {
renderTypes = ChunkRenderTypeSet.union(renderTypes, facadeRenderTypes);
}
return renderTypes;
return CUTOUT_SET;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package com.enderio.conduits.client.model.conduit.facades;

import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.core.SectionPos;

// TODO: In future, support hiding specific conduit types too.
public class FacadeHelper {

private static boolean FACADES_VISIBLE = true;

public static void setFacadesVisible(boolean visible) {
if (visible != FACADES_VISIBLE) {
RenderSystem.recordRenderCall(() -> {
ConduitBundleBlockEntity.CHUNK_FACADES.keySet().forEach((section) -> {
Minecraft.getInstance().levelRenderer.setSectionDirty(SectionPos.x(section), SectionPos.y(section),
SectionPos.z(section));
});
});
}

FACADES_VISIBLE = visible;
}

public static boolean areFacadesVisible() {
return FACADES_VISIBLE;
}

public static void rebuildChunkMeshes() {
var minecraft = Minecraft.getInstance();

if (minecraft.levelRenderer.viewArea == null) {
return;
}

for (var section : minecraft.levelRenderer.viewArea.sections) {
section.setDirty(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public static void onEquipmentChanged(LivingEquipmentChangeEvent event) {
ItemStack offHand = event.getEntity().getItemBySlot(EquipmentSlot.OFFHAND);
FacadeHelper.setFacadesVisible(
!mainHand.is(EIOTags.Items.HIDE_FACADES) && !offHand.is(EIOTags.Items.HIDE_FACADES));

FacadeHelper.rebuildChunkMeshes();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
import com.enderio.core.common.blockentity.EnderBlockEntity;
import dev.gigaherz.graph3.Graph;
import dev.gigaherz.graph3.GraphObject;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand All @@ -43,6 +48,7 @@
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
Expand Down Expand Up @@ -79,7 +85,8 @@ public class ConduitBundleBlockEntity extends EnderBlockEntity {
public static final String CONDUIT_INV_KEY = "ConduitInv";

@UseOnly(LogicalSide.CLIENT)
public static final Map<BlockPos, BlockState> FACADES = new HashMap<>();
public static final Long2ObjectMap<BlockState> FACADES = new Long2ObjectOpenHashMap<>();
public static final Long2ObjectMap<LongSet> CHUNK_FACADES = new Long2ObjectOpenHashMap<>();

private final ConduitShape shape = new ConduitShape();

Expand Down Expand Up @@ -120,9 +127,15 @@ public void updateClient() {
requestModelDataUpdate();
level.setBlocksDirty(getBlockPos(), Blocks.AIR.defaultBlockState(), getBlockState());
if (bundle.hasFacade()) {
FACADES.put(worldPosition, bundle.facade().get().defaultBlockState());
FACADES.put(worldPosition.asLong(), bundle.facade().get().defaultBlockState());
CHUNK_FACADES.computeIfAbsent(SectionPos.asLong(worldPosition), p -> new LongOpenHashSet())
.add(worldPosition.asLong());
} else {
FACADES.remove(worldPosition);
FACADES.remove(worldPosition.asLong());
LongSet chunkList = CHUNK_FACADES.getOrDefault(SectionPos.asLong(worldPosition), null);
if (chunkList != null) {
chunkList.remove(worldPosition.asLong());
}
}
}
}
Expand Down Expand Up @@ -217,7 +230,8 @@ public void onChunkUnloaded() {
ConduitSavedData savedData = ConduitSavedData.get(serverLevel);
bundle.getConduits().forEach(type -> onChunkUnloaded(savedData, type));
} else {
FACADES.remove(worldPosition);
CHUNK_FACADES.remove(SectionPos.asLong(worldPosition));
FACADES.remove(worldPosition.asLong());
}
}

Expand All @@ -231,7 +245,7 @@ private void onChunkUnloaded(ConduitSavedData savedData, Holder<Conduit<?>> cond
public void setRemoved() {
super.setRemoved();
if (level != null && level.isClientSide) {
FACADES.remove(worldPosition);
FACADES.remove(worldPosition.asLong());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.enderio.conduits.mixin;

import com.enderio.conduits.common.conduit.block.ConduitBundleBlockEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

/**
* This fixes the block breaking overlay when a facade is attached to use the model of the facade. Without this, it will use the model of the conduit, and be invisible in most cases.
*/
@Mixin(BlockRenderDispatcher.class)
public class BlockRenderDispatcherMixin {
@WrapOperation(method = "renderBreakingTexture(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/BlockAndTintGetter;Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/neoforged/neoforge/client/model/data/ModelData;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/block/BlockModelShaper;getBlockModel(Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/client/resources/model/BakedModel;"))
public BakedModel enderio$checkFacades(BlockModelShaper instance, BlockState state, Operation<BakedModel> original,
BlockState localState, BlockPos pos, BlockAndTintGetter level) {
BlockState facadeState = ConduitBundleBlockEntity.FACADES.getOrDefault(pos.asLong(), null);

return original.call(instance, facadeState == null ? state : facadeState);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ description="The conduits module for Ender IO"
displayURL="https://enderio.com/"
logoFile="logo.png"

[[mixins]]
config="enderioconduits.mixins.json"

[[dependencies.enderio_conduits]]
modId="minecraft"
type="required"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"required" : true,
"package" : "com.enderio.conduits.mixin",
"compatibilityLevel" : "JAVA_17",
"client" : [
"BlockRenderDispatcherMixin"
],
"minVersion" : "0.8"
}
Loading

0 comments on commit 397c648

Please sign in to comment.