From 268f450ee80ec4d35de3eff36f81ffd73281a25e Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 22 Mar 2026 15:21:27 +0100 Subject: [PATCH] Add initial MultiTileArea --- .../region/dynamic/MultiTileArea.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java diff --git a/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java new file mode 100644 index 00000000..b8a7ed39 --- /dev/null +++ b/BauSystem/BauSystem_RegionDynamic/src/de/steamwar/bausystem/region/dynamic/MultiTileArea.java @@ -0,0 +1,188 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 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.region.dynamic; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import de.steamwar.bausystem.region.Point; +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.shared.Pair; +import lombok.NonNull; +import org.bukkit.Location; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiConsumer; + +public abstract class MultiTileArea implements Region.Area { + + private final Set tiles = new HashSet<>(); + + public boolean addTile(Tile tile) { + boolean result = tiles.add(tile); + quantize(); + return result; + } + + public boolean removeTile(Tile tile) { + boolean result = tiles.remove(tile); + quantize(); + return result; + } + + public void quantize() { + long time = System.currentTimeMillis(); + Quantizer quantizer = new Quantizer(tiles); + List> results = new ArrayList<>(); + while (true) { + Pair pair = quantizer.next(); + if (pair == null) break; + results.add(pair); + } + // [{"min_tile_x":0,"min_tile_z":0,"max_tile_x":0,"max_tile_z":0}] + time = System.currentTimeMillis() - time; + System.out.println(tiles.size() + ": " + results.size() + " in " + time + "ms"); + } + + @Override + public @NonNull Point getMinPoint(boolean extension) { + return Point.ZERO; + } + + @Override + public @NonNull Point getMaxPoint(boolean extension) { + return Point.ZERO; + } + + @Override + public @NonNull Point getCopyPoint() { + return Point.ZERO; + } + + @Override + public boolean inRegion(Location location, boolean extension) { + return inRegion(location.getBlockX(), location.getBlockZ(), extension); + } + + @Override + public boolean inRegion(int x, int z, boolean extension) { + Tile tile = Tile.fromXZ(x, z).orElse(null); + return tiles.contains(tile); + } + + @Override + public @Nullable Clipboard copy(boolean extension) { + return null; + } + + @Override + public void forEachChunk(BiConsumer executor) { + } + + @Override + public boolean isChunkOutside(int chunkX, int chunkZ) { + return true; + } + + private static class Quantizer { + + private Map tileToNeighbourCount = new HashMap<>(); + + public Quantizer(Set tiles) { + for (Tile tile : tiles) { + int count = (int) tile.neighboursRing() + .filter(tiles::contains) + .count(); + tileToNeighbourCount.put(tile, count); + } + } + + public Pair next() { + Map.Entry entry = tileToNeighbourCount.entrySet() + .stream() + .max(Map.Entry.comparingByValue()) + .orElse(null); + if (entry == null) return null; + + Tile minTile = entry.getKey(); + Tile maxTile = entry.getKey(); + tileToNeighbourCount.remove(entry.getKey()); + + while (true) { + Pair, Integer> neg_x = tryExpand(minTile, maxTile, -1, 0); + Pair, Integer> pos_x = tryExpand(minTile, maxTile, 1, 0); + Pair, Integer> neg_z = tryExpand(minTile, maxTile, 0, -1); + Pair, Integer> pos_z = tryExpand(minTile, maxTile, 0, 1); + + if (neg_x.getValue() == 0 && pos_x.getValue() == 0 && neg_z.getValue() == 0 && pos_z.getValue() == 0) { + break; + } + + Set remove; + if (neg_x.getValue() >= pos_x.getValue() && neg_x.getValue() >= neg_z.getValue() && neg_x.getValue() >= pos_z.getValue()) { + minTile = minTile.add(-1, 0).orElseThrow(); + remove = neg_x.getKey(); + } else if (pos_x.getValue() >= neg_x.getValue() && pos_x.getValue() >= neg_z.getValue() && pos_x.getValue() >= pos_z.getValue()) { + maxTile = maxTile.add(1, 0).orElseThrow(); + remove = pos_x.getKey(); + } else if (neg_z.getValue() >= neg_x.getValue() && neg_z.getValue() >= pos_x.getValue() && neg_z.getValue() >= pos_z.getValue()) { + minTile = minTile.add(0, -1).orElseThrow(); + remove = neg_z.getKey(); + } else { + maxTile = maxTile.add(0, 1).orElseThrow(); + remove = pos_z.getKey(); + } + + remove.forEach(tileToNeighbourCount::remove); + } + + return new Pair<>(minTile, minTile.equals(maxTile) ? null : maxTile); + } + + private static final Pair, Integer> EMPTY = new Pair<>(Collections.emptySet(), 0); + + private Pair, Integer> tryExpand(Tile minTile, Tile maxTile, int dx, int dz) { + Set expaned = new HashSet<>(); + if (dx == 0) { + for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) { + int z = (dz < 0 ? minTile.getTileZ() : maxTile.getTileZ()) + dz; + Tile tile = Tile.fromTile(x, z).orElse(null); + if (tile == null) return EMPTY; + if (!tileToNeighbourCount.containsKey(tile)) return EMPTY; + expaned.add(tile); + } + } else if (dz == 0) { + for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) { + int x = (dx < 0 ? minTile.getTileX() : maxTile.getTileX()) + dx; + Tile tile = Tile.fromTile(x, z).orElse(null); + if (tile == null) return EMPTY; + if (!tileToNeighbourCount.containsKey(tile)) return EMPTY; + expaned.add(tile); + } + } else { + throw new IllegalStateException(); + } + + int total = expaned.stream() + .mapToInt(tileToNeighbourCount::get) + .sum(); + return new Pair<>(expaned, total); + } + } +}