From f5f326bf89abfd36f67cef34632c0f13a147ab00 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Wed, 21 Dec 2016 05:44:01 +1100 Subject: [PATCH] Better mask parsing + optimize mask union/intersect --- core/src/main/java/com/boydti/fawe/Fawe.java | 4 +- .../boydti/fawe/object/mask/AngleMask.java | 25 ++-- .../extension/factory/DefaultMaskParser.java | 68 +++++---- .../function/mask/MaskIntersection.java | 129 ++++++++++++++++++ .../worldedit/function/mask/MaskUnion.java | 83 +++++++++++ .../sk89q/worldedit/function/mask/Masks.java | 4 + 6 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 core/src/main/java/com/sk89q/worldedit/function/mask/MaskIntersection.java create mode 100644 core/src/main/java/com/sk89q/worldedit/function/mask/MaskUnion.java diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index d12d7ea5..5d88b3b6 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -57,6 +57,7 @@ import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.function.entity.ExtentEntityCopy; import com.sk89q.worldedit.function.mask.BlockMask; import com.sk89q.worldedit.function.mask.FuzzyBlockMask; +import com.sk89q.worldedit.function.mask.MaskUnion; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.mask.OffsetMask; import com.sk89q.worldedit.function.mask.SolidBlockMask; @@ -408,7 +409,8 @@ public class Fawe { FuzzyBlockMask.inject(); // Optimizations OffsetMask.inject(); // Optimizations DefaultMaskParser.inject(); // Add new masks - Masks.inject(); // + Masks.inject(); // Optimizations + MaskUnion.inject(); // Optimizations // Operations Operations.inject(); // Optimizations ForwardExtentCopy.inject(); // Fixes + optimizations diff --git a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java index bdb9afd7..ea1b470c 100644 --- a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java +++ b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java @@ -48,17 +48,18 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { public boolean getAngle(Vector vector) { int x = vector.getBlockX(); int z = vector.getBlockZ(); - int o = getHighestTerrainBlock(x, z, 0, maxY); - if (getHighestTerrainBlock(x - 1, z, o + min, o + max) != -1) { +// int o = getHighestTerrainBlock(x, z, 0, maxY); + int y = vector.getBlockY(); + if (getHighestTerrainBlock(x - 1, z, y + min, y + max) != -1) { return true; } - if (getHighestTerrainBlock(x + 1, z, o + min, o + max) != -1) { + if (getHighestTerrainBlock(x + 1, z, y + min, y + max) != -1) { return true; } - if (getHighestTerrainBlock(x, z - 1, o + min, o + max) != -1) { + if (getHighestTerrainBlock(x, z - 1, y + min, y + max) != -1) { return true; } - if (getHighestTerrainBlock(x, z + 1, o + min, o + max) != -1) { + if (getHighestTerrainBlock(x, z + 1, y + min, y + max) != -1) { return true; } return false; @@ -70,22 +71,22 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { long pair = MathMan.pairInt(x, z); Integer height = heights.get(pair); if (height != null) { - if (height >= minY && height <= maxY) { + if (height >= minY && height <= maxY && height >= 0 && height <= this.maxY) { return height; } else { return -1; } } - maxY = Math.min(this.maxY, Math.max(0, maxY)); - minY = Math.max(0, minY); + int maxSearchY = Math.min(this.maxY, Math.max(0, maxY)); + int minSearchY = Math.min(this.maxY, Math.max(0, minY)); mutable.x = x; mutable.z = z; boolean air = false; - if (maxY != this.maxY) { - mutable.y = maxY + 1; + if (maxSearchY != this.maxY) { + mutable.y = maxSearchY + 1; air = !super.test(mutable); } - for (int y = maxY; y >= minY; --y) { + for (int y = maxSearchY; y >= minSearchY; --y) { mutable.y = y; if (super.test(mutable)) { if (!air) { @@ -98,7 +99,7 @@ public class AngleMask extends SolidBlockMask implements ResettableMask { air = true; } } - if (minY == 0 && maxY == this.maxY) { + if (minSearchY == 0 && maxSearchY == this.maxY) { heights.put(pair, -1); } else { int value = getHighestTerrainBlock(x, z, 0, this.maxY); diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java index f3a6ff43..4e936544 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java @@ -90,16 +90,35 @@ public class DefaultMaskParser extends FaweParser { public Mask parseFromInput(String input, ParserContext context) throws InputParseException { List masks = new ArrayList(); - for (String component : input.split(" ")) { + for (String component : split(input, ' ')) { if (component.isEmpty()) { continue; } - - Mask current = getBlockMaskComponent(masks, component, context); - - masks.add(current); + HashSet blocks = new HashSet(); + List masksUnion = new ArrayList(); + for (String elem : split(component, ',')) { + ArrayList list = new ArrayList(); + list.add(catchSuggestion(input, list, elem, context)); + if (list.size() == 1) { + Mask mask = list.get(0); + if (mask.getClass() == BlockMask.class) { + blocks.addAll(((BlockMask) mask).getBlocks()); + } else { + masksUnion.add(mask); + } + } else { + masksUnion.add(new MaskIntersection(list)); + } + } + if (!blocks.isEmpty()) { + masksUnion.add(new BlockMask(Request.request().getExtent(), blocks)); + } + if (masksUnion.size() == 1) { + masks.add(masksUnion.get(0)); + } else { + masks.add(new MaskUnion(masksUnion)); + } } - switch (masks.size()) { case 0: return null; @@ -123,7 +142,6 @@ public class DefaultMaskParser extends FaweParser { private Mask getBlockMaskComponent(List masks, String input, ParserContext context) throws InputParseException { Extent extent = Request.request().getExtent(); - final char firstChar = input.charAt(0); switch (firstChar) { case '#': @@ -234,8 +252,8 @@ public class DefaultMaskParser extends FaweParser { throw new SuggestInputParseException(input, "/:"); } try { - int y1 = (int) Math.abs(Expression.compile(split[0]).evaluate()); - int y2 = (int) Math.abs(Expression.compile(split[1]).evaluate()); + int y1 = (int) (Expression.compile(split[0]).evaluate()); + int y2 = (int) (Expression.compile(split[1]).evaluate()); return new AngleMask(extent, y1, y2); } catch (NumberFormatException | ExpressionException e) { throw new SuggestInputParseException(input, "/:"); @@ -338,34 +356,10 @@ public class DefaultMaskParser extends FaweParser { } } } - List split = split(input, ','); - if (split.size() == 1) { - ParserContext tempContext = new ParserContext(context); - tempContext.setRestricted(false); - tempContext.setPreferringWildcard(true); - return new BlockMask(extent, worldEdit.getBlockFactory().parseFromListInput(input, tempContext)); - } - HashSet blocks = new HashSet(); - ArrayList maskUnion = new ArrayList(); - for (String elem : split) { - ArrayList list = new ArrayList(); - list.add(catchSuggestion(input, list, elem, context)); - if (list.size() == 1) { - Mask mask = list.get(0); - if (mask instanceof BlockMask) { - blocks.addAll(((BlockMask) mask).getBlocks()); - } else { - maskUnion.add(mask); - } - } - } - if (!blocks.isEmpty()) { - maskUnion.add(new BlockMask(extent, blocks)); - } - if (maskUnion.size() == 1) { - return maskUnion.get(0); - } - return new MaskUnion(maskUnion); + ParserContext tempContext = new ParserContext(context); + tempContext.setRestricted(false); + tempContext.setPreferringWildcard(true); + return new BlockMask(extent, worldEdit.getBlockFactory().parseFromListInput(input, tempContext)); } } diff --git a/core/src/main/java/com/sk89q/worldedit/function/mask/MaskIntersection.java b/core/src/main/java/com/sk89q/worldedit/function/mask/MaskIntersection.java new file mode 100644 index 00000000..e7747f94 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/function/mask/MaskIntersection.java @@ -0,0 +1,129 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.mask; + +import com.sk89q.worldedit.Vector; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Combines several masks and requires that all masks return true + * when a certain position is tested. It serves as a logical AND operation + * on a list of masks. + */ +public class MaskIntersection extends AbstractMask { + + private final Set masks = new HashSet(); + private Mask[] masksArray; + + /** + * Create a new intersection. + * + * @param masks a list of masks + */ + public MaskIntersection(Collection masks) { + checkNotNull(masks); + this.masks.addAll(masks); + formArray(); + } + + private void formArray() { + if (masks.isEmpty()) { + masksArray = new Mask[] {Masks.alwaysFalse()}; + } else { + masksArray = masks.toArray(new Mask[masks.size()]); + } + } + + /** + * Create a new intersection. + * + * @param mask a list of masks + */ + public MaskIntersection(Mask... mask) { + this(Arrays.asList(checkNotNull(mask))); + } + + /** + * Add some masks to the list. + * + * @param masks the masks + */ + public void add(Collection masks) { + checkNotNull(masks); + this.masks.addAll(masks); + formArray(); + } + + /** + * Add some masks to the list. + * + * @param mask the masks + */ + public void add(Mask... mask) { + add(Arrays.asList(checkNotNull(mask))); + } + + /** + * Get the masks that are tested with. + * + * @return the masks + */ + public Collection getMasks() { + return masks; + } + + public Mask[] getMasksArray() { + return masksArray; + } + + @Override + public boolean test(Vector vector) { + for (Mask mask : masksArray) { + if (!mask.test(vector)) { + return false; + } + } + return true; + } + + @Nullable + @Override + public Mask2D toMask2D() { + List mask2dList = new ArrayList(); + for (Mask mask : masks) { + Mask2D mask2d = mask.toMask2D(); + if (mask2d != null) { + mask2dList.add(mask2d); + } else { + return null; + } + } + return new MaskIntersection2D(mask2dList); + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/function/mask/MaskUnion.java b/core/src/main/java/com/sk89q/worldedit/function/mask/MaskUnion.java new file mode 100644 index 00000000..ce802530 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/function/mask/MaskUnion.java @@ -0,0 +1,83 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.mask; + +import com.sk89q.worldedit.Vector; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Combines several masks and requires that one or more masks return true + * when a certain position is tested. It serves as a logical OR operation + * on a list of masks. + */ +public class MaskUnion extends MaskIntersection { + + /** + * Create a new union. + * + * @param masks a list of masks + */ + public MaskUnion(Collection masks) { + super(masks); + } + + /** + * Create a new union. + * + * @param mask a list of masks + */ + public MaskUnion(Mask... mask) { + super(mask); + } + + @Override + public boolean test(Vector vector) { + for (Mask mask : getMasksArray()) { + if (mask.test(vector)) { + return true; + } + } + + return false; + } + + @Nullable + @Override + public Mask2D toMask2D() { + List mask2dList = new ArrayList(); + for (Mask mask : getMasks()) { + Mask2D mask2d = mask.toMask2D(); + if (mask2d != null) { + mask2dList.add(mask2d); + } else { + return null; + } + } + return new MaskUnion2D(mask2dList); + } + + public static Class inject() { + return MaskUnion.class; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java b/core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java index d5119042..6533053a 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java +++ b/core/src/main/java/com/sk89q/worldedit/function/mask/Masks.java @@ -31,6 +31,10 @@ public final class Masks { return ALWAYS_TRUE; } + public static Mask alwaysFalse() { + return ALWAYS_FALSE; + } + /** * Return a 2D mask that always returns true; *