-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite facade rendering #945
base: dev/1.21.1
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,105 @@ | ||
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.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) { | ||
Map<BlockPos, BlockState> facades = new Object2ObjectOpenHashMap<>(); | ||
|
||
Set<BlockPos> 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); | ||
for (BlockPos entry : blockList) { | ||
facades.put(entry, ConduitBundleBlockEntity.FACADES.get(entry.asLong())); | ||
} | ||
|
||
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)) { | ||
|
||
for (var renderType : model.getRenderTypes(entry.getValue(), random, ModelData.EMPTY)) { | ||
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.EMPTY, renderType); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here (and above as well) |
||
} | ||
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; | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -71,23 +68,9 @@ public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction | |
|
||
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() | ||
|
@@ -340,12 +323,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 ChunkRenderTypeSet.of(RenderType.cutout()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be cached in a static field to avoid allocation. |
||
} | ||
|
||
@Override | ||
|
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 |
---|---|---|
|
@@ -29,6 +29,9 @@ | |
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.objects.ObjectOpenHashSet; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
|
@@ -43,6 +46,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; | ||
|
@@ -79,7 +83,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<Set<BlockPos>> CHUNK_FACADES = new Long2ObjectOpenHashMap<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a small thing, can this be a |
||
|
||
private final ConduitShape shape = new ConduitShape(); | ||
|
||
|
@@ -120,9 +125,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 ObjectOpenHashSet<>()) | ||
.add(worldPosition); | ||
} else { | ||
FACADES.remove(worldPosition); | ||
FACADES.remove(worldPosition.asLong()); | ||
Set<BlockPos> chunkList = CHUNK_FACADES.getOrDefault(SectionPos.asLong(worldPosition), null); | ||
if (chunkList != null) { | ||
chunkList.remove(worldPosition); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -217,7 +228,8 @@ public void onChunkUnloaded() { | |
ConduitSavedData savedData = ConduitSavedData.get(serverLevel); | ||
bundle.getConduits().forEach(type -> onChunkUnloaded(savedData, type)); | ||
} else { | ||
FACADES.remove(worldPosition); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just a heads up, I added another |
||
CHUNK_FACADES.remove(SectionPos.asLong(worldPosition)); | ||
FACADES.remove(worldPosition.asLong()); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
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; | ||
|
||
@Mixin(BlockRenderDispatcher.class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we don't have many mixins in enderio, do you mind writing a short explanation what this does/fixes? I think this is for the breaking overlay, but want to make sure. |
||
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 |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"required" : true, | ||
"package" : "com.enderio.conduits.mixin", | ||
"compatibilityLevel" : "JAVA_17", | ||
"client" : [ | ||
"BlockRenderDispatcherMixin" | ||
], | ||
"minVersion" : "0.8" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The map allocation should be moved below the
if (blockList == null)
check to avoid allocating a redundant map for sections without facades (i.e. most sections, including empty ones).