mirror of
https://github.com/GTNewHorizons/GTNHLib.git
synced 2026-11-14 01:14:15 +00:00
Honor VertexFormat for WavefrontVBOBuilder (#433)
This commit is contained in:
1 parent
6450ac4cd1
commit
9e79aedeab
9 files changed
+443
-16
No files matched your search
+77
-14
@@ -1,6 +1,9 @@
|
||||
|
||||
package com.gtnewhorizon.gtnhlib.client.model.wavefront;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.NORMAL_BIT;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.TEXTURE_BIT;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -10,7 +13,6 @@ import java.util.List;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.model.ModelFormatException;
|
||||
|
||||
@@ -23,6 +25,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
|
||||
/**
|
||||
* Wavefront Object importer Based heavily off of the specifications found at
|
||||
@@ -32,7 +35,6 @@ public final class WavefrontVBOBuilder {
|
||||
|
||||
private final List<Vector3f> vertices = new ArrayList<>(64);
|
||||
private final List<Vector2f> textureCoordinates = new ArrayList<>(64);
|
||||
private final IVertexArrayObject vao;
|
||||
|
||||
static final int FORMAT_V_VT_VN = 0;
|
||||
static final int FORMAT_V_VT = 1;
|
||||
@@ -53,25 +55,62 @@ public final class WavefrontVBOBuilder {
|
||||
return compileToVBO(resource, DefaultVertexFormat.POSITION_TEXTURE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the format declares an attribute an OBJ cannot supply; only position, UV and
|
||||
* normal are fillable.
|
||||
*/
|
||||
public static IVertexArrayObject compileToVBO(ResourceLocation resource, VertexFormat format) {
|
||||
return new WavefrontVBOBuilder(resource, format).vao;
|
||||
}
|
||||
validateFormat(format);
|
||||
|
||||
private WavefrontVBOBuilder(ResourceLocation resource, VertexFormat format) throws ModelFormatException {
|
||||
try {
|
||||
final IResource res = Minecraft.getMinecraft().getResourceManager().getResource(resource);
|
||||
vao = loadObjModel(res.getInputStream(), format);
|
||||
try (InputStream inputStream = Minecraft.getMinecraft().getResourceManager().getResource(resource)
|
||||
.getInputStream()) {
|
||||
final DirectTessellator tessellator = DirectTessellator.startCapturing(format);
|
||||
try {
|
||||
loadObjModel(inputStream, tessellator, format);
|
||||
return tessellator.uploadToVBO(VertexBufferType.IMMUTABLE);
|
||||
} finally {
|
||||
DirectTessellator.stopCapturing();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ModelFormatException("IO Exception reading model format", e);
|
||||
}
|
||||
}
|
||||
|
||||
private IVertexArrayObject loadObjModel(InputStream inputStream, VertexFormat format) throws ModelFormatException {
|
||||
/**
|
||||
* Rejects formats whose attributes the OBJ loader cannot populate
|
||||
*/
|
||||
static void validateFormat(VertexFormat format) {
|
||||
final VertexFormatElement[] elements = format.elementsArray;
|
||||
|
||||
if (!format.hasInlinePosition()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Vertex format must start with a 3-float position element, got "
|
||||
+ (elements.length == 0 ? "an empty format" : elements[0]));
|
||||
}
|
||||
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
final int bit = elements[i].getVertexBit();
|
||||
if (bit != TEXTURE_BIT && bit != NORMAL_BIT) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot fill vertex format element " + i
|
||||
+ " "
|
||||
+ elements[i]
|
||||
+ " from an OBJ; only a leading position, UV and normal are supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loadObjModel(InputStream inputStream, DirectTessellator tessellator, VertexFormat format)
|
||||
throws ModelFormatException {
|
||||
new WavefrontVBOBuilder(format).parse(inputStream, tessellator);
|
||||
}
|
||||
|
||||
private WavefrontVBOBuilder(VertexFormat format) {
|
||||
hasTex = format.hasTexture();
|
||||
hasNormals = format.hasNormals();
|
||||
}
|
||||
|
||||
final DirectTessellator tessellator = DirectTessellator.startCapturing();
|
||||
|
||||
private void parse(InputStream inputStream, DirectTessellator tessellator) throws ModelFormatException {
|
||||
BufferedReader reader = null;
|
||||
|
||||
String currentLine;
|
||||
@@ -120,7 +159,9 @@ public final class WavefrontVBOBuilder {
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
return DirectTessellator.stopCapturingToVBO(VertexBufferType.IMMUTABLE);
|
||||
if (format == -1) {
|
||||
throw new ModelFormatException("Model contains no faces");
|
||||
}
|
||||
}
|
||||
|
||||
static Vector3f parseVertex(final String line, final int lineCount, final String[] tokens)
|
||||
@@ -186,6 +227,28 @@ public final class WavefrontVBOBuilder {
|
||||
this.vertexArr = new Vector3f[length];
|
||||
this.texArr = new Vector2f[length];
|
||||
format = determineFormat(tokens[0]);
|
||||
|
||||
if (hasTex && (format == FORMAT_V || format == FORMAT_V_VN)) {
|
||||
throw new ModelFormatException(
|
||||
"Model has no texture coordinates but the requested vertex format declares a UV attribute (line "
|
||||
+ lineCount
|
||||
+ ")");
|
||||
}
|
||||
} else {
|
||||
if (length != this.vertexArr.length) {
|
||||
throw new ModelFormatException(
|
||||
"Face at line " + lineCount
|
||||
+ " has "
|
||||
+ length
|
||||
+ " vertices but the model started with "
|
||||
+ this.vertexArr.length
|
||||
+ "; mixed face sizes are not supported");
|
||||
}
|
||||
if (determineFormat(tokens[0]) != format) {
|
||||
throw new ModelFormatException(
|
||||
"Face at line " + lineCount
|
||||
+ " uses a different vertex reference format than the first face; mixed face formats are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
final Vector3f[] vertices = this.vertexArr;
|
||||
@@ -315,9 +378,9 @@ public final class WavefrontVBOBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing space if present
|
||||
// Remove trailing space if present; resultLength is -1 for an all-whitespace line
|
||||
final int resultLength = output.length() - 1;
|
||||
if (output.charAt(resultLength) == ' ') {
|
||||
if (resultLength >= 0 && output.charAt(resultLength) == ' ') {
|
||||
output.setLength(resultLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,11 +429,20 @@ public class DirectTessellator extends Tessellator {
|
||||
|
||||
// Method may change/get removed in the future.
|
||||
// For future-proof use, use TessellatorManager#startCapturingDirect(VertexFormat)
|
||||
/**
|
||||
* @throws IllegalArgumentException if the format does not start with an inline 3-float position element (see
|
||||
* {@link VertexFormat#hasInlinePosition()})
|
||||
*/
|
||||
@Deprecated
|
||||
public final void setVertexFormat(VertexFormat format) {
|
||||
if (this.format != null) {
|
||||
throw new IllegalStateException("Cannot call setVertexFormat() after a vertex has already been emitted!");
|
||||
}
|
||||
if (!format.hasInlinePosition()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Vertex format must start with a 3-float position element, got "
|
||||
+ (format.elementsArray.length == 0 ? "an empty format" : format.elementsArray[0]));
|
||||
}
|
||||
this.preDefinedFormat = format;
|
||||
this.format = format;
|
||||
this.hasTexture = format.hasTexture();
|
||||
|
||||
@@ -304,6 +304,10 @@ public class TessellatorManager {
|
||||
return directTessellatorIndex != -1;
|
||||
}
|
||||
|
||||
public static int getDirectCaptureDepth() {
|
||||
return directTessellatorIndex + 1;
|
||||
}
|
||||
|
||||
private static void setDirectTessellator(DirectTessellator tessellator) {
|
||||
if (++directTessellatorIndex >= DIRECT_TESSELLATOR_STACK_DEPTH) {
|
||||
directTessellatorIndex--;
|
||||
@@ -335,7 +339,12 @@ public class TessellatorManager {
|
||||
|
||||
public static DirectTessellator startCapturingDirect(VertexFormat format) {
|
||||
final DirectTessellator tessellator = startCapturingDirect();
|
||||
tessellator.setVertexFormat(format);
|
||||
try {
|
||||
tessellator.setVertexFormat(format);
|
||||
} catch (Throwable t) {
|
||||
stopCapturingDirect();
|
||||
throw t;
|
||||
}
|
||||
return tessellator;
|
||||
}
|
||||
|
||||
@@ -873,7 +882,7 @@ public class TessellatorManager {
|
||||
/**
|
||||
* Fast path to get the Tessellator used in the main thread. Does not check if this is called on the main thread.
|
||||
* This should only be used if you are 100% certain that it only gets called in the main thread.
|
||||
*
|
||||
*
|
||||
* @return Tessellator.instance, or DirectTessellator if currently capturing.
|
||||
*/
|
||||
public static Tessellator getMainThreadTessellator() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.joml.Vector3f;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Type;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;
|
||||
import lombok.Getter;
|
||||
@@ -245,6 +246,15 @@ public class VertexFormat {
|
||||
return this.vertexFlags;
|
||||
}
|
||||
|
||||
public final boolean hasInlinePosition() {
|
||||
if (elementsArray.length == 0) return false;
|
||||
|
||||
final VertexFormatElement first = elementsArray[0];
|
||||
return first.getWriter().isInlinePosition() && first.getType() == Type.FLOAT
|
||||
&& first.getCount() == 3
|
||||
&& first.getByteSize() == 12;
|
||||
}
|
||||
|
||||
public final boolean hasTexture() {
|
||||
return (vertexFlags & TEXTURE_BIT) != 0;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ public class VertexFormatElement {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return usage.getName() + "[" + type.getName() + " x" + count + ", index=" + index + ", " + byteSize + " bytes]";
|
||||
}
|
||||
|
||||
public void setupBufferState(long offset, int stride) {
|
||||
this.usage.setupBufferState(this.count, this.type.getGlType(), stride, offset, this.index);
|
||||
}
|
||||
|
||||
+4
@@ -14,6 +14,10 @@ public interface IVertexAttributeWriter {
|
||||
// Populate the Tessellator using the buffer's contents
|
||||
int readAttribute(long pointer, Tessellator tessellator);
|
||||
|
||||
default boolean isInlinePosition() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default int writeAttributeTransformed(long pointer, int[] data, int index, Matrix4fc transform, Vector3f scratch) {
|
||||
return writeAttribute(pointer, data, index);
|
||||
}
|
||||
|
||||
+5
@@ -41,4 +41,9 @@ public final class PositionVertexAttributeWriter implements IVertexAttributeWrit
|
||||
throw new UnsupportedOperationException("Cannot read position from tessellator!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInlinePosition() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package com.gtnewhorizon.gtnhlib.client.model.wavefront;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraftforge.client.model.ModelFormatException;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Type;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.writers.IVertexAttributeWriter;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.writers.PositionVertexAttributeWriter;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.writers.TextureVertexAttributeWriter;
|
||||
|
||||
public class WavefrontVBOBuilderTest {
|
||||
|
||||
private static final int A_POSITION = 3;
|
||||
private static final int A_UV = 7;
|
||||
|
||||
private static final VertexFormat GENERIC_POSITION_UV = new VertexFormat(
|
||||
new VertexFormatElement(
|
||||
A_POSITION,
|
||||
Type.FLOAT,
|
||||
Usage.GENERIC,
|
||||
3,
|
||||
POSITION_BIT,
|
||||
new PositionVertexAttributeWriter()),
|
||||
new VertexFormatElement(
|
||||
A_UV,
|
||||
Type.FLOAT,
|
||||
Usage.GENERIC,
|
||||
2,
|
||||
TEXTURE_BIT,
|
||||
new TextureVertexAttributeWriter()));
|
||||
|
||||
private static final String TRIANGLE_OBJ = "v 0 0 0\n" + "v 1 0 0\n"
|
||||
+ "v 0 1 0\n"
|
||||
+ "vt 0 0\n"
|
||||
+ "vt 1 0\n"
|
||||
+ "vt 0 1\n"
|
||||
+ "f 1/1 2/2 3/3\n";
|
||||
|
||||
private static final String QUAD_OBJ = "v 0 0 0\n" + "v 1 0 0\n"
|
||||
+ "v 1 1 0\n"
|
||||
+ "v 0 1 0\n"
|
||||
+ "vt 0 0\n"
|
||||
+ "vt 1 0\n"
|
||||
+ "vt 1 1\n"
|
||||
+ "vt 0 1\n"
|
||||
+ "f 1/1 2/2 3/3 4/4\n";
|
||||
|
||||
private static final float UV_DELTA = 0.001f;
|
||||
|
||||
/** A 12-byte float3 that is not a position, to prove the guard looks at more than the element's shape. */
|
||||
private static final IVertexAttributeWriter NOT_POSITION = new IVertexAttributeWriter() {
|
||||
|
||||
@Override
|
||||
public int writeAttribute(long pointer, int[] data, int index) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeAttribute(long pointer, Tessellator tessellator) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readAttribute(long pointer, Tessellator tessellator) {
|
||||
return 12;
|
||||
}
|
||||
};
|
||||
|
||||
private final List<DirectTessellator> allocated = new ArrayList<>();
|
||||
|
||||
@AfterEach
|
||||
void freeTessellators() {
|
||||
allocated.forEach(DirectTessellator::delete);
|
||||
allocated.clear();
|
||||
}
|
||||
|
||||
private static InputStream obj(String source) {
|
||||
return new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private DirectTessellator tessellator(VertexFormat format) {
|
||||
final DirectTessellator tess = new DirectTessellator(1024);
|
||||
allocated.add(tess);
|
||||
if (format != null) tess.setVertexFormat(format);
|
||||
return tess;
|
||||
}
|
||||
|
||||
private DirectTessellator build(String source, VertexFormat parseFormat, VertexFormat predefinedFormat) {
|
||||
final DirectTessellator tess = tessellator(predefinedFormat);
|
||||
WavefrontVBOBuilder.loadObjModel(obj(source), tess, parseFormat);
|
||||
return tess;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenericFormatIsHonoured() {
|
||||
assertEquals(20, GENERIC_POSITION_UV.getVertexSize(), "3 floats of position + 2 floats of UV");
|
||||
|
||||
final DirectTessellator tess = build(TRIANGLE_OBJ, GENERIC_POSITION_UV, GENERIC_POSITION_UV);
|
||||
|
||||
assertSame(GENERIC_POSITION_UV, tess.getVertexFormat(), "The format passed in must be the one used");
|
||||
assertEquals(3, tess.getVertexCount());
|
||||
assertEquals(60, tess.getWriteBuffer().limit(), "3 vertices at the custom 20-byte stride");
|
||||
|
||||
final long base = memAddress0(tess.getWriteBuffer());
|
||||
|
||||
assertEquals(0f, memGetFloat(base), 0.0001f);
|
||||
assertEquals(0f, memGetFloat(base + 4), 0.0001f);
|
||||
assertEquals(0f, memGetFloat(base + 8), 0.0001f);
|
||||
assertEquals(0f, memGetFloat(base + 12), UV_DELTA);
|
||||
assertEquals(1f, memGetFloat(base + 16), UV_DELTA);
|
||||
|
||||
assertEquals(1f, memGetFloat(base + 20), 0.0001f);
|
||||
assertEquals(0f, memGetFloat(base + 24), 0.0001f);
|
||||
assertEquals(1f, memGetFloat(base + 32), UV_DELTA);
|
||||
assertEquals(1f, memGetFloat(base + 36), UV_DELTA);
|
||||
|
||||
assertEquals(0f, memGetFloat(base + 40), 0.0001f);
|
||||
assertEquals(1f, memGetFloat(base + 44), 0.0001f);
|
||||
assertEquals(0f, memGetFloat(base + 52), UV_DELTA);
|
||||
assertEquals(0f, memGetFloat(base + 56), UV_DELTA);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenericAttributeLocationsSurvive() {
|
||||
final VertexFormat used = build(TRIANGLE_OBJ, GENERIC_POSITION_UV, GENERIC_POSITION_UV).getVertexFormat();
|
||||
|
||||
assertEquals(A_POSITION, used.elementsArray[0].getIndex());
|
||||
assertEquals(A_UV, used.elementsArray[1].getIndex());
|
||||
assertEquals(Usage.GENERIC, used.elementsArray[1].getUsage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDerivedFormatCannotExpressGenericAttributes() {
|
||||
final VertexFormat derived = build(TRIANGLE_OBJ, GENERIC_POSITION_UV, null).getVertexFormat();
|
||||
|
||||
assertSame(DefaultVertexFormat.POSITION_TEXTURE, derived);
|
||||
assertEquals(GENERIC_POSITION_UV.getVertexSize(), derived.getVertexSize(), "Same stride, different bindings");
|
||||
assertEquals(Usage.PRIMARY_UV, derived.elementsArray[1].getUsage(), "Binds through glTexCoordPointer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultOverloadIsUnchanged() {
|
||||
final VertexFormat format = DefaultVertexFormat.POSITION_TEXTURE_NORMAL;
|
||||
|
||||
final DirectTessellator derived = build(TRIANGLE_OBJ, format, null);
|
||||
final DirectTessellator predefined = build(TRIANGLE_OBJ, format, format);
|
||||
|
||||
assertSame(format, derived.getVertexFormat(), "The derived format was already POSITION_TEXTURE_NORMAL");
|
||||
assertSame(format, predefined.getVertexFormat());
|
||||
|
||||
final ByteBuffer a = derived.getWriteBuffer();
|
||||
final ByteBuffer b = predefined.getWriteBuffer();
|
||||
|
||||
assertEquals(72, a.limit(), "3 vertices at the 24-byte POSITION_TEXTURE_NORMAL stride");
|
||||
assertEquals(a.limit(), b.limit());
|
||||
for (int i = 0; i < a.limit(); i++) {
|
||||
assertEquals(a.get(i), b.get(i), "Byte " + i + " differs from the pre-fix output");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQuadsKeepCustomStride() {
|
||||
final DirectTessellator tess = build(QUAD_OBJ, GENERIC_POSITION_UV, GENERIC_POSITION_UV);
|
||||
|
||||
assertEquals(GL11.GL_QUADS, tess.getDrawMode());
|
||||
assertEquals(4, tess.getVertexCount());
|
||||
assertEquals(80, tess.getWriteBuffer().limit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnfillableAttributeIsRejected() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> WavefrontVBOBuilder.validateFormat(DefaultVertexFormat.POSITION_TEXTURE_COLOR),
|
||||
"An OBJ cannot supply a color channel");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> WavefrontVBOBuilder.validateFormat(DefaultVertexFormat.POSITION_TEXTURE_LIGHT),
|
||||
"An OBJ cannot supply a brightness channel");
|
||||
assertDoesNotThrow(() -> WavefrontVBOBuilder.validateFormat(DefaultVertexFormat.POSITION_TEXTURE_NORMAL));
|
||||
assertDoesNotThrow(() -> WavefrontVBOBuilder.validateFormat(GENERIC_POSITION_UV));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFormatNotStartingWithPositionIsRejected() {
|
||||
final VertexFormat uvFirst = new VertexFormat(
|
||||
DefaultVertexFormat.TEXTURE_ELEMENT,
|
||||
DefaultVertexFormat.POSITION_ELEMENT);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> WavefrontVBOBuilder.validateFormat(uvFirst));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> tessellator(uvFirst),
|
||||
"setVertexFormat guards the same invariant for every predefined-format caller");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTexturedFormatAgainstUntexturedModel() {
|
||||
final String positionsOnly = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
|
||||
|
||||
assertThrows(
|
||||
ModelFormatException.class,
|
||||
() -> build(positionsOnly, DefaultVertexFormat.POSITION_TEXTURE, DefaultVertexFormat.POSITION_TEXTURE),
|
||||
"Must report the mismatch rather than dereferencing the unfilled UV array");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testModelWithoutFaces() {
|
||||
assertThrows(
|
||||
ModelFormatException.class,
|
||||
() -> build("v 0 0 0\nv 1 0 0\n", GENERIC_POSITION_UV, GENERIC_POSITION_UV));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMalformedFaceStillPropagates() {
|
||||
final String badIndex = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 99\n";
|
||||
|
||||
assertThrows(
|
||||
IndexOutOfBoundsException.class,
|
||||
() -> build(badIndex, DefaultVertexFormat.POSITION, DefaultVertexFormat.POSITION));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloat3NonPositionFirstElementIsRejected() {
|
||||
final VertexFormat normalFirst = new VertexFormat(
|
||||
new VertexFormatElement(0, Type.FLOAT, Usage.GENERIC, 3, NORMAL_BIT, NOT_POSITION),
|
||||
new VertexFormatElement(
|
||||
A_UV,
|
||||
Type.FLOAT,
|
||||
Usage.GENERIC,
|
||||
2,
|
||||
TEXTURE_BIT,
|
||||
new TextureVertexAttributeWriter()));
|
||||
|
||||
assertFalse(
|
||||
normalFirst.hasInlinePosition(),
|
||||
"Element 0 is written as inline xyz, so a float3 that is not a position must not claim that slot");
|
||||
assertThrows(IllegalArgumentException.class, () -> WavefrontVBOBuilder.validateFormat(normalFirst));
|
||||
assertThrows(IllegalArgumentException.class, () -> tessellator(normalFirst));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhitespaceOnlyLinesAreIgnored() {
|
||||
final String padded = "v 0 0 0\n \n" + "v 1 0 0\n\t\n"
|
||||
+ "v 0 1 0\n"
|
||||
+ "vt 0 0\n"
|
||||
+ "vt 1 0\n"
|
||||
+ "vt 0 1\n"
|
||||
+ " \n"
|
||||
+ "f 1/1 2/2 3/3\n";
|
||||
|
||||
final ByteBuffer clean = build(TRIANGLE_OBJ, GENERIC_POSITION_UV, GENERIC_POSITION_UV).getWriteBuffer();
|
||||
final ByteBuffer spaced = build(padded, GENERIC_POSITION_UV, GENERIC_POSITION_UV).getWriteBuffer();
|
||||
|
||||
assertEquals(clean.limit(), spaced.limit());
|
||||
for (int i = 0; i < clean.limit(); i++) {
|
||||
assertEquals(clean.get(i), spaced.get(i), "Byte " + i + " differs from the unpadded model");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedFaceSizesRejected() {
|
||||
final String mixed = "v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nf 1 2 3\nf 1 2 3 4\n";
|
||||
|
||||
assertThrows(
|
||||
ModelFormatException.class,
|
||||
() -> build(mixed, DefaultVertexFormat.POSITION, DefaultVertexFormat.POSITION),
|
||||
"A single VBO carries one draw mode, so the quad cannot follow the triangle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedFaceFormatsRejected() {
|
||||
final String mixed = "v 0 0 0\nv 1 0 0\n" + "v 0 1 0\n"
|
||||
+ "vt 0 0\n"
|
||||
+ "vt 1 0\n"
|
||||
+ "vt 0 1\n"
|
||||
+ "f 1/1 2/2 3/3\n"
|
||||
+ "f 1 2 3\n";
|
||||
|
||||
assertThrows(
|
||||
ModelFormatException.class,
|
||||
() -> build(mixed, GENERIC_POSITION_UV, GENERIC_POSITION_UV),
|
||||
"The second face has no UV indices; reading stale subtokens would silently corrupt the UVs");
|
||||
}
|
||||
}
|
||||
+17
@@ -13,6 +13,8 @@ import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
|
||||
/**
|
||||
* Unit tests for TessellatorManager compiling mode and capturing mode.
|
||||
@@ -25,6 +27,21 @@ public class TessellatorManagerTest {
|
||||
TessellatorManager.cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRejectedFormatDoesNotLeakTheStack() {
|
||||
final VertexFormat uvFirst = new VertexFormat(
|
||||
DefaultVertexFormat.TEXTURE_ELEMENT,
|
||||
DefaultVertexFormat.POSITION_ELEMENT);
|
||||
|
||||
final int depth = TessellatorManager.getDirectCaptureDepth();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> TessellatorManager.startCapturingDirect(uvFirst));
|
||||
assertEquals(
|
||||
depth,
|
||||
TessellatorManager.getDirectCaptureDepth(),
|
||||
"Nothing should be left on the DirectTessellator stack");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetCompilingWithNullCallback() {
|
||||
assertThrows(
|
||||
|
||||
Reference in new issue
Block a user