From a648ef6622d28ac3d9724432b8111059381d28d6 Mon Sep 17 00:00:00 2001 From: piexel Date: Mon, 19 Oct 2026 21:52:11 +0200 Subject: [PATCH] Build object meshes from a block grid instead of a BlockVector map Every face looked up its neighbour in a HashMap keyed by BlockVector, whose hash codes collide heavily; on the woodland mansion that took 1.8 s of a 2.4 s build, so making a block see-through lagged by seconds. The blocks now go into an array over the object's bounds, which brings the build down to about 50-70 ms with the same faces. Opening an object gets the same speed-up. Model used: Claude Opus 5.5 --- .../com/volmit/iris/core/gui/ObjectMesh.java | 95 +++++++++++++------ 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/gui/ObjectMesh.java b/core/src/main/java/com/volmit/iris/core/gui/ObjectMesh.java index c524852d2..5162446d6 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/ObjectMesh.java +++ b/core/src/main/java/com/volmit/iris/core/gui/ObjectMesh.java @@ -55,15 +55,15 @@ record ObjectMesh(List solidQuads, List translucentQuads, List List solid = new ArrayList<>(); List translucent = new ArrayList<>(); List topsUnderBlockAbove = new ArrayList<>(); - for (Map.Entry entry : blocks.entrySet()) { - BlockVector position = entry.getKey(); - BlockData block = entry.getValue(); - BlockModel model = modelOf.apply(block); - boolean seeThrough = isSeeThrough.test(block); - for (Face face : model.faces()) { - Quad quad = new Quad(face, position.getBlockX(), position.getBlockY(), position.getBlockZ(), - seeThrough ? SEE_THROUGH_OPACITY : FULL_OPACITY); - if (!isHiddenByNeighbour(blocks, modelOf, isSeeThrough, position, block, model, face)) { + BlockGrid grid = new BlockGrid(blocks, modelOf, bounds, isSeeThrough); + for (BlockVector position : blocks.keySet()) { + int x = position.getBlockX(); + int y = position.getBlockY(); + int z = position.getBlockZ(); + int cell = grid.cellAt(x, y, z); + for (Face face : grid.modelIn(cell).faces()) { + Quad quad = new Quad(face, x, y, z, grid.isSeeThroughIn(cell) ? SEE_THROUGH_OPACITY : FULL_OPACITY); + if (!grid.isHiddenByNeighbour(x, y, z, face)) { (quad.isBlended() ? translucent : solid).add(quad); } else if (face.cullFace() == CubeFace.UP) { topsUnderBlockAbove.add(quad); @@ -111,27 +111,68 @@ record ObjectMesh(List solidQuads, List translucentQuads, List return solidQuads.size() + translucentQuads.size(); } - // Glass next to glass and water next to water hide the side between them too, as they do in the game. A see-through - // block only hides sides of other see-through blocks, so whatever it covers shows through it. - private static boolean isHiddenByNeighbour(Map blocks, Function modelOf, - Predicate isSeeThrough, BlockVector position, BlockData block, - BlockModel model, Face face) { - CubeFace side = face.cullFace(); - if (side == null) { - return false; + /** + * The object's blocks laid out in an array over its bounds, so finding a block's neighbours is quick. Looking them + * up by {@link BlockVector} in a map is slow, since its hash codes collide a lot. + */ + private static final class BlockGrid { + private final Bounds bounds; + private final BlockData[] blocks; + private final BlockModel[] models; + private final boolean[] seeThrough; + + BlockGrid(Map placed, Function modelOf, Bounds bounds, + Predicate isSeeThrough) { + this.bounds = bounds; + int cells = bounds.sizeX() * bounds.sizeY() * bounds.sizeZ(); + this.blocks = new BlockData[cells]; + this.models = new BlockModel[cells]; + this.seeThrough = new boolean[cells]; + placed.forEach((position, block) -> { + int cell = cellAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + blocks[cell] = block; + models[cell] = modelOf.apply(block); + seeThrough[cell] = isSeeThrough.test(block); + }); } - BlockData neighbour = blocks.get(new BlockVector( - position.getBlockX() + side.stepX(), - position.getBlockY() + side.stepY(), - position.getBlockZ() + side.stepZ())); - if (neighbour == null || (isSeeThrough.test(neighbour) && !isSeeThrough.test(block))) { - return false; + + int cellAt(int x, int y, int z) { + return ((y - bounds.minY()) * bounds.sizeZ() + (z - bounds.minZ())) * bounds.sizeX() + (x - bounds.minX()); } - BlockModel neighbourModel = modelOf.apply(neighbour); - if (neighbourModel.coveredSides().contains(side.opposite())) { - return true; + + BlockModel modelIn(int cell) { + return models[cell]; + } + + boolean isSeeThroughIn(int cell) { + return seeThrough[cell]; + } + + // Glass next to glass and water next to water hide the side between them too, as they do in the game. A + // see-through block only hides sides of other see-through blocks, so whatever it covers shows through it. + boolean isHiddenByNeighbour(int x, int y, int z, Face face) { + CubeFace side = face.cullFace(); + if (side == null || !contains(x + side.stepX(), y + side.stepY(), z + side.stepZ())) { + return false; + } + int cell = cellAt(x, y, z); + int neighbourCell = cellAt(x + side.stepX(), y + side.stepY(), z + side.stepZ()); + BlockData neighbour = blocks[neighbourCell]; + if (neighbour == null || (seeThrough[neighbourCell] && !seeThrough[cell])) { + return false; + } + BlockModel neighbourModel = models[neighbourCell]; + if (neighbourModel.coveredSides().contains(side.opposite())) { + return true; + } + return neighbour.getMaterial() == blocks[cell].getMaterial() && models[cell].hidesSameBlockSides() + && neighbourModel.hidesSameBlockSides(); + } + + private boolean contains(int x, int y, int z) { + return x >= bounds.minX() && x < bounds.maxX() && y >= bounds.minY() && y < bounds.maxY() + && z >= bounds.minZ() && z < bounds.maxZ(); } - return neighbour.getMaterial() == block.getMaterial() && model.hidesSameBlockSides() && neighbourModel.hidesSameBlockSides(); } /**