Fix virtual model rendering

- Add various utility classes to allow FRAPI-compatible virtual
rendering
- Fix PartialModel using field instead of getter method
This commit is contained in:
PepperCode1 2021-12-21 23:37:52 -08:00
parent 1ee11c0af9
commit 09f3c495e9
8 changed files with 252 additions and 12 deletions

View file

@ -28,6 +28,13 @@ repositories {
maven { maven {
url 'https://maven.parchmentmc.org/' url 'https://maven.parchmentmc.org/'
} }
maven {
name 'Modrinth'
url 'https://api.modrinth.com/maven'
}
maven {
url 'https://maven.vram.io'
}
} }
dependencies { dependencies {
@ -44,6 +51,8 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}"
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
modCompileOnly 'maven.modrinth:indium:1.0.2-alpha1+mc1.18'
modCompileOnly 'io.vram:frex-fabric-mc118:6.0.229'
//implementation 'org.joml:joml:1.10.1' //implementation 'org.joml:joml:1.10.1'
} }

View file

@ -44,7 +44,7 @@ public class PartialModel {
public static void onModelRegistry(ResourceManager manager, Consumer<ResourceLocation> out) { public static void onModelRegistry(ResourceManager manager, Consumer<ResourceLocation> out) {
for (PartialModel partial : ALL) for (PartialModel partial : ALL)
out.accept(partial.modelLocation); out.accept(partial.getLocation());
tooLate = true; tooLate = true;
} }

View file

@ -21,6 +21,7 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
// TODO Fabric
public class BakedModelModel implements Model { public class BakedModelModel implements Model {
// DOWN, UP, NORTH, SOUTH, WEST, EAST, null // DOWN, UP, NORTH, SOUTH, WEST, EAST, null
private static final Direction[] dirs; private static final Direction[] dirs;
@ -44,7 +45,6 @@ public class BakedModelModel implements Model {
for (Direction dir : dirs) { for (Direction dir : dirs) {
random.setSeed(42); random.setSeed(42);
// TODO
List<BakedQuad> quads = model.getQuads(null, dir, random/*, VirtualEmptyModelData.INSTANCE*/); List<BakedQuad> quads = model.getQuads(null, dir, random/*, VirtualEmptyModelData.INSTANCE*/);
numQuads += quads.size(); numQuads += quads.size();
@ -69,7 +69,6 @@ public class BakedModelModel implements Model {
for (Direction dir : dirs) { for (Direction dir : dirs) {
random.setSeed(42); random.setSeed(42);
// TODO
List<BakedQuad> quads = model.getQuads(null, dir, random/*, VirtualEmptyModelData.INSTANCE*/); List<BakedQuad> quads = model.getQuads(null, dir, random/*, VirtualEmptyModelData.INSTANCE*/);
for (BakedQuad bakedQuad : quads) { for (BakedQuad bakedQuad : quads) {

View file

@ -4,6 +4,9 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Random; import java.util.Random;
import com.jozufozu.flywheel.fabric.model.CullingBakedModel;
import com.jozufozu.flywheel.fabric.model.DefaultLayerFilteringBakedModel;
import com.jozufozu.flywheel.fabric.model.LayerFilteringBakedModel;
import com.jozufozu.flywheel.util.Lazy; import com.jozufozu.flywheel.util.Lazy;
import com.jozufozu.flywheel.util.VirtualEmptyBlockGetter; import com.jozufozu.flywheel.util.VirtualEmptyBlockGetter;
import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferBuilder;
@ -12,7 +15,6 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.BlockModelShaper; import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.ModelBlockRenderer; import net.minecraft.client.renderer.block.ModelBlockRenderer;
@ -25,7 +27,6 @@ import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
// TODO
public class ModelUtil { public class ModelUtil {
private static final Lazy<ModelBlockRenderer> MODEL_RENDERER = Lazy.of(() -> new ModelBlockRenderer(Minecraft.getInstance().getBlockColors())); private static final Lazy<ModelBlockRenderer> MODEL_RENDERER = Lazy.of(() -> new ModelBlockRenderer(Minecraft.getInstance().getBlockColors()));
@ -50,8 +51,9 @@ public class ModelUtil {
// .collect(Collectors.toList()); // .collect(Collectors.toList());
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
model = DefaultLayerFilteringBakedModel.wrap(model);
blockRenderer.tesselateBlock(VirtualEmptyBlockGetter.INSTANCE, model, referenceState, BlockPos.ZERO, ms, builder, blockRenderer.tesselateBlock(VirtualEmptyBlockGetter.INSTANCE, model, referenceState, BlockPos.ZERO, ms, builder,
true, new Random(), 42, OverlayTexture.NO_OVERLAY); false, new Random(), 42, OverlayTexture.NO_OVERLAY);
builder.end(); builder.end();
return builder; return builder;
} }
@ -65,27 +67,25 @@ public class ModelUtil {
BufferBuilder builder = new BufferBuilder(512); BufferBuilder builder = new BufferBuilder(512);
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
// ForgeHooksClient.setRenderType(layer);
ModelBlockRenderer.enableCaching(); ModelBlockRenderer.enableCaching();
for (StructureTemplate.StructureBlockInfo info : blocks) { for (StructureTemplate.StructureBlockInfo info : blocks) {
BlockState state = info.state; BlockState state = info.state;
if (state.getRenderShape() != RenderShape.MODEL) if (state.getRenderShape() != RenderShape.MODEL)
continue; continue;
// if (!ItemBlockRenderTypes.canRenderInLayer(state, layer))
if (ItemBlockRenderTypes.getChunkRenderType(state) != layer)
continue;
BlockPos pos = info.pos; BlockPos pos = info.pos;
ms.pushPose(); ms.pushPose();
ms.translate(pos.getX(), pos.getY(), pos.getZ()); ms.translate(pos.getX(), pos.getY(), pos.getZ());
modelRenderer.tesselateBlock(renderWorld, blockModels.getBlockModel(state), state, pos, ms, builder, BakedModel model = blockModels.getBlockModel(state);
model = CullingBakedModel.wrap(model);
model = LayerFilteringBakedModel.wrap(model, layer);
modelRenderer.tesselateBlock(renderWorld, model, state, pos, ms, builder,
true, random, 42, OverlayTexture.NO_OVERLAY); true, random, 42, OverlayTexture.NO_OVERLAY);
ms.popPose(); ms.popPose();
} }
ModelBlockRenderer.clearCache(); ModelBlockRenderer.clearCache();
// ForgeHooksClient.setRenderType(null);
builder.end(); builder.end();
return builder; return builder;

View file

@ -0,0 +1,61 @@
package com.jozufozu.flywheel.fabric.model;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
public class CullingBakedModel extends ForwardingBakedModel {
private static final ThreadLocal<CullingBakedModel> THREAD_LOCAL = ThreadLocal.withInitial(CullingBakedModel::new);
protected int completionFlags = 0;
protected int resultFlags = 0;
protected final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
public static BakedModel wrap(BakedModel model) {
if (!FabricModelUtil.FREX_LOADED && !((FabricBakedModel) model).isVanillaAdapter()) {
CullingBakedModel wrapper = THREAD_LOCAL.get();
wrapper.wrapped = model;
return wrapper;
}
return model;
}
protected CullingBakedModel() {
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
completionFlags = 0;
resultFlags = 0;
context.pushTransform(quad -> {
Direction cullFace = quad.cullFace();
if (cullFace != null) {
int mask = 1 << cullFace.ordinal();
if ((completionFlags & mask) == 0) {
completionFlags |= mask;
if (Block.shouldRenderFace(state, blockView, pos, cullFace, mutablePos.setWithOffset(pos, cullFace))) {
resultFlags |= mask;
return true;
} else {
return false;
}
} else {
return (resultFlags & mask) != 0;
}
}
return true;
});
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
context.popTransform();
}
}

View file

@ -0,0 +1,49 @@
package com.jozufozu.flywheel.fabric.model;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
public class DefaultLayerFilteringBakedModel extends ForwardingBakedModel {
private static final ThreadLocal<DefaultLayerFilteringBakedModel> THREAD_LOCAL = ThreadLocal.withInitial(DefaultLayerFilteringBakedModel::new);
public static BakedModel wrap(BakedModel model) {
if (!((FabricBakedModel) model).isVanillaAdapter()) {
DefaultLayerFilteringBakedModel wrapper = THREAD_LOCAL.get();
wrapper.wrapped = model;
return wrapper;
}
return model;
}
protected DefaultLayerFilteringBakedModel() {
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
context.pushTransform(DefaultLayerFilteringBakedModel::hasDefaultBlendMode);
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
context.popTransform();
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
context.pushTransform(DefaultLayerFilteringBakedModel::hasDefaultBlendMode);
super.emitItemQuads(stack, randomSupplier, context);
context.popTransform();
}
public static boolean hasDefaultBlendMode(QuadView quad) {
return FabricModelUtil.getBlendMode(quad.material()) == BlendMode.DEFAULT;
}
}

View file

@ -0,0 +1,67 @@
package com.jozufozu.flywheel.fabric.model;
import java.lang.reflect.Field;
import com.jozufozu.flywheel.Flywheel;
import io.vram.frex.api.material.MaterialConstants;
import io.vram.frex.fabric.compat.FabricMaterial;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.loader.api.FabricLoader;
public class FabricModelUtil {
public static final boolean INDIUM_LOADED = FabricLoader.getInstance().isModLoaded("indium");
public static final boolean FREX_LOADED = FabricLoader.getInstance().isModLoaded("frex");
private static final BlendModeGetter BLEND_MODE_GETTER = createBlendModeGetter();
private static BlendModeGetter createBlendModeGetter() {
if (FREX_LOADED) {
try {
Field frexMaterialField = FabricMaterial.class.getDeclaredField("wrapped");
frexMaterialField.setAccessible(true);
return material -> {
try {
io.vram.frex.api.material.RenderMaterial frexMaterial = (io.vram.frex.api.material.RenderMaterial) frexMaterialField.get(material);
return switch (frexMaterial.preset()) {
case MaterialConstants.PRESET_DEFAULT -> BlendMode.DEFAULT;
case MaterialConstants.PRESET_SOLID -> BlendMode.SOLID;
case MaterialConstants.PRESET_CUTOUT_MIPPED -> BlendMode.CUTOUT_MIPPED;
case MaterialConstants.PRESET_CUTOUT -> BlendMode.CUTOUT;
case MaterialConstants.PRESET_TRANSLUCENT -> BlendMode.TRANSLUCENT;
case MaterialConstants.PRESET_NONE -> {
if (frexMaterial.transparency() != MaterialConstants.TRANSPARENCY_NONE) {
yield BlendMode.TRANSLUCENT;
} else if (frexMaterial.cutout() == MaterialConstants.CUTOUT_NONE) {
yield BlendMode.SOLID;
} else {
yield frexMaterial.unmipped() ? BlendMode.CUTOUT : BlendMode.CUTOUT_MIPPED;
}
}
default -> BlendMode.DEFAULT;
};
} catch (Exception e) {
}
return BlendMode.DEFAULT;
};
} catch (Exception e) {
Flywheel.log.error("Detected FREX but failed to load wrapper field.", e);
return material -> BlendMode.DEFAULT;
}
} else if (INDIUM_LOADED) {
return material -> ((link.infra.indium.renderer.RenderMaterialImpl) material).blendMode(0);
} else {
return material -> ((RenderMaterialImpl) material).blendMode(0);
}
}
public static BlendMode getBlendMode(RenderMaterial material) {
return BLEND_MODE_GETTER.getBlendMode(material);
}
private interface BlendModeGetter {
BlendMode getBlendMode(RenderMaterial material);
}
}

View file

@ -0,0 +1,55 @@
package com.jozufozu.flywheel.fabric.model;
import java.util.Random;
import java.util.function.Supplier;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
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;
public class LayerFilteringBakedModel extends ForwardingBakedModel {
private static final ThreadLocal<LayerFilteringBakedModel> THREAD_LOCAL = ThreadLocal.withInitial(LayerFilteringBakedModel::new);
protected RenderType targetLayer;
public static BakedModel wrap(BakedModel model, RenderType layer) {
LayerFilteringBakedModel wrapper = THREAD_LOCAL.get();
wrapper.wrapped = model;
wrapper.targetLayer = layer;
return wrapper;
}
protected LayerFilteringBakedModel() {
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
RenderType defaultLayer = ItemBlockRenderTypes.getChunkRenderType(state);
if (((FabricBakedModel) wrapped).isVanillaAdapter()) {
if (defaultLayer == targetLayer) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
}
} else {
context.pushTransform(quad -> {
RenderType quadLayer = FabricModelUtil.getBlendMode(quad.material()).blockRenderLayer;
if (quadLayer == null) {
quadLayer = defaultLayer;
}
return quadLayer == targetLayer;
});
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
context.popTransform();
}
}
}