All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sk89q.worldedit.function.visitor.BreadthFirstSearch Maven / Gradle / Ivy

Go to download

Blazingly fast Minecraft world manipulation for artists, builders and everyone else.

There is a newer version: 2.10.0
Show newest version
/*
 * WorldEdit, a Minecraft world manipulation toolkit
 * Copyright (C) sk89q 
 * Copyright (C) WorldEdit team and contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package com.sk89q.worldedit.function.visitor;

import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.math.BlockVectorSet;
import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent;
import com.fastasyncworldedit.core.util.ExtentTraverser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.RunContext;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Performs a breadth-first search starting from points added with
 * {@link #visit(BlockVector3)}. The search continues
 * to a certain adjacent point provided that the method
 * {@link #isVisitable(BlockVector3, BlockVector3)}
 * returns true for that point.
 *
 * 

As an abstract implementation, this class can be used to implement * functionality that starts at certain points and extends outward from * those points.

*/ public abstract class BreadthFirstSearch implements Operation { //FAWE start public static final BlockVector3[] DEFAULT_DIRECTIONS = new BlockVector3[6]; public static final BlockVector3[] DIAGONAL_DIRECTIONS; static { DEFAULT_DIRECTIONS[0] = (BlockVector3.UNIT_MINUS_Y); DEFAULT_DIRECTIONS[1] = (BlockVector3.UNIT_Y); DEFAULT_DIRECTIONS[2] = (BlockVector3.UNIT_MINUS_X); DEFAULT_DIRECTIONS[3] = (BlockVector3.UNIT_X); DEFAULT_DIRECTIONS[4] = (BlockVector3.UNIT_MINUS_Z); DEFAULT_DIRECTIONS[5] = (BlockVector3.UNIT_Z); List list = new ArrayList<>(); for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { if (x != 0 || y != 0 || z != 0) { BlockVector3 pos = BlockVector3.at(x, y, z); if (!list.contains(pos)) { list.add(pos); } } } } } list.sort((o1, o2) -> (int) Math.signum(o1.lengthSq() - o2.lengthSq())); DIAGONAL_DIRECTIONS = list.toArray(new BlockVector3[0]); } //FAWE end private final RegionFunction function; //FAWE start - allow chunk preloading and BVS > Queue, Set, List private final SingleThreadQueueExtent singleQueue; private BlockVectorSet queue = new BlockVectorSet(); private BlockVectorSet visited = new BlockVectorSet(); private BlockVector3[] directions; //FAWE end protected final int minY; protected final int maxY; private int affected = 0; //FAWE start private int currentDepth = 0; private final int maxDepth; private int maxBranch = Integer.MAX_VALUE; /** * Create a new instance. * * @param function the function to apply to visited blocks */ public BreadthFirstSearch(RegionFunction function) { //FAWE start - int depth, min/max y this(function, Integer.MAX_VALUE, WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMinY(), WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMaxY(), null ); } //FAWE start - int depth, min/max y, preloading /** * Create a new instance. * * @param function the function to apply to visited blocks * @param depth maximum number of iterations * @param minY minimum allowable y to visit. Inclusive. * @param maxY maximum allowable y to visit. Inclusive. */ public BreadthFirstSearch(RegionFunction function, int depth, int minY, int maxY) { this(function, depth, minY, maxY, null); } /** * Create a new instance. * * @param function the function to apply to visited blocks * @param maxDepth the maximum number of iterations * @param minY minimum y value to visit. Inclusive. * @param maxY maximum y value to visit. Inclusive. * @param extent extent to use for preloading */ public BreadthFirstSearch(RegionFunction function, int maxDepth, int minY, int maxY, Extent extent) { checkNotNull(function); this.function = function; this.directions = DEFAULT_DIRECTIONS; this.maxDepth = maxDepth; this.minY = minY; this.maxY = maxY; if (extent != null) { ExtentTraverser queueTraverser = new ExtentTraverser<>(extent).find(ParallelQueueExtent.class); this.singleQueue = queueTraverser != null ? (SingleThreadQueueExtent) queueTraverser.get().getExtent() : null; } else { this.singleQueue = null; } } public void setDirections(BlockVector3... directions) { this.directions = directions; } public void setDirections(Collection directions) { setDirections(directions.toArray(new BlockVector3[0])); } //FAWE end /** * Get the list of directions will be visited. * *

Directions are {@link BlockVector3}s that determine * what adjacent points area available. Vectors should not be * unit vectors. An example of a valid direction is * {@code BlockVector3.at(1, 0, 1)}.

* *

The list of directions can be cleared.

* * @return the list of directions */ public Collection getDirections() { return Arrays.asList(directions); } /** * Add the directions along the axes as directions to visit. */ public void addAxes() { //FAWE start - HS HashSet set = Sets.newHashSet(directions); set.add(BlockVector3.UNIT_MINUS_Y); set.add(BlockVector3.UNIT_Y); set.add(BlockVector3.UNIT_MINUS_X); set.add(BlockVector3.UNIT_X); set.add(BlockVector3.UNIT_MINUS_Z); set.add(BlockVector3.UNIT_Z); setDirections(set); //FAWE end } /** * Add the diagonal directions as directions to visit. */ public void addDiagonal() { //FAWE start - HS HashSet set = Sets.newHashSet(directions); set.add(Direction.NORTHEAST.toBlockVector()); set.add(Direction.SOUTHEAST.toBlockVector()); set.add(Direction.SOUTHWEST.toBlockVector()); set.add(Direction.NORTHWEST.toBlockVector()); setDirections(set); //FAWE end } /** * Add the given location to the list of locations to visit, provided * that it has not been visited. The position passed to this method * will still be visited even if it fails * {@link #isVisitable(BlockVector3, BlockVector3)} as * {@link #isVisitable(BlockVector3, BlockVector3)} is never actually * called. * *

This method should be used before the search begins, because if * the position does fail the test, and the search has already * visited it (because it is connected to another root point), * the search will mark the position as "visited" and a call to this * method will do nothing.

* * @param position the position */ public void visit(BlockVector3 position) { if (!visited.contains(position)) { queue.add(position); visited.add(position); } } /** * Try to visit the given 'to' location. * * @param from the origin block * @param to the block under question */ private void visit(BlockVector3 from, BlockVector3 to) { if (!visited.contains(to)) { visited.add(to); if (isVisitable(from, to)) { queue.add(to); } } } //FAWE start public void setVisited(BlockVectorSet set) { this.visited = set; } public BlockVectorSet getVisited() { return visited; } public boolean isVisited(BlockVector3 pos) { return visited.contains(pos); } public void setMaxBranch(int maxBranch) { this.maxBranch = maxBranch; } //FAWE end /** * Return whether the given 'to' block should be visited, starting from the * 'from' block. * * @param from the origin block * @param to the block under question * @return true if the 'to' block should be visited */ protected abstract boolean isVisitable(BlockVector3 from, BlockVector3 to); /** * Get the number of affected objects. * * @return the number of affected */ public int getAffected() { return affected; } @Override public Operation resume(RunContext run) throws WorldEditException { //FAWE start - directions, visited and preloading MutableBlockVector3 mutable = new MutableBlockVector3(); BlockVector3[] dirs = directions; BlockVectorSet tempQueue = new BlockVectorSet(); BlockVectorSet chunkLoadSet = new BlockVectorSet(); for (currentDepth = 0; !queue.isEmpty() && currentDepth <= maxDepth; currentDepth++) { int loadCount = 0; if (singleQueue != null && Settings.settings().QUEUE.PRELOAD_CHUNK_COUNT > 1) { int cx = Integer.MIN_VALUE; int cz = Integer.MIN_VALUE; outer: for (BlockVector3 from : queue) { for (BlockVector3 direction : dirs) { if (loadCount > Settings.settings().QUEUE.PRELOAD_CHUNK_COUNT) { break outer; } int x = from.getBlockX() + direction.getBlockX(); int z = from.getBlockZ() + direction.getBlockX(); if (cx != (cx = x >> 4) || cz != (cz = z >> 4)) { int y = from.getBlockY() + direction.getBlockY(); if (y < singleQueue.getMinY() || y > singleQueue.getMaxY()) { continue; } if (!visited.contains(x, y, z)) { loadCount++; chunkLoadSet.add(cx, 0, cz); } } } } for (BlockVector3 chunk : chunkLoadSet) { singleQueue.addChunkLoad(chunk.getBlockX(), chunk.getBlockZ()); } } for (BlockVector3 from : queue) { if (function.apply(from)) { affected++; } for (int i = 0, j = 0; i < dirs.length && j < maxBranch; i++) { BlockVector3 direction = dirs[i]; int y = from.getBlockY() + direction.getY(); if (y < minY || y > maxY) { continue; } int x = from.getBlockX() + direction.getX(); int z = from.getBlockZ() + direction.getZ(); if (!visited.contains(x, y, z)) { if (isVisitable(from, mutable.setComponents(x, y, z))) { j++; visited.add(x, y, z); tempQueue.add(x, y, z); } } } } if (currentDepth == maxDepth) { break; } BlockVectorSet tmp = queue; queue = tempQueue; tmp.clear(); tempQueue = tmp; } //FAWE end return null; } //FAWE start public int getDepth() { return currentDepth; } //FAWE end @Override public void cancel() { //FAWE start queue.clear(); visited.clear(); affected = 0; //FAWE emd } @Override public Iterable getStatusMessages() { return ImmutableList.of(Caption.of( "worldedit.operation.affected.block", TextComponent.of(getAffected()) )); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy