mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-14 04:00:30 +00:00
Fix quad order when rendering turtles upside down
- Reverse quads in our model transformer and when rendering as a block entity. - Correctly recompute normals when the quads have been inverted. Closes #1283
This commit is contained in:
parent
36b9f4ec55
commit
953372b1b7
@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model.turtle;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
import com.mojang.math.Transformation;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
|
||||
* <p>
|
||||
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
|
||||
* handle flipping models upside down.
|
||||
* <p>
|
||||
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
|
||||
*/
|
||||
public final class ModelTransformer {
|
||||
public static final int[] ORDER = new int[]{ 3, 2, 1, 0 };
|
||||
public static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||
|
||||
private final Matrix4f transformation;
|
||||
private final boolean invert;
|
||||
private @Nullable TransformedQuads cache;
|
||||
|
||||
public ModelTransformer(Transformation transformation) {
|
||||
this.transformation = transformation.getMatrix();
|
||||
invert = transformation.getMatrix().determinant() < 0;
|
||||
}
|
||||
|
||||
public List<BakedQuad> transform(List<BakedQuad> quads) {
|
||||
if (quads.isEmpty()) return List.of();
|
||||
|
||||
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||
// so it's not worth being smarter here.
|
||||
var cache = this.cache;
|
||||
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||
|
||||
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||
this.cache = new TransformedQuads(quads, transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private BakedQuad transformQuad(BakedQuad quad) {
|
||||
var inputData = quad.getVertices();
|
||||
var outputData = new int[inputData.length];
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var inStart = STRIDE * i;
|
||||
// Reverse the order of the quads if we're inverting
|
||||
var outStart = STRIDE * (invert ? ORDER[i] : i);
|
||||
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
|
||||
|
||||
// Apply the matrix to our position
|
||||
var inPosStart = inStart + POS_OFFSET;
|
||||
var outPosStart = outStart + POS_OFFSET;
|
||||
|
||||
var x = Float.intBitsToFloat(inputData[inPosStart]);
|
||||
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
|
||||
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
|
||||
|
||||
// Transform the position
|
||||
var pos = new Vector4f(x, y, z, 1);
|
||||
transformation.transformProject(pos);
|
||||
|
||||
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
|
||||
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
|
||||
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
|
||||
}
|
||||
|
||||
var direction = Direction.rotate(transformation, quad.getDirection());
|
||||
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
|
||||
}
|
||||
|
||||
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||
}
|
||||
|
||||
private static int findOffset(VertexFormat format, VertexFormatElement element) {
|
||||
var offset = 0;
|
||||
for (var other : format.getElements()) {
|
||||
if (other == element) return offset / Integer.BYTES;
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ 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;
|
||||
@ -30,6 +31,7 @@ import net.minecraft.resources.ResourceLocation;
|
||||
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;
|
||||
@ -146,16 +148,30 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a block model.
|
||||
*
|
||||
* @param transform The current matrix stack.
|
||||
* @param renderer The buffer to write to.
|
||||
* @param lightmapCoord The current lightmap coordinate.
|
||||
* @param overlayLight The overlay light.
|
||||
* @param model The model to render.
|
||||
* @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) {
|
||||
random.setSeed(0);
|
||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), 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;
|
||||
@ -167,7 +183,50 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
var r = (float) (tint >> 16 & 255) / 255.0F;
|
||||
var g = (float) (tint >> 8 & 255) / 255.0F;
|
||||
var b = (float) (tint & 255) / 255.0F;
|
||||
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
||||
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.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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,84 +4,32 @@
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
|
||||
*
|
||||
* @see ModelTransformer
|
||||
*/
|
||||
public class TransformedBakedModel extends CustomBakedModel {
|
||||
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||
|
||||
private final Matrix4f transformation;
|
||||
private @Nullable TransformedQuads cache;
|
||||
private final ModelTransformer transformation;
|
||||
|
||||
public TransformedBakedModel(BakedModel model, Transformation transformation) {
|
||||
super(model);
|
||||
this.transformation = transformation.getMatrix();
|
||||
this.transformation = new ModelTransformer(transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
|
||||
var cache = this.cache;
|
||||
var quads = wrapped.getQuads(blockState, face, rand);
|
||||
if (quads.isEmpty()) return List.of();
|
||||
|
||||
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||
// so it's not worth being smarter here.
|
||||
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||
|
||||
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||
this.cache = new TransformedQuads(quads, transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private BakedQuad transformQuad(BakedQuad quad) {
|
||||
var vertexData = quad.getVertices().clone();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
// Apply the matrix to our position
|
||||
var start = STRIDE * i + POS_OFFSET;
|
||||
|
||||
var x = Float.intBitsToFloat(vertexData[start]);
|
||||
var y = Float.intBitsToFloat(vertexData[start + 1]);
|
||||
var z = Float.intBitsToFloat(vertexData[start + 2]);
|
||||
|
||||
// Transform the position
|
||||
var pos = new Vector4f(x, y, z, 1);
|
||||
transformation.transformProject(pos);
|
||||
|
||||
vertexData[start] = Float.floatToRawIntBits(pos.x());
|
||||
vertexData[start + 1] = Float.floatToRawIntBits(pos.y());
|
||||
vertexData[start + 2] = Float.floatToRawIntBits(pos.z());
|
||||
}
|
||||
|
||||
return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade());
|
||||
}
|
||||
|
||||
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||
}
|
||||
|
||||
private static int findOffset(VertexFormat format, VertexFormatElement element) {
|
||||
var offset = 0;
|
||||
for (var other : format.getElements()) {
|
||||
if (other == element) return offset / Integer.BYTES;
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
|
||||
return transformation.transform(wrapped.getQuads(blockState, face, rand));
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
@ -12,7 +13,6 @@ import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.client.model.BakedModelWrapper;
|
||||
import net.minecraftforge.client.model.QuadTransformers;
|
||||
import net.minecraftforge.client.model.data.ModelData;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -20,16 +20,15 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
|
||||
*
|
||||
* @see ModelTransformer
|
||||
*/
|
||||
public class TransformedBakedModel extends BakedModelWrapper<BakedModel> {
|
||||
private final Transformation transformation;
|
||||
private final boolean invert;
|
||||
private @Nullable TransformedQuads cache;
|
||||
private final ModelTransformer transformation;
|
||||
|
||||
public TransformedBakedModel(BakedModel model, Transformation transformation) {
|
||||
super(model);
|
||||
this.transformation = transformation;
|
||||
invert = transformation.getNormalMatrix().determinant() < 0;
|
||||
this.transformation = new ModelTransformer(transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -39,19 +38,6 @@ public class TransformedBakedModel extends BakedModelWrapper<BakedModel> {
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) {
|
||||
var cache = this.cache;
|
||||
var quads = originalModel.getQuads(state, side, rand, extraData, renderType);
|
||||
if (quads.isEmpty()) return List.of();
|
||||
|
||||
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||
// so it's not worth being smarter here.
|
||||
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||
|
||||
var transformed = QuadTransformers.applying(transformation).process(quads);
|
||||
this.cache = new TransformedQuads(quads, transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||
return transformation.transform(originalModel.getQuads(state, side, rand, extraData, renderType));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user