From 598f4aa6cd782878b236c51f2d71eaee66d9aec2 Mon Sep 17 00:00:00 2001 From: sk89q Date: Wed, 13 Oct 2010 10:08:53 -0700 Subject: [PATCH] Changed the clipboard so it doesn't store min/max anymore, just origin and offset. Added /editrotate. --- src/CuboidClipboard.java | 236 ++++++++++-------- src/WorldEdit.java | 45 ++-- src/WorldEditSMListener.java | 2 + src/WorldEditSession.java | 14 +- .../worldedit/EmptyClipboardException.java | 28 +++ src/com/sk89q/worldedit/Vector.java | 23 ++ 6 files changed, 219 insertions(+), 129 deletions(-) create mode 100644 src/com/sk89q/worldedit/EmptyClipboardException.java diff --git a/src/CuboidClipboard.java b/src/CuboidClipboard.java index fe09c4da4..45a00cb59 100644 --- a/src/CuboidClipboard.java +++ b/src/CuboidClipboard.java @@ -25,30 +25,52 @@ import java.util.ArrayList; import com.sk89q.worldedit.*; /** + * The clipboard remembers the state of a cuboid region. * - * @author Albert + * @author sk89q */ public class CuboidClipboard { private int[][][] data; - private Vector min; - private Vector max; + private Vector offset; private Vector origin; + private Vector size; /** - * Constructs the region instance. The minimum and maximum points must be - * the respective minimum and maximum numbers! - * - * @param min - * @param max + * Constructs the clipboard. + * + * @param size + */ + public CuboidClipboard(Vector size) { + this.size = size; + data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()]; + origin = new Vector(); + offset = new Vector(); + } + + /** + * Constructs the clipboard. + * + * @param size * @param origin */ - public CuboidClipboard(Vector min, Vector max, Vector origin) { - this.min = min; - this.max = max; + public CuboidClipboard(Vector size, Vector origin) { + this.size = size; + data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()]; this.origin = origin; - data = new int[(int)((max.getX()) - min.getX() + 1)] - [(int)(max.getY() - min.getY() + 1)] - [(int)(max.getZ() - min.getZ() + 1)]; + offset = new Vector(); + } + + /** + * Constructs the clipboard. + * + * @param size + * @param origin + */ + public CuboidClipboard(Vector size, Vector origin, Vector offset) { + this.size = size; + data = new int[size.getBlockX()][size.getBlockY()][size.getBlockZ()]; + this.origin = origin; + this.offset = offset; } /** @@ -57,7 +79,7 @@ public class CuboidClipboard { * @return width */ public int getWidth() { - return (int)(max.getX() - min.getX() + 1); + return size.getBlockX(); } /** @@ -66,7 +88,7 @@ public class CuboidClipboard { * @return length */ public int getLength() { - return (int)(max.getZ() - min.getZ() + 1); + return size.getBlockZ(); } /** @@ -75,7 +97,47 @@ public class CuboidClipboard { * @return height */ public int getHeight() { - return (int)(max.getY() - min.getY() + 1); + return size.getBlockY(); + } + + /** + * Rotate the clipboard in 2D. It can only rotate by angles divisible by 90. + * + * @param angle in degrees + */ + public void rotate2D(int angle) { + angle = angle % 360; + if (angle % 90 != 0) { // Can only rotate 90 degrees at the moment + return; + } + + int width = getWidth(); + int length = getLength(); + int height = getHeight(); + int newWidth = angle % 180 == 0 ? width : length; + int newLength = angle % 180 == 0 ? length : width; + Vector sizeRotated = size.transform2D(angle, 0, 0, 0, 0); + int shiftX = sizeRotated.getX() < 0 ? newWidth - 1 : 0; + int shiftZ = sizeRotated.getZ() < 0 ? newLength - 1: 0; + + int newData[][][] = new int[newWidth][getHeight()][newLength]; + + for (int x = 0; x < width; x++) { + for (int z = 0; z < length; z++) { + int newX = (new Vector(x, 0, z)).transform2D(angle, 0, 0, 0, 0) + .getBlockX(); + int newZ = (new Vector(x, 0, z)).transform2D(angle, 0, 0, 0, 0) + .getBlockZ(); + for (int y = 0; y < height; y++) { + newData[shiftX + newX][y][shiftZ + newZ] = data[x][y][z]; + } + } + } + + data = newData; + size = new Vector(newWidth, getHeight(), newLength); + offset = offset.transform2D(angle, 0, 0, 0, 0) + .subtract(shiftX, 0, shiftZ); } /** @@ -84,11 +146,11 @@ public class CuboidClipboard { * @param editSession */ public void copy(EditSession editSession) { - for (int x = (int)min.getX(); x <= (int)max.getX(); x++) { - for (int y = (int)min.getY(); y <= (int)max.getY(); y++) { - for (int z = (int)min.getZ(); z <= (int)max.getZ(); z++) { - data[x - (int)min.getX()][y - (int)min.getY()][z - (int)min.getZ()] = - editSession.getBlock(x, y, z); + for (int x = 0; x < size.getBlockX(); x++) { + for (int y = 0; y < size.getBlockY(); y++) { + for (int z = 0; z < size.getBlockZ(); z++) { + data[x][y][z] = + editSession.getBlock(new Vector(x, y, z).add(origin)); } } } @@ -104,76 +166,31 @@ public class CuboidClipboard { */ public void paste(EditSession editSession, Vector newOrigin, boolean noAir) throws MaxChangedBlocksException { - int offsetX = (int)(min.getX() - origin.getX() + newOrigin.getX()); - int offsetY = (int)(min.getY() - origin.getY() + newOrigin.getY()); - int offsetZ = (int)(min.getZ() - origin.getZ() + newOrigin.getZ()); - - place(editSession, offsetX, offsetY, offsetZ, noAir); + place(editSession, newOrigin.add(offset), noAir); } /** * Places the blocks in a position from the minimum corner. * * @param editSession - * @param offsetX - * @param offsetY - * @param offsetZ + * @param pos * @param noAir * @throws MaxChangedBlocksException */ - public void place(EditSession editSession, int offsetX, - int offsetY, int offsetZ, boolean noAir) + public void place(EditSession editSession, Vector pos, boolean noAir) throws MaxChangedBlocksException { - int xs = getWidth(); - int ys = getHeight(); - int zs = getLength(); - - for (int x = 0; x < xs; x++) { - for (int y = 0; y < ys; y++) { - for (int z = 0; z < zs; z++) { - if (noAir && data[x][y][z] == 0) { continue; } - - editSession.setBlock(x + offsetX, y + offsetY, z + offsetZ, - data[x][y][z]); + for (int x = 0; x < size.getBlockX(); x++) { + for (int y = 0; y < size.getBlockY(); y++) { + for (int z = 0; z < size.getBlockZ(); z++) { + if (noAir && data[x][y][z] == 0) continue; + + editSession.setBlock(new Vector(x, y, z).add(pos), + data[x][y][z]); } } } } - /** - * Stack the clipboard in a certain direction a certain number of - * times. - * - * @param editSession - * @param xm - * @param ym - * @param zm - * @param count - * @param noAir - * @param moveOrigin move the origin - * @throws MaxChangedBlocksException - */ - public void stack(EditSession editSession, int xm, int ym, int zm, short count, - boolean noAir, boolean moveOrigin) throws MaxChangedBlocksException { - int xs = getWidth(); - int ys = getHeight(); - int zs = getLength(); - int offsetX = (int)min.getX(); - int offsetY = (int)min.getY(); - int offsetZ = (int)min.getZ(); - - for (short i = 1; i <= count; i++) { - place(editSession, offsetX + xm * xs, offsetY + ym * ys, - offsetZ + zm * zs, noAir); - } - - if (moveOrigin) { - min = new Vector((int)offsetX + xm * count, - (int)offsetY + ym * count, - (int)offsetZ + zm * count); - } - } - /** * Saves the clipboard data to a .schematic-format file. * @@ -182,32 +199,32 @@ public class CuboidClipboard { * @throws SchematicException */ public void saveSchematic(String path) throws IOException, SchematicException { - int xs = getWidth(); - int ys = getHeight(); - int zs = getLength(); + int width = getWidth(); + int height = getHeight(); + int length = getLength(); - if (xs > 65535) { + if (width > 65535) { throw new SchematicException("Width of region too large for a .schematic"); } - if (ys > 65535) { + if (height > 65535) { throw new SchematicException("Height of region too large for a .schematic"); } - if (zs > 65535) { + if (length > 65535) { throw new SchematicException("Length of region too large for a .schematic"); } HashMap schematic = new HashMap(); - schematic.put("Width", new ShortTag("Width", (short)xs)); - schematic.put("Length", new ShortTag("Length", (short)zs)); - schematic.put("Height", new ShortTag("Height", (short)ys)); + schematic.put("Width", new ShortTag("Width", (short)width)); + schematic.put("Length", new ShortTag("Length", (short)length)); + schematic.put("Height", new ShortTag("Height", (short)height)); schematic.put("Materials", new StringTag("Materials", "Alpha")); // Copy blocks - byte[] blocks = new byte[xs * ys * zs]; - for (int x = 0; x < xs; x++) { - for (int y = 0; y < ys; y++) { - for (int z = 0; z < zs; z++) { - int index = y * xs * zs + z * xs + x; + byte[] blocks = new byte[width * height * length]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + for (int z = 0; z < length; z++) { + int index = y * width * length + z * width + x; blocks[index] = (byte)data[x][y][z]; } } @@ -215,7 +232,7 @@ public class CuboidClipboard { schematic.put("Blocks", new ByteArrayTag("Blocks", blocks)); // Current data is not supported - byte[] data = new byte[xs * ys * zs]; + byte[] data = new byte[width * height * length]; schematic.put("Data", new ByteArrayTag("Data", data)); // These are not stored either @@ -238,7 +255,7 @@ public class CuboidClipboard { * @throws SchematicException * @throws IOException */ - public static CuboidClipboard loadSchematic(String path, Vector origin) + public static CuboidClipboard loadSchematic(String path) throws SchematicException, IOException { FileInputStream stream = new FileInputStream(path); NBTInputStream nbtStream = new NBTInputStream(stream); @@ -250,27 +267,23 @@ public class CuboidClipboard { if (!schematic.containsKey("Blocks")) { throw new SchematicException("Schematic file is missing a \"Blocks\" tag"); } - short xs = (Short)getChildTag(schematic, "Width", ShortTag.class).getValue(); - short zs = (Short)getChildTag(schematic, "Length", ShortTag.class).getValue(); - short ys = (Short)getChildTag(schematic, "Height", ShortTag.class).getValue(); + short width = (Short)getChildTag(schematic, "Width", ShortTag.class).getValue(); + short length = (Short)getChildTag(schematic, "Length", ShortTag.class).getValue(); + short height = (Short)getChildTag(schematic, "Height", ShortTag.class).getValue(); String materials = (String)getChildTag(schematic, "Materials", StringTag.class).getValue(); if (!materials.equals("Alpha")) { throw new SchematicException("Schematic file is not an Alpha schematic"); } byte[] blocks = (byte[])getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue(); - Vector min = origin; - Vector max = new Vector( - origin.getX() + xs - 1, - origin.getY() + ys - 1, - origin.getZ() + zs - 1 - ); - CuboidClipboard clipboard = new CuboidClipboard(min, max, origin); + Vector size = new Vector(width, height, length); - for (int x = 0; x < xs; x++) { - for (int y = 0; y < ys; y++) { - for (int z = 0; z < zs; z++) { - int index = y * xs * zs + z * xs + x; + CuboidClipboard clipboard = new CuboidClipboard(size); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + for (int z = 0; z < length; z++) { + int index = y * width * length + z * width + x; clipboard.data[x][y][z] = blocks[index]; } } @@ -279,6 +292,15 @@ public class CuboidClipboard { return clipboard; } + /** + * Get child tag of a NBT structure. + * + * @param items + * @param key + * @param expected + * @return child tag + * @throws SchematicException + */ private static Tag getChildTag(Map items, String key, Class expected) throws SchematicException { if (!items.containsKey(key)) { diff --git a/src/WorldEdit.java b/src/WorldEdit.java index 7e128224c..b035f5939 100644 --- a/src/WorldEdit.java +++ b/src/WorldEdit.java @@ -94,6 +94,7 @@ public class WorldEdit { commands.put("/editlimit", "[Num] - See documentation"); commands.put("/editexpand", " [Num] - Expands the selection"); commands.put("/editcontract", " [Num] - Contracts the selection"); + commands.put("/editrotate", "[Angle] - Rotate the clipboard"); commands.put("/forestgen", " - Make an ugly pine tree forest"); commands.put("/unstuck", "Go up to the first free spot"); commands.put("/ascend", "Go up one level"); @@ -303,15 +304,11 @@ public class WorldEdit { // Paste } else if (split[0].equalsIgnoreCase("/editpasteair") || split[0].equalsIgnoreCase("/editpaste")) { - if (session.getClipboard() == null) { - player.printError("Nothing is in your clipboard."); - } else { - Vector pos = player.getBlockIn(); - session.getClipboard().paste(editSession, pos, - split[0].equalsIgnoreCase("/editpaste")); - player.findFreePosition(); - player.print("Pasted. Undo with /editundo"); - } + Vector pos = player.getBlockIn(); + session.getClipboard().paste(editSession, pos, + split[0].equalsIgnoreCase("/editpaste")); + player.findFreePosition(); + player.print("Pasted. Undo with /editundo"); return true; @@ -363,8 +360,7 @@ public class WorldEdit { if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { player.printError("Schematic could not read or it does not exist."); } else { - Vector origin = player.getBlockIn(); - session.setClipboard(CuboidClipboard.loadSchematic(filePath, origin)); + session.setClipboard(CuboidClipboard.loadSchematic(filePath)); logger.log(Level.INFO, player.getName() + " loaded " + filePath); player.print(filename + " loaded."); } @@ -377,12 +373,7 @@ public class WorldEdit { return true; // Save clipboard to .schematic - } else if (split[0].equalsIgnoreCase("/editsave")) { - if (session.getClipboard() == null) { - player.printError("Nothing is in your clipboard."); - return true; - } - + } else if (split[0].equalsIgnoreCase("/editsave")) { checkArgs(split, 1, 1, split[0]); String filename = split[1].replace("\0", "") + ".schematic"; File dir = new File("schematics"); @@ -488,7 +479,9 @@ public class WorldEdit { Vector max = region.getMaximumPoint(); Vector pos = player.getBlockIn(); - CuboidClipboard clipboard = new CuboidClipboard(min, max, pos); + CuboidClipboard clipboard = new CuboidClipboard( + max.subtract(min).add(new Vector(1, 1, 1)), + min, min.subtract(pos)); clipboard.copy(editSession); session.setClipboard(clipboard); @@ -543,7 +536,7 @@ public class WorldEdit { return true; - // Expand + // Contract } else if (split[0].equalsIgnoreCase("/editcontract")) { checkArgs(split, 1, 2, split[0]); Vector dir; @@ -563,6 +556,20 @@ public class WorldEdit { int newSize = region.getSize(); player.print("Region contracted " + (oldSize - newSize) + " blocks."); + return true; + + // Rotate + } else if (split[0].equalsIgnoreCase("/editrotate")) { + checkArgs(split, 1, 1, split[0]); + int angle = Integer.parseInt(split[1]); + if (angle % 90 == 0) { + CuboidClipboard clipboard = session.getClipboard(); + clipboard.rotate2D(angle); + player.print("Clipboard rotated by " + angle + " degrees."); + } else { + player.printError("Angles must be divisible by 90 degrees."); + } + return true; } diff --git a/src/WorldEditSMListener.java b/src/WorldEditSMListener.java index 89144e377..539bbe252 100644 --- a/src/WorldEditSMListener.java +++ b/src/WorldEditSMListener.java @@ -161,6 +161,8 @@ public class WorldEditSMListener extends PluginListener { modPlayer.sendMessage(Colors.Rose + "Unknown direction: " + ue.getDirection()); } catch (InsufficientArgumentsException e6) { modPlayer.sendMessage(Colors.Rose + e6.getMessage()); + } catch (EmptyClipboardException ec) { + modPlayer.sendMessage(Colors.Rose + "Your clipboard is empty."); } catch (WorldEditException e7) { modPlayer.sendMessage(Colors.Rose + e7.getMessage()); } diff --git a/src/WorldEditSession.java b/src/WorldEditSession.java index 202e4fa71..029e9a380 100644 --- a/src/WorldEditSession.java +++ b/src/WorldEditSession.java @@ -172,12 +172,16 @@ public class WorldEditSession { } /** - * Get the region. May return null. If you change the region, you should + * Get the region. If you change the region, you should * call learnRegionChanges(). * * @return region + * @throws IncompleteRegionException */ - public Region getRegion() { + public Region getRegion() throws IncompleteRegionException { + if (region == null) { + throw new IncompleteRegionException(); + } return region; } @@ -185,8 +189,12 @@ public class WorldEditSession { * Gets the clipboard. * * @return clipboard, may be null + * @throws EmptyClipboardException */ - public CuboidClipboard getClipboard() { + public CuboidClipboard getClipboard() throws EmptyClipboardException { + if (clipboard == null) { + throw new EmptyClipboardException(); + } return clipboard; } diff --git a/src/com/sk89q/worldedit/EmptyClipboardException.java b/src/com/sk89q/worldedit/EmptyClipboardException.java new file mode 100644 index 000000000..faceea8ba --- /dev/null +++ b/src/com/sk89q/worldedit/EmptyClipboardException.java @@ -0,0 +1,28 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 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; + +/** + * + * @author Albert + */ +public class EmptyClipboardException extends WorldEditException { + +} diff --git a/src/com/sk89q/worldedit/Vector.java b/src/com/sk89q/worldedit/Vector.java index de6a9cf2e..6934c304b 100644 --- a/src/com/sk89q/worldedit/Vector.java +++ b/src/com/sk89q/worldedit/Vector.java @@ -356,6 +356,29 @@ public class Vector { Math.pow(pt.z - z, 2)); } + /** + * 2D transformation. + * + * @param vec + * @param angle in degrees + * @param aboutX + * @param aboutY + * @param translateX + * @param translateY + * @return + */ + public Vector transform2D(double angle, + double aboutX, double aboutZ, double translateX, double translateZ) { + angle = Math.toRadians(angle); + double x = this.x; + double z = this.z; + double x2 = x * Math.cos(angle) - z * Math.sin(angle); + double z2 = x * Math.sin(angle) + z * Math.cos(angle); + return new Vector(x2 + aboutX + translateX, + y, + z2 + aboutZ + translateZ); + } + /** * Get a block point from a point. *