diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Cuboid2Command.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Cuboid2Command.java new file mode 100644 index 00000000..68464b3f --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Cuboid2Command.java @@ -0,0 +1,72 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import de.steamwar.bausystem.shared.Pair; +import de.steamwar.bausystem.utils.WorldEditUtils; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; + +@Linked +public class Cuboid2Command extends SWCommand { + + public Cuboid2Command() { + super("cuboid"); + } + + @Register + public void command(Player player) { + Pair selection = WorldEditUtils.getSelection(player); + if (selection == null) { + player.sendMessage("§cDu musst erst eine Auswahl treffen!"); + return; + } + Location min = selection.getKey(); + Location max = selection.getValue(); + + Vector minVec = new Vector(Math.min(min.getX(), max.getX()), Math.min(min.getY(), max.getY()), Math.min(min.getZ(), max.getZ())); + Vector maxVec = new Vector(Math.max(min.getX(), max.getX()), Math.max(min.getY(), max.getY()), Math.max(min.getZ(), max.getZ())); + + CuboidSchematicWriter cuboidSchematicWriter = new CuboidSchematicWriter(minVec, maxVec); + cuboidSchematicWriter.create(player, cuboidSchematic -> { + AtomicInteger counter = new AtomicInteger(); + try { + cuboidSchematic.write(new GZIPOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + counter.incrementAndGet(); + } + })); + } catch (IOException e) { + throw new RuntimeException(e); + } + player.sendMessage("§aDie Schematic ist " + counter.get() + " Bytes groß!"); + System.out.println(cuboidSchematic); + }); + } +} diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidCommand.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidCommand.java new file mode 100644 index 00000000..6f33f813 --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidCommand.java @@ -0,0 +1,200 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.shared.Pair; +import de.steamwar.bausystem.utils.WorldEditUtils; +import de.steamwar.command.SWCommand; +import lombok.ToString; +import org.bukkit.Axis; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.function.Supplier; + +// @Linked +public class CuboidCommand extends SWCommand { + + public CuboidCommand() { + super("cuboid"); + } + + @Register + public void genericCommand(Player player) { + Pair selection = WorldEditUtils.getSelection(player); + if (selection == null) { + player.sendMessage("§cDu musst erst eine Auswahl treffen!"); + return; + } + Location min = selection.getKey(); + Location max = selection.getValue(); + + Vector minVec = new Vector(Math.min(min.getX(), max.getX()), Math.min(min.getY(), max.getY()), Math.min(min.getZ(), max.getZ())); + Vector maxVec = new Vector(Math.max(min.getX(), max.getX()), Math.max(min.getY(), max.getY()), Math.max(min.getZ(), max.getZ())); + + Map blocks = new HashMap<>(); + for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { + for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + blocks.put(new Vector(x, y, z), player.getWorld().getBlockAt(x, y, z).getType()); + } + } + } + + List left = new ArrayList<>(); + left.add(minVec); + + long time = System.currentTimeMillis(); + List cuboids = new ArrayList<>(); + runTickEfficient(left::isEmpty, () -> { + System.out.println(left.size()); + Vector current = left.remove(0); + if (current.getX() > maxVec.getX() || current.getY() > maxVec.getY() || current.getZ() > maxVec.getZ()) { + return; + } + + BlockCuboid cuboid = new BlockCuboid(current, blocks.get(current)); + expand(cuboid, maxVec, blocks); + cuboids.add(cuboid); + + left.add(new Vector(cuboid.min.getX(), cuboid.min.getY(), cuboid.max.getZ() + 1)); + left.add(new Vector(cuboid.min.getX(), cuboid.max.getY() + 1, cuboid.min.getZ())); + left.add(new Vector(cuboid.max.getX() + 1, cuboid.min.getY(), cuboid.min.getZ())); + }, () -> { + System.out.println((System.currentTimeMillis() - time) + " ms " + cuboids); + }); + } + + private void runTickEfficient(Supplier finished, Runnable next, Runnable finishedCallback) { + new BukkitRunnable() { + @Override + public void run() { + if (finished.get()) { + cancel(); + finishedCallback.run(); + return; + } + long time = System.currentTimeMillis(); + while (System.currentTimeMillis() - time < 50) { + if (finished.get()) { + cancel(); + finishedCallback.run(); + return; + } + next.run(); + } + } + }.runTaskTimer(BauSystem.getInstance(), 0, 1); + } + + @ToString + private static class BlockCuboid { + private final Vector min; + private final Vector max; + private final Material material; + + public BlockCuboid(Vector min, Material material) { + this.min = min.clone(); + this.max = min.clone(); + this.material = material; + } + } + + private void expand(BlockCuboid cuboid, Vector max, Map blocks) { + List axes = new ArrayList<>(); + axes.add(Axis.X); + axes.add(Axis.Y); + axes.add(Axis.Z); + do { + Vector sizes = cuboid.max.clone().subtract(cuboid.min).add(new Vector(1, 1, 1)); + axes.sort(Comparator.comparing(axis -> { + switch (axis) { + case X: + return sizes.getX(); + case Y: + return sizes.getY(); + case Z: + return sizes.getZ(); + default: + return 0.0; + } + })); + axes.removeIf(axis -> { + Vector direction = new Vector(); + switch (axis) { + case X: + direction.setX(1); + break; + case Y: + direction.setY(1); + break; + case Z: + direction.setZ(1); + break; + } + return !expand(cuboid, max, direction, blocks); + }); + } while (!axes.isEmpty()); + } + + private boolean expand(BlockCuboid cuboid, Vector max, Vector direction, Map blocks) { + if (direction.getX() == 1) { + if (cuboid.max.getX() + 1 > max.getX()) { + return false; + } + for (double y = cuboid.min.getY(); y <= cuboid.max.getY(); y++) { + for (double z = cuboid.min.getZ(); z <= cuboid.max.getZ(); z++) { + if (blocks.get(new Vector(cuboid.max.getX() + 1, y, z)) != cuboid.material) { + return false; + } + } + } + } else if (direction.getY() == 1) { + if (cuboid.max.getY() + 1 > max.getY()) { + return false; + } + for (double x = cuboid.min.getX(); x <= cuboid.max.getX(); x++) { + for (double z = cuboid.min.getZ(); z <= cuboid.max.getZ(); z++) { + if (blocks.get(new Vector(x, cuboid.max.getY() + 1, z)) != cuboid.material) { + return false; + } + } + } + } else if (direction.getZ() == 1) { + if (cuboid.max.getZ() + 1 > max.getZ()) { + return false; + } + for (double x = cuboid.min.getX(); x <= cuboid.max.getX(); x++) { + for (double y = cuboid.min.getY(); y <= cuboid.max.getY(); y++) { + if (blocks.get(new Vector(x, y, cuboid.max.getZ() + 1)) != cuboid.material) { + return false; + } + } + } + } + cuboid.max.add(direction); + return true; + } +} diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematic.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematic.java new file mode 100644 index 00000000..5505d15a --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematic.java @@ -0,0 +1,94 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import com.sk89q.jnbt.*; +import lombok.Getter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Getter +public class CuboidSchematic { + private Pos offset; + private Pos size; + private Map> cuboids = new HashMap<>(); + + public CuboidSchematic(Pos offset, Pos size) { + this.offset = offset; + this.size = size; + } + + public void addCuboid(TypedCuboid cuboid) { + cuboids.computeIfAbsent(cuboid.blockData, s -> new ArrayList<>()).add(cuboid); + } + + public void write(OutputStream outputStream) { + Map schematic = new HashMap<>(); + schematic.put("OffsetX", new IntTag(offset.x)); + schematic.put("OffsetY", new IntTag(offset.y)); + schematic.put("OffsetZ", new IntTag(offset.z)); + schematic.put("SizeX", new IntTag(size.x)); + schematic.put("SizeY", new IntTag(size.y)); + schematic.put("SizeZ", new IntTag(size.z)); + + Map cuboidMap = new HashMap<>(); + for (Map.Entry> entry : cuboids.entrySet()) { + byte[] cuboidArray = new byte[entry.getValue().size() * 15]; + for (int i = 0; i < entry.getValue().size(); i++) { + TypedCuboid cuboid = entry.getValue().get(i); + List bytes = new ArrayList<>(); + writeVarInt(cuboid.x, bytes); + writeVarInt(cuboid.y, bytes); + writeVarInt(cuboid.z, bytes); + writeVarInt(cuboid.dx, bytes); + writeVarInt(cuboid.dy, bytes); + writeVarInt(cuboid.dz, bytes); + } + cuboidMap.put(entry.getKey(), new ByteArrayTag(cuboidArray)); + } + schematic.put("Cuboids", new CompoundTag(cuboidMap)); + + CompoundTag compoundTag = new CompoundTag(schematic); + try { + NBTOutputStream nbtOutputStream = new NBTOutputStream(outputStream); + nbtOutputStream.writeTag(compoundTag); + nbtOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void writeVarInt(int value, List bytes) { + while (true) { + if (value < 128) { + bytes.add((byte) value); + return; + } else { + bytes.add((byte) (value & 127 | 128)); + value >>>= 7; + } + } + } +} diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematicWriter.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematicWriter.java new file mode 100644 index 00000000..7ca835c4 --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/CuboidSchematicWriter.java @@ -0,0 +1,199 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import de.steamwar.bausystem.BauSystem; +import org.bukkit.Axis; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Consumer; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class CuboidSchematicWriter { + + private static final List AXES = Arrays.asList( + new Axis[]{Axis.X, Axis.Y, Axis.Z}, + new Axis[]{Axis.X, Axis.Z, Axis.Y}, + new Axis[]{Axis.Y, Axis.X, Axis.Z}, + new Axis[]{Axis.Y, Axis.Z, Axis.X}, + new Axis[]{Axis.Z, Axis.X, Axis.Y}, + new Axis[]{Axis.Z, Axis.Y, Axis.X} + ); + + private Pos min; + private Pos max; + + public CuboidSchematicWriter(Vector min, Vector max) { + this.min = new Pos(min); + this.max = new Pos(max); + } + + public void create(Player player, Consumer consumer) { + // Setup + Pos playerPos = new Pos(player.getLocation()); + Pos offset = min.sub(playerPos); + Map blocks = CuboidSchematicWriter.getBlocks(player, min, max); + Map> reverseBlocks = reverse(blocks); + + // Create CuboidSchematic + CuboidSchematic schematic = new CuboidSchematic(offset, max.sub(min)); + + // Create Cuboids + long time = System.currentTimeMillis(); + List left = new ArrayList<>(); + left.add(new Pos(0, 0, 0)); + runTickEfficient(left::isEmpty, () -> { + Pos current = left.remove(0); + String block = blocks.remove(current); + if (block == null) { + return; + } + + TypedCuboid cuboid = null; + for (Axis[] axes : AXES) { + TypedCuboid currentCuboid = new TypedCuboid(current.x, current.y, current.z, block); + expand(currentCuboid, new ArrayList<>(Arrays.asList(axes)), reverseBlocks); + if (cuboid == null || currentCuboid.size() > cuboid.size()) { + cuboid = currentCuboid; + } + } + Set cuboidBlocks = reverseBlocks.get(block); + for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) { + for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) { + for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) { + Pos pos = new Pos(x, y, z); + cuboidBlocks.remove(pos); + blocks.remove(pos); + } + } + } + schematic.addCuboid(cuboid); + + left.add(new Pos(current.x + cuboid.dx + 1, current.y, current.z)); + left.add(new Pos(current.x, current.y + cuboid.dy + 1, current.z)); + left.add(new Pos(current.x, current.y, current.z + cuboid.dz + 1)); + }, () -> { + long elapsed = System.currentTimeMillis() - time; + long cuboids = schematic.getCuboids().values().stream().mapToLong(List::size).sum(); + System.out.println("Finished in " + elapsed + "ms with " + cuboids + " cuboids"); + System.out.println("Average: " + (cuboids / elapsed) + " cuboids per ms"); + System.out.println("Types: " + schematic.getCuboids().entrySet().stream().map(e -> e.getKey() + ": " + e.getValue().size()).collect(Collectors.joining(", "))); + consumer.accept(schematic); + }); + } + + private void runTickEfficient(Supplier finished, Runnable next, Runnable finishedCallback) { + new BukkitRunnable() { + @Override + public void run() { + long time = System.currentTimeMillis(); + while (System.currentTimeMillis() - time < 50) { + if (finished.get()) { + cancel(); + finishedCallback.run(); + return; + } + next.run(); + } + } + }.runTaskTimer(BauSystem.getInstance(), 0, 1); + } + + private static Map getBlocks(Player player, Pos min, Pos max) { + Map blocks = new HashMap<>(); + for (int x = min.x; x <= max.x; x++) { + for (int y = min.y; y <= max.y; y++) { + for (int z = min.z; z <= max.z; z++) { + blocks.put(new Pos(x - min.x, y - min.y, z - min.z), player.getWorld().getBlockAt(x, y, z).getBlockData().getAsString()); + } + } + } + return blocks; + } + + private static Map> reverse(Map input) { + Map> output = new HashMap<>(); + for (Map.Entry entry : input.entrySet()) { + output.computeIfAbsent(entry.getValue(), k -> new HashSet<>()).add(entry.getKey()); + } + return output; + } + + private static void expand(TypedCuboid cuboid, List axes, Map> reverseBlocks) { + while (!axes.isEmpty()) { + axes.removeIf(axis -> { + Vector direction = new Vector(); + switch (axis) { + case X: + direction.setX(1); + break; + case Y: + direction.setY(1); + break; + case Z: + direction.setZ(1); + break; + } + return !expand(cuboid, direction, reverseBlocks); + }); + } + } + + private static boolean expand(TypedCuboid cuboid, Vector direction, Map> reverseBlocks) { + Set blocks = reverseBlocks.get(cuboid.blockData); + if (direction.getX() == 1) { + for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) { + for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) { + if (!blocks.contains(new Pos(cuboid.x + cuboid.dx + 1, y, z))) { + return false; + } + } + } + } else if (direction.getY() == 1) { + for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) { + for (int z = cuboid.z; z < cuboid.z + cuboid.dz; z++) { + if (!blocks.contains(new Pos(x, cuboid.y + cuboid.dy + 1, z))) { + return false; + } + } + } + } else if (direction.getZ() == 1) { + for (int x = cuboid.x; x < cuboid.x + cuboid.dx; x++) { + for (int y = cuboid.y; y < cuboid.y + cuboid.dy; y++) { + if (!blocks.contains(new Pos(x, y, cuboid.z + cuboid.dz + 1))) { + return false; + } + } + } + } + if (direction.getX() == 1) { + cuboid.dx++; + } else if (direction.getY() == 1) { + cuboid.dy++; + } else if (direction.getZ() == 1) { + cuboid.dz++; + } + return true; + } +} diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Pos.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Pos.java new file mode 100644 index 00000000..e746d750 --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/Pos.java @@ -0,0 +1,67 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +@ToString +@EqualsAndHashCode +public class Pos { + public final int x; + public final int y; + public final int z; + + public Pos(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Pos(Location loc) { + this.x = loc.getBlockX(); + this.y = loc.getBlockY(); + this.z = loc.getBlockZ(); + } + + public Pos(Vector vec) { + this.x = vec.getBlockX(); + this.y = vec.getBlockY(); + this.z = vec.getBlockZ(); + } + + public Pos add(int x, int y, int z) { + return new Pos(this.x + x, this.y + y, this.z + z); + } + + public Pos add(Pos pos) { + return new Pos(this.x + pos.x, this.y + pos.y, this.z + pos.z); + } + + public Pos sub(int x, int y, int z) { + return new Pos(this.x - x, this.y - y, this.z - z); + } + + public Pos sub(Pos pos) { + return new Pos(this.x - pos.x, this.y - pos.y, this.z - pos.z); + } +} diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/TypedCuboid.java b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/TypedCuboid.java new file mode 100644 index 00000000..55afd8c6 --- /dev/null +++ b/BauSystem_Main/src/de/steamwar/bausystem/features/cuboid/TypedCuboid.java @@ -0,0 +1,71 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.cuboid; + +import lombok.ToString; + +@ToString +public class TypedCuboid { + + public int x; + public int y; + public int z; + public int dx; + public int dy; + public int dz; + + public String blockData; + + public TypedCuboid(int x, int y, int z, String blockData) { + this.x = x; + this.y = y; + this.z = z; + + this.dx = 1; + this.dy = 1; + this.dz = 1; + + this.blockData = blockData; + } + + public boolean intersects(TypedCuboid cuboid) { + int minx = x - cuboid.dx; + int miny = y - cuboid.dy; + int minz = z - cuboid.dz; + int maxx = minx + dx + cuboid.dx; + int maxy = miny + dy + cuboid.dy; + int maxz = minz + dz + cuboid.dz; + return maxx > cuboid.x && maxy > cuboid.y && maxz > cuboid.z && minx < cuboid.x && miny < cuboid.y && minz < cuboid.z; + } + + public boolean intersects(Pos pos) { + int minx = x - dx; + int miny = y - dy; + int minz = z - dz; + int maxx = minx + dx; + int maxy = miny + dy; + int maxz = minz + dz; + return maxx > pos.x && maxy > pos.y && maxz > pos.z && minx < pos.x && miny < pos.y && minz < pos.z; + } + + public long size() { + return (long) dx * (long) dy * (long) dz; + } +}