Use platform-specific hooks for rendering BakedModels

This commit is contained in:
Jonathan Coates 2023-07-22 23:52:32 +01:00
parent 42d5771e8e
commit cebdfce06b
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 182 additions and 85 deletions

View File

@ -4,8 +4,13 @@
package dan200.computercraft.client.platform;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import javax.annotation.Nullable;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
@ -18,4 +23,16 @@ static ClientPlatformHelper get() {
* @param message The message to send.
*/
void sendToServer(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.
*
* @param transform The current matrix transformation to apply.
* @param buffers The current pool of render buffers.
* @param model The model to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
}

View File

@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
/**
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
*/
public final class ModelRenderer {
private ModelRenderer() {
}
/**
* Render a list of {@linkplain BakedQuad quads} to a buffer.
* <p>
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
* but supports inverted quads (i.e. those with a negative scale).
*
* @param transform The current matrix transformation to apply.
* @param buffer The buffer to draw to.
* @param quads The quads to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
if (inverted) {
putBulkQuadInvert(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
} else {
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
}
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} for
* when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
*/
private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var vector = new Vector4f();
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
var vertices = quad.getVertices();
for (var vertex : ModelTransformer.INVERSE_ORDER) {
var i = vertex * ModelTransformer.STRIDE;
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
matrix.transform(x, y, z, 1, vector);
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
vector.x(), vector.y(), vector.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normalX, normalY, normalZ
);
}
}
}

View File

@ -5,23 +5,18 @@
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import com.mojang.math.Transformation;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
@ -31,10 +26,8 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
@ -109,23 +102,22 @@ public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack trans
var family = turtle.getFamily();
var overlay = turtle.getOverlay();
var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (overlayModel != null) {
renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
}
// Render the upgrades
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
transform.popPose();
}
private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
var upgrade = turtle.getUpgrade(side);
if (upgrade == null) return;
transform.pushPose();
@ -137,15 +129,15 @@ private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lig
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
pushPoseFromTransformation(transform, model.getMatrix());
renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
transform.popPose();
}
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
/**
@ -159,75 +151,8 @@ private void renderModel(PoseStack transform, VertexConsumer renderer, int light
* @param tints Tints for the quads, as an array of RGB values.
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
*/
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
for (var facing : DirectionUtil.FACINGS) {
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
}
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
}
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
if (inverted) {
putBulkQuadInvert(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
} else {
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
}
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} for
* when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
*/
private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
var vertices = quad.getVertices();
for (var vertex : ModelTransformer.INVERSE_ORDER) {
var i = vertex * ModelTransformer.STRIDE;
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
var transformed = matrix.transform(new Vector4f(x, y, z, 1));
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
transformed.x(), transformed.y(), transformed.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normal.x(), normal.y(), normal.z()
);
}
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
}
private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {

View File

@ -5,17 +5,28 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.model.FoiledModel;
import dan200.computercraft.client.render.ModelRenderer;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import javax.annotation.Nullable;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
@Override
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
Minecraft.getInstance().player.connection.send(NetworkHandler.encodeServer(message));
@ -31,4 +42,17 @@ public BakedModel getModel(ModelManager manager, ResourceLocation location) {
public BakedModel createdFoiledModel(BakedModel model) {
return new FoiledModel(model);
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
// Unfortunately we can't call Fabric's emitItemQuads here, as there's no way to obtain a RenderContext via the
// API. Instead, we special case our FoiledModel, and just render everything else normally.
var buffer = ItemRenderer.getFoilBuffer(buffers, Sheets.translucentCullBlockSheet(), true, model instanceof FoiledModel);
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var face = ModelHelper.faceFromIndex(faceIdx);
random.setSeed(42);
ModelRenderer.renderQuads(transform, buffer, model.getQuads(null, face, random), lightmapCoord, overlayLight, tints);
}
}
}

View File

@ -5,16 +5,29 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.model.FoiledModel;
import dan200.computercraft.client.render.ModelRenderer;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.model.data.ModelData;
import javax.annotation.Nullable;
import java.util.Arrays;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
private static final Direction[] directions = Arrays.copyOf(Direction.values(), 7);
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
return manager.getModel(location);
@ -29,4 +42,16 @@ public BakedModel createdFoiledModel(BakedModel model) {
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
NetworkHandler.sendToServer(message);
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
for (var renderType : model.getRenderTypes(ItemStack.EMPTY, true)) {
var buffer = buffers.getBuffer(renderType);
for (var face : directions) {
random.setSeed(42);
var quads = model.getQuads(null, face, random, ModelData.EMPTY, renderType);
ModelRenderer.renderQuads(transform, buffer, quads, lightmapCoord, overlayLight, tints);
}
}
}
}