From 8a24fd9741452a8f4ff4b53f2fa80406290c41fa Mon Sep 17 00:00:00 2001 From: Yetanotherx Date: Mon, 2 Jan 2012 21:39:48 -0500 Subject: [PATCH] Add a cylindrical region selector. - Supports shift - Supports expand/contract on the y axis - getArea is slightly inaccurate, but more accuracy would mean more CPU usage. - Displays as a cuboid in legacy mode. --- .../worldedit/commands/SelectionCommands.java | 8 +- .../worldedit/cui/SelectionCylinderEvent.java | 47 +++ .../worldedit/regions/CylinderRegion.java | 328 ++++++++++++++++++ .../regions/CylinderRegionSelector.java | 207 +++++++++++ 4 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/sk89q/worldedit/cui/SelectionCylinderEvent.java create mode 100644 src/main/java/com/sk89q/worldedit/regions/CylinderRegion.java create mode 100644 src/main/java/com/sk89q/worldedit/regions/CylinderRegionSelector.java diff --git a/src/main/java/com/sk89q/worldedit/commands/SelectionCommands.java b/src/main/java/com/sk89q/worldedit/commands/SelectionCommands.java index c5b70e2c8..50fef14dd 100644 --- a/src/main/java/com/sk89q/worldedit/commands/SelectionCommands.java +++ b/src/main/java/com/sk89q/worldedit/commands/SelectionCommands.java @@ -37,6 +37,7 @@ import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.SphereRegionSelector; import com.sk89q.worldedit.blocks.*; +import com.sk89q.worldedit.regions.CylinderRegionSelector; /** * Selection commands. @@ -593,7 +594,7 @@ public class SelectionCommands { @Command( aliases = { "/sel", ";" }, - usage = "[cuboid|extend|poly|ellipsoid|sphere]", + usage = "[cuboid|extend|poly|ellipsoid|sphere|cyl]", desc = "Choose a region selector", min = 0, max = 1 @@ -628,8 +629,11 @@ public class SelectionCommands { } else if (typeName.equalsIgnoreCase("sphere")) { selector = new SphereRegionSelector(oldSelector); player.print("Sphere selector: left click=center, right click to extend"); + } else if (typeName.equalsIgnoreCase("cyl")) { + selector = new CylinderRegionSelector(oldSelector); + player.print("Cylindrical selector: Left click=center, right click to extend."); } else { - player.printError("Only cuboid|extend|poly|ellipsoid|sphere are accepted."); + player.printError("Only cuboid|extend|poly|ellipsoid|sphere|cyl are accepted."); return; } diff --git a/src/main/java/com/sk89q/worldedit/cui/SelectionCylinderEvent.java b/src/main/java/com/sk89q/worldedit/cui/SelectionCylinderEvent.java new file mode 100644 index 000000000..9c8a77418 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/cui/SelectionCylinderEvent.java @@ -0,0 +1,47 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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.cui; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; + +public class SelectionCylinderEvent implements CUIEvent { + + protected Vector pos; + protected Vector2D radius; + + public SelectionCylinderEvent(Vector pos, Vector2D radius) { + this.pos = pos; + this.radius = radius; + } + + public String getTypeId() { + return "cyl"; + } + + public String[] getParameters() { + return new String[] { + String.valueOf(pos.getBlockX()), + String.valueOf(pos.getBlockY()), + String.valueOf(pos.getBlockZ()), + String.valueOf(radius.getX()), + String.valueOf(radius.getZ()) + }; + } +} diff --git a/src/main/java/com/sk89q/worldedit/regions/CylinderRegion.java b/src/main/java/com/sk89q/worldedit/regions/CylinderRegion.java new file mode 100644 index 000000000..c33f4a44f --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/regions/CylinderRegion.java @@ -0,0 +1,328 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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.regions; + +import java.util.HashSet; +import java.util.Set; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.data.ChunkStore; + +/** + * Represents a cylindrical region. + * + * @author yetanotherx + */ +public class CylinderRegion extends AbstractRegion { + private Vector center; + private Vector2D center2D; + private Vector2D radius; + private int minY; + private int maxY; + private boolean hasY = false; + + /** + * Construct the region + */ + public CylinderRegion() { + this((LocalWorld) null); + } + + /** + * Construct the region. + * + * @param world + */ + public CylinderRegion(LocalWorld world) { + this(world, new Vector(), new Vector2D(), 0, 0); + hasY = false; + } + + /** + * Construct the region. + * + * @param world + * @param points + * @param minY + * @param maxY + */ + public CylinderRegion(LocalWorld world, Vector center, Vector2D radius, int minY, int maxY) { + super(world); + this.center = center; + setRadius(radius); + this.minY = minY; + this.maxY = maxY; + hasY = true; + } + + public CylinderRegion(CylinderRegion region) { + this(region.world, region.center, region.getRadius(), region.minY, region.maxY); + hasY = region.hasY; + } + + /** + * Returns the main center point of the cylinder + * + * @return + */ + public Vector getCenter() { + return center; + } + + /** + * Sets the main center point of the region + * + */ + public void setCenter(Vector center) { + this.center = center; + this.center2D = center.toVector2D(); + } + + /** + * Returns the radius of the cylinder + * + * @return + */ + public Vector2D getRadius() { + return radius.subtract(0.5, 0.5); + } + + /** + * Sets the radius of the cylinder + * + * @param radius + */ + public void setRadius(Vector2D radius) { + this.radius = radius.add(0.5, 0.5); + } + + /** + * Extends the radius to be at least the given radius + * + * @param minRadius + */ + public void extendRadius(Vector2D minRadius) { + setRadius(Vector2D.getMaximum(minRadius, getRadius())); + } + + /** + * Set the minimum Y. + * + * @param y + */ + public void setMinimumY(int y) { + hasY = true; + minY = y; + } + + /** + * Se the maximum Y. + * + * @param y + */ + public void setMaximumY(int y) { + hasY = true; + maxY = y; + } + + /** + * Get the lower point of a region. + * + * @return min. point + */ + public Vector getMinimumPoint() { + return center2D.subtract(getRadius()).toVector(minY); + } + + /** + * Get the upper point of a region. + * + * @return max. point + */ + public Vector getMaximumPoint() { + return center2D.add(getRadius()).toVector(maxY); + } + + /** + * Gets the maximum Y value + * @return + */ + public int getMaximumY() { + return maxY; + } + + /** + * Gets the minimum Y value + * @return + */ + public int getMinimumY() { + return minY; + } + + /** + * Get the number of blocks in the region. + * + * @return number of blocks + */ + public int getArea() { + return (int) Math.floor(radius.getX() * radius.getZ() * Math.PI * getHeight()); + } + + /** + * Get X-size. + * + * @return width + */ + public int getWidth() { + return (int) (2 * radius.getX()); + } + + /** + * Get Y-size. + * + * @return height + */ + public int getHeight() { + return maxY - minY + 1; + } + + /** + * Get Z-size. + * + * @return length + */ + public int getLength() { + return (int) (2 * radius.getZ()); + } + + /** + * Expand the region. + * + * @param change + */ + public void expand(Vector change) throws RegionOperationException { + if (change.getBlockX() != 0 || change.getBlockZ() != 0) { + throw new RegionOperationException("Cylinders can only be expanded vertically."); + } + + int changeY = change.getBlockY(); + if (changeY > 0) { + maxY += changeY; + } else { + minY += changeY; + } + } + + /** + * Contract the region. + * + * @param change + */ + public void contract(Vector change) throws RegionOperationException { + if (change.getBlockX() != 0 || change.getBlockZ() != 0) { + throw new RegionOperationException("Cylinders can only be expanded vertically."); + } + + int changeY = change.getBlockY(); + if (changeY > 0) { + minY += changeY; + } else { + maxY += changeY; + } + } + + @Override + public void shift(Vector change) throws RegionOperationException { + setCenter(getCenter().add(change)); + + int changeY = change.getBlockY(); + maxY += changeY; + minY += changeY; + } + + /** + * Checks to see if a point is inside this region. + */ + public boolean contains(Vector pt) { + final int blockY = pt.getBlockY(); + if (blockY < minY || blockY > maxY) { + return false; + } + + return pt.toVector2D().subtract(center2D).divide(radius).lengthSq() <= 1; + } + + /** + * Get a list of chunks. + * + * @return + */ + public Set getChunks() { + Set chunks = new HashSet(); + + Vector min = getMinimumPoint(); + Vector max = getMaximumPoint(); + + for (int x = min.getBlockX(); x <= max.getBlockX(); ++x) { + for (int z = min.getBlockZ(); z <= max.getBlockZ(); ++z) { + Vector pt = new Vector(x, minY, z); + if (contains(pt)) { + chunks.add(ChunkStore.toChunk(pt)); + } + } + } + + return chunks; + } + + /** + * Sets the height of the cylinder to fit the specified Y. + * + * @param y + * @return true if the area was expanded + */ + public boolean setY(int y) { + if (!hasY) { + minY = y; + maxY = y; + hasY = true; + return true; + } else if (y < minY) { + minY = y; + return true; + } else if (y > maxY) { + maxY = y; + return true; + } + + return false; + } + + /** + * Returns string representation in the format + * "(centerX, centerY, centerZ) - (radiusX, radiusZ)" + * + * @return string + */ + @Override + public String toString() { + return center + " - " + radius; + } +} diff --git a/src/main/java/com/sk89q/worldedit/regions/CylinderRegionSelector.java b/src/main/java/com/sk89q/worldedit/regions/CylinderRegionSelector.java new file mode 100644 index 000000000..21f3df229 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/regions/CylinderRegionSelector.java @@ -0,0 +1,207 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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.regions; + +import java.util.ArrayList; +import java.util.List; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalPlayer; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.cui.CUIRegion; +import com.sk89q.worldedit.cui.SelectionCylinderEvent; +import com.sk89q.worldedit.cui.SelectionMinMaxEvent; +import com.sk89q.worldedit.cui.SelectionPointEvent; +import com.sk89q.worldedit.cui.SelectionShapeEvent; +import java.text.NumberFormat; + +/** + * Selector for polygonal regions. + * + * @author sk89q + */ +public class CylinderRegionSelector implements RegionSelector, CUIRegion { + protected CylinderRegion region; + protected static final NumberFormat format; + + static { + format = (NumberFormat) NumberFormat.getInstance().clone(); + format.setMaximumFractionDigits(3); + } + + public CylinderRegionSelector(LocalWorld world) { + region = new CylinderRegion(world); + } + + public CylinderRegionSelector(RegionSelector oldSelector) { + this(oldSelector.getIncompleteRegion().getWorld()); + if (oldSelector instanceof CylinderRegionSelector) { + final CylinderRegionSelector cylSelector = (CylinderRegionSelector) oldSelector; + + region = new CylinderRegion(cylSelector.region); + } else { + final Region oldRegion; + try { + oldRegion = oldSelector.getRegion(); + } catch (IncompleteRegionException e) { + return; + } + + Vector pos1 = oldRegion.getMinimumPoint(); + Vector pos2 = oldRegion.getMaximumPoint(); + + Vector center = pos1.add(pos2).divide(2).floor(); + region.setCenter(center); + region.setRadius(pos2.toVector2D().subtract(center.toVector2D())); + + region.setMaximumY(Math.max(pos1.getBlockY(), pos2.getBlockY())); + region.setMinimumY(Math.min(pos1.getBlockY(), pos2.getBlockY())); + } + } + + public boolean selectPrimary(Vector pos) { + if (!region.getCenter().equals(new Vector(0, 0, 0)) && pos.equals(region.getCenter())) { + return false; + } + + region = new CylinderRegion(region.getWorld()); + region.setCenter(pos); + region.setY(pos.getBlockY()); + + return true; + } + + public boolean selectSecondary(Vector pos) { + Vector center = region.getCenter(); + if (center.equals(new Vector(0, 0, 0))) { + return true; + } + + final Vector2D diff = pos.subtract(center).toVector2D(); + final Vector2D minRadius = Vector2D.getMaximum(diff, diff.multiply(-1.0)); + region.extendRadius(minRadius); + + region.setY(pos.getBlockY()); + + return true; + } + + public void explainPrimarySelection(LocalPlayer player, LocalSession session, Vector pos) { + player.print("Starting a new cylindrical selection at " + pos + "."); + + session.describeCUI(player); + } + + public void explainSecondarySelection(LocalPlayer player, LocalSession session, Vector pos) { + Vector center = region.getCenter(); + if (!center.equals(new Vector(0, 0, 0))) { + player.print("Radius set to " + format.format(region.getRadius().getX()) + "/" + format.format(region.getRadius().getZ()) + " blocks. (" + region.getArea() + ")."); + } else { + player.printError("You must select the center point before setting the radius."); + return; + } + + session.describeCUI(player); + } + + public void explainRegionAdjust(LocalPlayer player, LocalSession session) { + session.describeCUI(player); + } + + public BlockVector getPrimaryPosition() throws IncompleteRegionException { + if (!isDefined()) { + throw new IncompleteRegionException(); + } + + return region.getCenter().toBlockVector(); + } + + public CylinderRegion getRegion() throws IncompleteRegionException { + if (!isDefined()) { + throw new IncompleteRegionException(); + } + + return region; + } + + public CylinderRegion getIncompleteRegion() { + return region; + } + + public boolean isDefined() { + return !region.getRadius().equals(new Vector2D(0, 0)); + } + + public void learnChanges() { + } + + public void clear() { + region = new CylinderRegion(region.getWorld()); + } + + public String getTypeName() { + return "Cylinder"; + } + + public List getInformationLines() { + final List lines = new ArrayList(); + + if (!region.getCenter().equals(new Vector(0, 0, 0))) { + lines.add("Center: " + region.getCenter()); + } + if (!region.getRadius().equals(new Vector2D(0, 0))) { + lines.add("Radius: " + region.getRadius()); + } + + return lines; + } + + public int getArea() { + return region.getArea(); + } + + public void describeCUI(LocalSession session, LocalPlayer player) { + session.dispatchCUIEvent(player, new SelectionCylinderEvent(region.getCenter(), region.getRadius())); + session.dispatchCUIEvent(player, new SelectionMinMaxEvent(region.getMinimumY(), region.getMaximumY())); + } + + public void describeLegacyCUI(LocalSession session, LocalPlayer player) { + if (isDefined()) { + session.dispatchCUIEvent(player, new SelectionPointEvent(0, region.getMinimumPoint(), getArea())); + session.dispatchCUIEvent(player, new SelectionPointEvent(1, region.getMaximumPoint(), getArea())); + } else { + session.dispatchCUIEvent(player, new SelectionShapeEvent(getLegacyTypeID())); + } + } + + public int getProtocolVersion() { + return 1; + } + + public String getTypeID() { + return "cylinder"; + } + + public String getLegacyTypeID() { + return "cuboid"; + } +}