From 911e73291f1f9b6bedcd52025b00c6695d8ea1b5 Mon Sep 17 00:00:00 2001 From: Algent Date: Wed, 2 Sep 2026 01:15:59 +0200 Subject: [PATCH] test: characterize hollow microblocks --- .../test/DefaultContentFunctionalTest.java | 219 +++++++++++++++++ .../HollowMicroblockCharacterizationTest.java | 225 ++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 src/test/java/codechicken/microblock/HollowMicroblockCharacterizationTest.java diff --git a/src/functionalTest/java/codechicken/multipart/test/DefaultContentFunctionalTest.java b/src/functionalTest/java/codechicken/multipart/test/DefaultContentFunctionalTest.java index a3c75f0..7983304 100644 --- a/src/functionalTest/java/codechicken/multipart/test/DefaultContentFunctionalTest.java +++ b/src/functionalTest/java/codechicken/multipart/test/DefaultContentFunctionalTest.java @@ -1,6 +1,7 @@ package codechicken.multipart.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -10,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -21,6 +23,8 @@ import net.minecraft.init.Blocks; import org.junit.jupiter.api.Test; import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; import codechicken.microblock.BlockMicroMaterial; import codechicken.microblock.CommonMicroClass; import codechicken.microblock.CommonMicroblockClient; @@ -42,7 +46,13 @@ import codechicken.microblock.FaceMicroblock; import codechicken.microblock.FacePlacement$; import codechicken.microblock.FacePlacementGrid$; import codechicken.microblock.GrassMicroMaterial; +import codechicken.microblock.HollowMicroClass; import codechicken.microblock.HollowMicroClass$; +import codechicken.microblock.HollowMicroblock; +import codechicken.microblock.HollowMicroblockClient; +import codechicken.microblock.HollowPlacement; +import codechicken.microblock.HollowPlacement$; +import codechicken.microblock.ISidedHollowConnect; import codechicken.microblock.MicroMaterialRegistry; import codechicken.microblock.MicroMaterialRegistry.IMicroMaterial; import codechicken.microblock.Microblock; @@ -58,8 +68,12 @@ import codechicken.multipart.MultiPartRegistry; import codechicken.multipart.MultiPartRegistry.IPartFactory2; import codechicken.multipart.TEdgePart; import codechicken.multipart.TFacePart; +import codechicken.multipart.TMultiPart; import codechicken.multipart.TNormalOcclusion; +import codechicken.multipart.TSlottedPart; +import codechicken.multipart.TileMultipart; import scala.Tuple2; +import scala.collection.JavaConversions; class DefaultContentFunctionalTest { @@ -248,6 +262,95 @@ class DefaultContentFunctionalTest { } } + @Test + void keepsHollowFactoryBoundsAndGeneratedServerGeometry() { + HollowMicroClass$ factory = HollowMicroClass$.MODULE$; + assertSame(factory, HollowPlacement$.MODULE$.microClass()); + assertSame(factory, HollowPlacement.microClass()); + assertSame(HollowPlacement.HollowPlacementGrid$.MODULE$, HollowPlacement$.MODULE$.placementGrid()); + assertSame(factory.pBoxes(), HollowMicroClass.pBoxes()); + assertSame(factory.occBounds(), HollowMicroClass.occBounds()); + assertEquals("mcr_hllw", factory.getName()); + assertEquals(HollowMicroblock.class, factory.baseTrait()); + assertEquals(HollowMicroblockClient.class, factory.clientTrait()); + assertEquals(3, factory.itemSlot()); + assertEquals(1f, factory.getResistanceFactor()); + + scala.collection.Seq[] partialBounds = factory.pBoxes(); + Cuboid6[] occlusionBounds = factory.occBounds(); + assertEquals(256, partialBounds.length); + assertEquals(256, occlusionBounds.length); + for (int side = 0; side < 6; side++) { + for (int size = 1; size < 8; size++) { + int shape = size << 4 | side; + List partial = JavaConversions.seqAsJavaList(partialBounds[shape]); + assertCuboids(expectedPartialBounds(side, size), partial); + assertCuboid(expectedHollowOcclusionBound(side, size), occlusionBounds[shape]); + } + } + assertEquals(42, countPopulated(partialBounds)); + assertEquals(42, countPopulated(occlusionBounds)); + + Microblock generated = factory.create(false, 0); + assertTrue(generated instanceof HollowMicroblock); + assertTrue(generated instanceof TFacePart); + assertTrue(generated instanceof TNormalOcclusion); + HollowMicroblock hollow = (HollowMicroblock) generated; + assertEquals(factory.getClassId(), hollow.itemClassID()); + assertEquals(8, hollow.getHollowSize()); + assertTrue(hollow.allowCompleteOcclusion()); + assertFalse(hollow.solid(0)); + assertEquals(0x10, hollow.redstoneConductionMap()); + + for (int side = 0; side < 6; side++) { + generated.setShape(3, side); + int shape = 3 << 4 | side; + assertSame(FaceMicroClass$.MODULE$.aBounds()[shape], hollow.getBounds()); + assertCuboids(JavaConversions.seqAsJavaList(partialBounds[shape]), hollow.getPartialOcclusionBoxes()); + assertCuboids(expectedCollisionBounds(side, 3, 8), hollow.getCollisionBoxes()); + assertCuboids(expectedOcclusionBoxes(side, 3, 8), hollow.getOcclusionBoxes()); + assertEquals(4, hollow.getSubParts().size()); + for (int i = 0; i < 4; i++) { + assertEquals(0, hollow.getSubParts().get(i).data); + assertCuboid(hollow.getCollisionBoxes().get(i), hollow.getSubParts().get(i)); + } + } + } + + @Test + void keepsConnectedHollowSizesAndAllDynamicGeometry() { + ConnectorPart connector = new ConnectorPart(); + TileMultipart tile = new TileMultipart() { + + @Override + public TMultiPart partMap(int slot) { + return slot == 6 ? connector : null; + } + }; + connector.bind(tile); + int material = MicroMaterialRegistry.materialID("minecraft:stone"); + assertTrue(material >= 0); + Microblock generated = HollowMicroClass$.MODULE$.create(false, material); + generated.setShape(3, 0); + generated.bind(tile); + HollowMicroblock hollow = (HollowMicroblock) generated; + + for (int size = 1; size < 12; size++) { + connector.size = size; + for (int side = 0; side < 6; side++) { + generated.setShape(3, side); + assertEquals(size, hollow.getHollowSize()); + assertCuboids(expectedCollisionBounds(side, 3, size), hollow.getCollisionBoxes()); + assertCuboids(expectedOcclusionBoxes(side, 3, size), hollow.getOcclusionBoxes()); + assertEquals(4, hollow.getSubParts().size()); + for (int i = 0; i < 4; i++) { + assertEquals(0, hollow.getSubParts().get(i).data); + assertCuboid(hollow.getCollisionBoxes().get(i), hollow.getSubParts().get(i)); + } + } + } + } + @Test void registersTheExactOrderedBuiltInMaterialsAndRemaps() throws Exception { List expectedNames = new ArrayList<>(); @@ -319,6 +422,82 @@ class DefaultContentFunctionalTest { add(names, remaps, block, registeredName, maxMeta, block.getUnlocalizedName()); } + private static List expectedPartialBounds(int side, int size) { + double thickness = size / 8d; + double width = 1 / 8d; + return transform( + side, + Arrays.asList( + new Cuboid6(0, 0, 0, width, thickness, 1), + new Cuboid6(1 - width, 0, 0, 1, thickness, 1), + new Cuboid6(width, 0, 0, 1 - width, thickness, width), + new Cuboid6(width, 0, 1 - width, 1 - width, thickness, 1))); + } + + private static Cuboid6 expectedHollowOcclusionBound(int side, int size) { + return new Cuboid6(1 / 8d, 0, 1 / 8d, 7 / 8d, size / 8d, 7 / 8d) + .apply(Rotation.sideRotations[side].at(Vector3.center)); + } + + private static List expectedCollisionBounds(int side, int partSize, int hollowSize) { + double d1 = 0.5 - hollowSize / 32d; + double d2 = 0.5 + hollowSize / 32d; + double thickness = partSize / 8d; + return transform( + side, + Arrays.asList( + new Cuboid6(0, 0, 0, 1, thickness, d1), + new Cuboid6(0, 0, d2, 1, thickness, 1), + new Cuboid6(0, 0, d1, d1, thickness, d2), + new Cuboid6(d2, 0, d1, 1, thickness, d2))); + } + + private static List expectedOcclusionBoxes(int side, int partSize, int hollowSize) { + Cuboid6 c = expectedHollowOcclusionBound(side, partSize); + double d1 = 0.5 - hollowSize / 32d; + double d2 = 0.5 + hollowSize / 32d; + double x1 = c.min.x; + double x2 = c.max.x; + double y1 = c.min.y; + double y2 = c.max.y; + double z1 = c.min.z; + double z2 = c.max.z; + + switch (side) { + case 0: + case 1: + return Arrays.asList( + new Cuboid6(d2, y1, d1, x2, y2, d2), + new Cuboid6(x1, y1, d1, d1, y2, d2), + new Cuboid6(x1, y1, d2, x2, y2, z2), + new Cuboid6(x1, y1, z1, x2, y2, d1)); + case 2: + case 3: + return Arrays.asList( + new Cuboid6(d1, d2, z1, d2, y2, z2), + new Cuboid6(d1, y1, z1, d2, d1, z2), + new Cuboid6(d2, y1, z1, x2, y2, z2), + new Cuboid6(x1, y1, z1, d1, y2, z2)); + case 4: + case 5: + return Arrays.asList( + new Cuboid6(x1, d1, d2, x2, d2, z2), + new Cuboid6(x1, d1, z1, x2, d2, d1), + new Cuboid6(x1, d2, z1, x2, y2, z2), + new Cuboid6(x1, y1, z1, x2, d1, z2)); + default: + throw new AssertionError(side); + } + } + + private static List transform(int side, List bounds) { + List transformed = new ArrayList<>(bounds.size()); + for (Cuboid6 bound : bounds) { + transformed.add(bound.apply(Rotation.sideRotations[side].at(Vector3.center))); + } + return transformed; + } + private static Cuboid6 expectedFaceBounds(int side, double thickness) { switch (side) { case 0: @@ -387,6 +566,26 @@ class DefaultContentFunctionalTest { return populated; } + private static int countPopulated(scala.collection.Seq[] bounds) { + int populated = 0; + for (scala.collection.Seq bound : bounds) { + if (bound != null) { + populated++; + } + } + return populated; + } + + private static void assertCuboids(Iterable expected, Iterable actual) { + Iterator expectedIterator = expected.iterator(); + Iterator actualIterator = actual.iterator(); + while (expectedIterator.hasNext()) { + assertTrue(actualIterator.hasNext()); + assertCuboid(expectedIterator.next(), actualIterator.next()); + } + assertFalse(actualIterator.hasNext()); + } + private static void assertCuboid(Cuboid6 expected, Cuboid6 actual) { assertEquals(expected.min.x, actual.min.x, 1e-12); assertEquals(expected.min.y, actual.min.y, 1e-12); @@ -418,4 +617,24 @@ class DefaultContentFunctionalTest { field.setAccessible(true); return (Map) field.get(null); } + + private static final class ConnectorPart extends TMultiPart implements TSlottedPart, ISidedHollowConnect { + + private int size; + + @Override + public String getType() { + return "multipart_test:hollow_connector"; + } + + @Override + public int getSlotMask() { + return 1 << 6; + } + + @Override + public int getHollowSize(int side) { + return size; + } + } } diff --git a/src/test/java/codechicken/microblock/HollowMicroblockCharacterizationTest.java b/src/test/java/codechicken/microblock/HollowMicroblockCharacterizationTest.java new file mode 100644 index 0000000..55cf79a --- /dev/null +++ b/src/test/java/codechicken/microblock/HollowMicroblockCharacterizationTest.java @@ -0,0 +1,225 @@ +package codechicken.microblock; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Type; + +import codechicken.lib.vec.Cuboid6; +import codechicken.multipart.TFacePart; +import codechicken.multipart.TNormalOcclusion; + +class HollowMicroblockCharacterizationTest { + + private static final Set PLACEMENT_FACADE_METHODS = signatures( + "customPlacement(Lcodechicken/microblock/MicroblockPlacement;)Lcodechicken/microblock/ExecutablePlacement;", + "expand(II)Z", + "microClass()Lcodechicken/microblock/HollowMicroClass$;", + "opposite(II)I", + "placementGrid()Lcodechicken/microblock/HollowPlacement$HollowPlacementGrid$;", + "sneakOpposite(II)Z"); + private static final Set PLACEMENT_COMPANION_METHODS = signatures( + "expand(II)Z", + "microClass()Lcodechicken/microblock/HollowMicroClass$;", + "microClass()Lcodechicken/microblock/MicroblockClass;", + "opposite(II)I", + "placementGrid()Lcodechicken/microblock/HollowPlacement$HollowPlacementGrid$;", + "placementGrid()Lcodechicken/microblock/PlacementGrid;", + "sneakOpposite(II)Z"); + private static final Set CLASS_FACADE_METHODS = signatures( + "baseTrait()Ljava/lang/Class;", + "baseTraitId()I", + "clientTrait()Ljava/lang/Class;", + "clientTraitId()I", + "create(ZI)Lcodechicken/microblock/Microblock;", + "createPart(Ljava/lang/String;Lcodechicken/lib/data/MCDataInput;)Lcodechicken/microblock/Microblock;", + "createPart(Ljava/lang/String;Lnet/minecraft/nbt/NBTTagCompound;)Lcodechicken/microblock/Microblock;", + "getClassId()I", + "getName()Ljava/lang/String;", + "getResistanceFactor()F", + "itemSlot()I", + "occBounds()[Lcodechicken/lib/vec/Cuboid6;", + "occBounds_$eq([Lcodechicken/lib/vec/Cuboid6;)V", + "pBoxes()[Lscala/collection/Seq;", + "pBoxes_$eq([Lscala/collection/Seq;)V", + "placementProperties()Lcodechicken/microblock/HollowPlacement$;", + "register()V", + "register(I)V"); + private static final Set CLASS_COMPANION_METHODS = signatures( + "baseTrait()Ljava/lang/Class;", + "clientTrait()Ljava/lang/Class;", + "getName()Ljava/lang/String;", + "getResistanceFactor()F", + "itemSlot()I", + "occBounds()[Lcodechicken/lib/vec/Cuboid6;", + "occBounds_$eq([Lcodechicken/lib/vec/Cuboid6;)V", + "pBoxes()[Lscala/collection/Seq;", + "pBoxes_$eq([Lscala/collection/Seq;)V", + "placementProperties()Lcodechicken/microblock/HollowPlacement$;", + "placementProperties()Lcodechicken/microblock/PlacementProperties;"); + + @Test + void keepsFacadesNestedGridCompanionAndGeneratedTraitSurfaces() throws Exception { + assertFacade(HollowPlacement.class, PLACEMENT_FACADE_METHODS); + assertCompanion(HollowPlacement$.class, PlacementProperties.class, PLACEMENT_COMPANION_METHODS, 1); + + Class grid = HollowPlacement.HollowPlacementGrid$.class; + assertTrue(Modifier.isPublic(grid.getModifiers())); + assertFalse(Modifier.isFinal(grid.getModifiers())); + assertSame(FaceEdgeGrid.class, grid.getSuperclass()); + assertEquals(0, publicDeclaredMethods(grid).size()); + Field gridModule = grid.getField("MODULE$"); + assertSame(grid, gridModule.getType()); + assertTrue(Modifier.isStatic(gridModule.getModifiers())); + assertTrue(Modifier.isFinal(gridModule.getModifiers())); + assertEquals(1, grid.getDeclaredFields().length); + Constructor constructor = grid.getConstructor(); + assertTrue(Modifier.isPublic(constructor.getModifiers())); + + assertFacade(HollowMicroClass.class, CLASS_FACADE_METHODS); + assertCompanion(HollowMicroClass$.class, CommonMicroClass.class, CLASS_COMPANION_METHODS, 3); + assertMutablePrivateArray(HollowMicroClass$.class.getDeclaredField("pBoxes"), scala.collection.Seq[].class); + assertMutablePrivateArray(HollowMicroClass$.class.getDeclaredField("occBounds"), Cuboid6[].class); + + assertTrait( + HollowMicroblock.class, + new Class[] { CommonMicroblock.class, TFacePart.class, TNormalOcclusion.class }, + signatures( + "allowCompleteOcclusion()Z", + "codechicken$microblock$HollowMicroblock$$super$occlusionTest(Lcodechicken/multipart/TMultiPart;)Z", + "getBounds()Lcodechicken/lib/vec/Cuboid6;", + "getCollisionBoxes()Ljava/util/List;", + "getHollowSize()I", + "getOcclusionBoxes()Ljava/lang/Iterable;", + "getPartialOcclusionBoxes()Ljava/util/List;", + "getSubParts()Ljava/util/List;", + "microClass()Lcodechicken/microblock/HollowMicroClass$;", + "occlusionTest(Lcodechicken/multipart/TMultiPart;)Z", + "redstoneConductionMap()I", + "solid(I)Z")); + assertHelper( + "codechicken.microblock.HollowMicroblock$class", + signatures( + "$init$(Lcodechicken/microblock/HollowMicroblock;)V", + "allowCompleteOcclusion(Lcodechicken/microblock/HollowMicroblock;)Z", + "getBounds(Lcodechicken/microblock/HollowMicroblock;)Lcodechicken/lib/vec/Cuboid6;", + "getCollisionBoxes(Lcodechicken/microblock/HollowMicroblock;)Ljava/util/List;", + "getHollowSize(Lcodechicken/microblock/HollowMicroblock;)I", + "getOcclusionBoxes(Lcodechicken/microblock/HollowMicroblock;)Ljava/lang/Iterable;", + "getPartialOcclusionBoxes(Lcodechicken/microblock/HollowMicroblock;)Ljava/util/List;", + "getSubParts(Lcodechicken/microblock/HollowMicroblock;)Ljava/util/List;", + "microClass(Lcodechicken/microblock/HollowMicroblock;)Lcodechicken/microblock/HollowMicroClass$;", + "occlusionTest(Lcodechicken/microblock/HollowMicroblock;Lcodechicken/multipart/TMultiPart;)Z", + "redstoneConductionMap(Lcodechicken/microblock/HollowMicroblock;)I", + "solid(Lcodechicken/microblock/HollowMicroblock;I)Z")); + + assertTrait( + HollowMicroblockClient.class, + new Class[] { HollowMicroblock.class, CommonMicroblockClient.class }, + signatures( + "codechicken$microblock$HollowMicroblockClient$$super$recalcBounds()V", + "drawBreaking(Lnet/minecraft/client/renderer/RenderBlocks;)V", + "drawHighlight(Lnet/minecraft/util/MovingObjectPosition;Lnet/minecraft/entity/player/EntityPlayer;F)Z", + "recalcBounds()V", + "render(Lcodechicken/lib/vec/Vector3;I)V", + "renderHollow(Lcodechicken/lib/vec/Vector3;ILcodechicken/lib/vec/Cuboid6;IZLscala/Function5;)V")); + assertHelper( + "codechicken.microblock.HollowMicroblockClient$class", + signatures( + "$init$(Lcodechicken/microblock/HollowMicroblockClient;)V", + "drawBreaking(Lcodechicken/microblock/HollowMicroblockClient;Lnet/minecraft/client/renderer/RenderBlocks;)V", + "drawHighlight(Lcodechicken/microblock/HollowMicroblockClient;Lnet/minecraft/util/MovingObjectPosition;Lnet/minecraft/entity/player/EntityPlayer;F)Z", + "recalcBounds(Lcodechicken/microblock/HollowMicroblockClient;)V", + "render(Lcodechicken/microblock/HollowMicroblockClient;Lcodechicken/lib/vec/Vector3;I)V", + "renderHollow(Lcodechicken/microblock/HollowMicroblockClient;Lcodechicken/lib/vec/Vector3;ILcodechicken/lib/vec/Cuboid6;IZLscala/Function5;)V")); + } + + @Test + void keepsPlacementRulesAndNestedGridIdentity() { + HollowPlacement$ placement = HollowPlacement$.MODULE$; + assertSame(HollowPlacement.HollowPlacementGrid$.MODULE$, placement.placementGrid()); + assertSame(HollowPlacement.HollowPlacementGrid$.MODULE$, HollowPlacement.placementGrid()); + assertNull(placement.customPlacement(null)); + assertNull(HollowPlacement.customPlacement(null)); + + for (int slot = -3; slot < 27; slot++) { + for (int side = 0; side < 6; side++) { + assertEquals(slot ^ 1, placement.opposite(slot, side)); + assertEquals(slot ^ 1, HollowPlacement.opposite(slot, side)); + boolean opposite = slot == (side ^ 1); + assertEquals(opposite, placement.sneakOpposite(slot, side)); + assertEquals(opposite, HollowPlacement.sneakOpposite(slot, side)); + assertEquals(opposite, placement.expand(slot, side)); + assertEquals(opposite, HollowPlacement.expand(slot, side)); + } + } + } + + private static void assertFacade(Class type, Set methods) { + assertTrue(Modifier.isPublic(type.getModifiers())); + assertTrue(Modifier.isFinal(type.getModifiers())); + assertSame(Object.class, type.getSuperclass()); + assertEquals(0, type.getDeclaredFields().length); + assertEquals(methods, publicDeclaredMethods(type)); + } + + private static void assertCompanion(Class type, Class superclass, Set methods, int fieldCount) + throws Exception { + assertTrue(Modifier.isPublic(type.getModifiers())); + assertTrue(Modifier.isFinal(type.getModifiers())); + assertSame(superclass, type.getSuperclass()); + assertEquals(methods, publicDeclaredMethods(type)); + Field module = type.getField("MODULE$"); + assertSame(type, module.getType()); + assertTrue(Modifier.isStatic(module.getModifiers())); + assertTrue(Modifier.isFinal(module.getModifiers())); + assertEquals(fieldCount, type.getDeclaredFields().length); + } + + private static void assertMutablePrivateArray(Field field, Class type) { + assertSame(type, field.getType()); + assertTrue(Modifier.isPrivate(field.getModifiers())); + assertFalse(Modifier.isFinal(field.getModifiers())); + } + + private static void assertTrait(Class type, Class[] interfaces, Set methods) { + assertTrue(type.isInterface()); + assertArrayEquals(interfaces, type.getInterfaces()); + assertEquals(methods, publicDeclaredMethods(type)); + } + + private static void assertHelper(String name, Set methods) throws Exception { + Class type = Class.forName(name, false, HollowMicroblockCharacterizationTest.class.getClassLoader()); + assertTrue(Modifier.isPublic(type.getModifiers())); + assertTrue(Modifier.isAbstract(type.getModifiers())); + assertSame(Object.class, type.getSuperclass()); + assertEquals(methods, publicDeclaredMethods(type)); + } + + private static Set publicDeclaredMethods(Class type) { + Set methods = new TreeSet<>(); + for (Method method : type.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers())) { + methods.add(method.getName() + Type.getMethodDescriptor(method)); + } + } + return methods; + } + + private static Set signatures(String... signatures) { + return new TreeSet<>(Arrays.asList(signatures)); + } +}