diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/ExpandableList.java b/src/main/java/com/minelittlepony/unicopia/block/state/ExpandableList.java new file mode 100644 index 00000000..3262ed9f --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/ExpandableList.java @@ -0,0 +1,45 @@ +package com.minelittlepony.unicopia.block.state; + +import java.util.Arrays; +import java.util.function.Supplier; + +class ExpandableList { + private final Supplier defaultValue; + private Object[] values; + private int size; + + public ExpandableList(int initialCapacity, Supplier defaultValue) { + this.defaultValue = defaultValue; + values = new Object[Math.max(0, initialCapacity)]; + } + + public int size() { + return size; + } + + @SuppressWarnings("unchecked") + public T get(int index) { + T t = (T)values[index]; + if (t == null) { + values[index] = t = defaultValue.get(); + } + return t; + } + + public T getOrExpand(int index) { + resize(index); + return get(index); + } + + public void set(int index, T value) { + resize(index); + values[index] = value; + } + + private void resize(int index) { + if (index >= values.length) { + values = Arrays.copyOf(values, Math.max(index + 1, (values.length + 1) * 2)); + } + size = Math.max(index + 1, size); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/Schematic.java b/src/main/java/com/minelittlepony/unicopia/block/state/Schematic.java new file mode 100644 index 00000000..2053b372 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/Schematic.java @@ -0,0 +1,72 @@ +package com.minelittlepony.unicopia.block.state; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.network.PacketByteBuf; + +public record Schematic(int dx, int dy, int dz, Entry[] states) { + public static Schematic fromPacket(PacketByteBuf buffer) { + Builder builder = new Builder(); + buffer.readCollection(ArrayList::new, buf -> { + byte op = buf.readByte(); + return switch (op) { + case 1 -> builder.set(buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); + case 2 -> builder.fill(buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); + default -> builder; + }; + }); + return builder.build(); + } + + public int volume() { + return states.length; + } + + public static final class Builder { + private static final BlockState AIR = Blocks.AIR.getDefaultState(); + + private int dx = -1; + private int dy = -1; + private int dz = -1; + private final ExpandableList>> layers = new ExpandableList<>(0, () -> new ExpandableList<>(dz, () -> new ExpandableList<>(dx, () -> AIR))); + + public Builder set(int x, int y, int z, BlockState state) { + dx = Math.max(dx, x); + dy = Math.max(dy, y); + dz = Math.max(dz, z); + layers.getOrExpand(y).getOrExpand(z).set(x, state); + return this; + } + + public Builder fill(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state) { + dx = Math.max(dx, maxX); + dy = Math.max(dy, maxY); + dz = Math.max(dz, maxZ); + for (int x = minX; x <= maxX; x++) + for (int y = minY; y <= maxY; y++) + for (int z = minZ; z <= maxZ; z++) + set(x, y, z, state); + return this; + } + + public Schematic build() { + List states = new LinkedList<>(); + for (int y = 0; y <= dy && y < layers.size(); y++) + for (int z = 0; z <= dz && z < layers.get(y).size(); z++) + for (int x = 0; x <= dx && x < layers.get(y).get(z).size(); x++) { + BlockState state = layers.get(y).get(z).get(x); + if (!state.isAir()) { + states.add(new Entry(x, y, z, state)); + } + } + + return new Schematic(dx, dy, dz, states.toArray(Entry[]::new)); + } + } + + public record Entry(int x, int y, int z, BlockState state) {} +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java index 8185da9f..21802353 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java @@ -10,6 +10,7 @@ import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.block.state.Schematic; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Drawable; import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow; import net.minecraft.client.gui.DrawContext; @@ -59,7 +60,7 @@ public interface PageElement extends Drawable { default -> throw new IllegalArgumentException("Unexpected value: " + t); }; })); - case 5 -> new Structure.Builder().fromBuffer(buffer).build(); + case 5 -> new Structure(Bounds.empty(), Schematic.fromPacket(buffer)); default -> throw new IllegalArgumentException("Unexpected value: " + type); }; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java index a29ea8f3..a4690b5b 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java @@ -1,15 +1,8 @@ package com.minelittlepony.unicopia.client.gui.spellbook.element; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.dimension.Bounds; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; +import com.minelittlepony.unicopia.block.state.Schematic; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.render.DiffuseLighting; @@ -17,48 +10,13 @@ import net.minecraft.client.render.LightmapTextureManager; import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.state.property.Properties; -import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; -public record Structure(Bounds bounds, List>> states) implements PageElement { - static final BlockState DIAMOND = Blocks.DIAMOND_BLOCK.getDefaultState(); - static final BlockState AIR = Blocks.AIR.getDefaultState(); - static final BlockState OBS = Blocks.OBSIDIAN.getDefaultState(); - static final BlockState SOU = Blocks.SOUL_SAND.getDefaultState(); - public static final Structure CRYSTAL_HEART_ALTAR = new Structure.Builder() - .fill(0, 0, 0, 2, 0, 2, DIAMOND) - .set(1, 1, 1, DIAMOND) - .set(1, 2, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.UP)) - .set(1, 4, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.DOWN)) - .set(1, 5, 1, DIAMOND) - .fill(0, 6, 0, 2, 6, 2, DIAMOND) - .build(); - public static final Structure ALTAR_STRUCTURE = new Structure.Builder() - .fill(0, 0, 0, 8, 0, 8, SOU) - .fill(3, 1, 3, 5, 1, 5, OBS) - .set(4, 1, 4, SOU) - .set(4, 1, 6, Blocks.LODESTONE.getDefaultState()) - .fill(0, 1, 2, 0, 4, 2, OBS).fill(0, 1, 6, 0, 4, 6, OBS) - .fill(2, 1, 0, 2, 4, 0, OBS).fill(6, 1, 0, 6, 4, 0, OBS) - .fill(8, 1, 2, 8, 4, 2, OBS).fill(8, 1, 6, 8, 4, 6, OBS) - .fill(2, 1, 8, 2, 4, 8, OBS).fill(6, 1, 8, 6, 4, 8, OBS) - .build(); - +public record Structure(Bounds bounds, Schematic schematic) implements PageElement { @Override public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { - int height = states.size(); - if (height == 0) { - return; - } - int depth = states.get(0).size(); - if (depth == 0) { - return; - } - int width = states.get(0).get(0).size(); - if (width == 0) { + if (schematic.volume() == 0) { return; } MatrixStack matrices = context.getMatrices(); @@ -72,110 +30,24 @@ public record Structure(Bounds bounds, List>> states) impl if (container != null) { matrices.translate(container.getBounds().width / 2, container.getBounds().height / 2, 100); float minDimensions = Math.min(container.getBounds().width, container.getBounds().height) - 30; - int minSize = Math.max(height, Math.max(width, depth)) * 16; + int minSize = (Math.max(schematic.dx(), Math.max(schematic.dy(), schematic.dz())) + 1) * 16; float scale = minDimensions / minSize; matrices.scale(scale, scale, 1); } - matrices.scale(-16, -16, -16); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-20 + MathHelper.sin(tickDelta / 10F) * 2)); + matrices.scale(16, -16, 16); + matrices.peek().getNormalMatrix().scale(1, -1, 1); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(20 + MathHelper.sin(tickDelta / 10F) * 2)); matrices.peek().getPositionMatrix().rotate(RotationAxis.POSITIVE_Y.rotationDegrees(age)); - DiffuseLighting.enableForLevel(matrices.peek().getPositionMatrix()); + matrices.translate((-schematic.dx() - 1) / 2F, (-schematic.dy() - 1) / 2F, (-schematic.dz() - 1) / 2F); + DiffuseLighting.disableGuiDepthLighting(); - matrices.translate(-width / 2F, -height / 2F, -depth / 2F); - - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) - for (int z = 0; z < depth; z++) { - BlockState state = states.get(y).get(z).get(x); + for (var entry : schematic.states()) { matrices.push(); - matrices.translate(x, y, z); - client.getBlockRenderManager().renderBlockAsEntity(state, matrices, immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV); + matrices.translate(entry.x(), entry.y(), entry.z()); + client.getBlockRenderManager().renderBlockAsEntity(entry.state(), matrices, immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV); matrices.pop(); } matrices.pop(); } - - public static class Builder { - private int dx = -1; - private int dy = -1; - private int dz = -1; - private final List>> layers = new ArrayList<>(); - - private void resize(int x, int y, int z) { - int ddx = Math.max(dx, x); - int ddy = Math.max(dy, y); - int ddz = Math.max(dz, z); - - if (ddx <= dx && ddy <= dy && ddz <= dz) { - return; - } - - if (ddy > dy) { - dy = ddy; - while (layers.size() <= ddy) { - layers.add(createFilledList(ddz, () -> createFilledList(ddx, () -> AIR))); - } - } - if (ddz > dz || ddx > dx) { - layers.forEach(layer -> { - if (ddz > dz) { - while (layer.size() <= ddz) { - layer.add(createFilledList(ddx, () -> AIR)); - } - } - if (ddx > dx) { - layer.forEach(row -> { - while (row.size() <= ddx) { - row.add(AIR); - } - }); - } - }); - dz = ddz; - dx = ddx; - } - } - - private List createFilledList(int length, Supplier contentSupplier) { - List list = new ArrayList<>(); - while (list.size() <= length) { - list.add(contentSupplier.get()); - } - return list; - } - - public Builder set(int x, int y, int z, BlockState state) { - resize(x, y, z); - layers.get(y).get(z).set(x, state); - return this; - } - - public Builder fill(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state) { - resize(maxX, maxY, maxZ); - for (int x = minX; x <= maxX; x++) - for (int y = minY; y <= maxY; y++) - for (int z = minZ; z <= maxZ; z++) - layers.get(y).get(z).set(x, state); - return this; - } - - public Builder fromBuffer(PacketByteBuf buffer) { - - buffer.readCollection(ArrayList::new, buf -> { - byte op = buf.readByte(); - return switch (op) { - case 1 -> set(buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); - case 2 -> fill(buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); - default -> this; - }; - }); - - return this; - } - - public Structure build() { - return new Structure(Bounds.empty(), layers); - } - } }