From f8199a3b435de841cd7aff0d5fc732e4001834b9 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Tue, 9 May 2017 21:02:44 +1000 Subject: [PATCH] Various minor cfi smooth image command fix perm typo fix mask typo fix random offset transform --- .../com/boydti/fawe/bukkit/FaweBukkit.java | 10 +- .../jnbt/anvil/HeightMapMCAGenerator.java | 69 +++++++++++++- .../object/collection/SummedAreaTable.java | 93 +++++++++++++++++++ .../object/extent/RandomOffsetTransform.java | 6 +- .../regions/general/plot/CreateFromImage.java | 22 ++++- .../java/com/boydti/fawe/util/MathMan.java | 4 + .../worldedit/command/GenerationCommands.java | 59 +++++++++++- .../sk89q/worldedit/command/MaskCommands.java | 2 +- 8 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/boydti/fawe/object/collection/SummedAreaTable.java diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index fde24497..f83ad037 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -405,9 +405,17 @@ public class FaweBukkit implements IFawe, Listener { // } // } + private boolean runChunkLoad = false; + @EventHandler public void onChunkLoad(ChunkLoadEvent event) { - SetQueue.IMP.runMiscTasks(); + if (runChunkLoad) return; + try { + runChunkLoad = true; + SetQueue.IMP.runMiscTasks(); + } finally { + runChunkLoad = false; + } } @EventHandler diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java index 21c16883..e1c3842f 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java @@ -4,6 +4,7 @@ import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.object.PseudoRandom; import com.boydti.fawe.object.collection.LocalBlockVector2DSet; +import com.boydti.fawe.object.collection.SummedAreaTable; import com.boydti.fawe.object.schematic.Schematic; import com.boydti.fawe.util.CachedTextureUtil; import com.boydti.fawe.util.MathMan; @@ -44,7 +45,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { private final Int2ObjectOpenHashMap blocks = new Int2ObjectOpenHashMap<>(); - private final byte[] heights; + public final byte[] heights; private final byte[] biomes; private final char[] floor; private final char[] main; @@ -101,6 +102,72 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { this.textureUtil = textureUtil; } + public void smooth(BufferedImage img, boolean white, int radius, int iterations) { + smooth(img, null, white, radius, iterations); + } + + public void smooth(Mask mask, int radius, int iterations) { + smooth(null, mask, false, radius, iterations); + } + + private void smooth(BufferedImage img, Mask mask, boolean white, int radius, int iterations) { + char snow = 78 << 4; + long[] copy = new long[heights.length]; + char[] layers = new char[heights.length]; + int width = getWidth(); + int length = getLength(); + SummedAreaTable table = new SummedAreaTable(copy, layers, width, radius); + for (int j = 0; j < iterations; j++) { + for (int i = 0; i < heights.length; i++) { + char combined = floor[i]; + int id = combined >> 4; + if (id == 78 || id == 80) { + layers[i] = (char) (((heights[i] & 0xFF) << 3) + (floor[i] & 0x7) + 1); + } else { + layers[i] = (char) (((heights[i] & 0xFF) << 3) + 8); + } + } + int index = 0; + table.processSummedAreaTable(); + if (img != null) { + for (int z = 0; z < getLength(); z++) { + for (int x = 0; x < getWidth(); x++, index++) { + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && !white && PseudoRandom.random.nextInt(256) <= height) { + int newHeight = table.average(x, z, index); + int blockHeight = (newHeight - 1) >> 3; + int layerHeight = (newHeight - 1) & 0x7; + heights[index] = (byte) blockHeight; + int id = floor[index] >> 4; + if (id == 78 || id == 80) { + floor[index] = (char) (snow + layerHeight); + } + } + } + } + } else if (mask != null) { + for (int z = 0; z < getLength(); z++) { + mutable.mutZ(z); + for (int x = 0; x < getWidth(); x++, index++) { + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + int height = table.average(x, z, index); + int blockHeight = (height - 1) >> 3; + int layerHeight = (height - 1) & 0x7; + heights[index] = (byte) blockHeight; + int id = floor[index] >> 4; + if (id == 78 || id == 80) { + floor[index] = (char) (snow + layerHeight); + } + } + } + } + } + } + } + public void setHeight(BufferedImage img) { int index = 0; for (int z = 0; z < getLength(); z++) { diff --git a/core/src/main/java/com/boydti/fawe/object/collection/SummedAreaTable.java b/core/src/main/java/com/boydti/fawe/object/collection/SummedAreaTable.java new file mode 100644 index 00000000..fc9212b6 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/collection/SummedAreaTable.java @@ -0,0 +1,93 @@ +package com.boydti.fawe.object.collection; + +import com.boydti.fawe.util.MathMan; + +public class SummedAreaTable { + private final char[] source; + private final long[] summed; + private final int length; + private final int width; + private final int area; + private final int radius; + private final float areaInverse; + + public SummedAreaTable(long[] buffer, char[] matrix, int width, int radius) { + this.source = matrix; + this.summed = buffer; + this.width = width; + this.length = buffer.length / width; + this.radius = radius; + this.area = MathMan.sqr(radius * 2 + 1); + this.areaInverse = 1f/area; + } + + public void processSummedAreaTable() { + int rowSize = source.length / width; + int colSize = width; + int index = 0; + for (int i=0; i radius) { + int X = index + minX + maxzw; + int M = index + minzw + minX; + total -= summed[X - 1]; + total += getSum(M - width - 1); + } + total -= getSum(Z - width); + if (area == this.area) { + return (int) (total * areaInverse); + } else { + return MathMan.lossyFastDivide((int) total, area); + } + } + + private long getVal(int row, int col, int index, long curr) { + long leftSum; // sub matrix sum of left matrix + long topSum; // sub matrix sum of top matrix + long topLeftSum; // sub matrix sum of top left matrix + /* top left value is itself */ + if (index == 0) { + return curr; + } + /* top row */ + else if (row == 0 && col != 0) { + leftSum = summed[index - 1]; + return curr + leftSum; + } + /* left-most column */ + else if (row !=0 && col == 0) { + topSum = summed[index - width]; + return curr + topSum; + } + else { + leftSum = summed[index - 1]; + topSum = summed[index - width]; + topLeftSum = summed[index - width - 1]; // overlap between leftSum and topSum + return curr + leftSum + topSum - topLeftSum; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/extent/RandomOffsetTransform.java b/core/src/main/java/com/boydti/fawe/object/extent/RandomOffsetTransform.java index 0a79ac46..90b2988a 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/RandomOffsetTransform.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/RandomOffsetTransform.java @@ -26,7 +26,7 @@ public class RandomOffsetTransform extends ResettableExtent { public boolean setBiome(Vector2D pos, BaseBiome biome) { int x = pos.getBlockX() + random.nextInt(1 + (dx << 1)) - dx; int z = pos.getBlockZ() + random.nextInt(1 + (dz << 1)) - dz; - return super.setBiome(mutable.setComponents(x, z), biome); + return getExtent().setBiome(mutable.setComponents(x, z), biome); } @Override @@ -34,7 +34,7 @@ public class RandomOffsetTransform extends ResettableExtent { int x = pos.getBlockX() + random.nextInt(1 + (dx << 1)) - dx; int y = pos.getBlockY() + random.nextInt(1 + (dy << 1)) - dy; int z = pos.getBlockZ() + random.nextInt(1 + (dz << 1)) - dz; - return super.setBlock(x, y, z, block); + return getExtent().setBlock(x, y, z, block); } @Override @@ -42,7 +42,7 @@ public class RandomOffsetTransform extends ResettableExtent { x = x + random.nextInt(1 + (dx << 1)) - dx; y = y + random.nextInt(1 + (dy << 1)) - dy; z = z + random.nextInt(1 + (dz << 1)) - dz; - return super.setBlock(x, y, z, block); + return getExtent().setBlock(x, y, z, block); } @Override diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java index ff85e749..dd913986 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java @@ -149,6 +149,7 @@ public class CreateFromImage extends Command { fp.sendMessage(BBC.getPrefix() + "/2 cfi ore[s]"); fp.sendMessage(BBC.getPrefix() + "/2 cfi schem [url] "); fp.sendMessage(BBC.getPrefix() + "/2 cfi height "); + fp.sendMessage(BBC.getPrefix() + "/2 cfi smooth [whiteonly]"); fp.sendMessage(BBC.getPrefix() + "/2 cfi waterHeight "); fp.sendMessage(BBC.getPrefix() + "/2 cfi waterId "); fp.sendMessage(BBC.getPrefix() + "/2 cfi color "); @@ -433,6 +434,25 @@ public class CreateFromImage extends Command { player.sendMessage(BBC.getPrefix() + "Set biome, what's next?"); return; } + case "smooth": { + int id; + if (argList.size() < 4) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " [whiteonly]"); + return; + } + int radius = Integer.parseInt(argList.get(2)); + int iterations = Integer.parseInt(argList.get(3)); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 5 && Boolean.parseBoolean(argList.get(4)); + generator.smooth(img, whiteOnly, radius, iterations); + } else { + Mask mask = we.getMaskFactory().parseFromInput(argList.get(1), context); + generator.smooth(mask, radius, iterations); + } + player.sendMessage(BBC.getPrefix() + "Smoothed terrain, what's next?"); + return; + } case "overlay": case "setoverlay": { Pattern id; @@ -552,7 +572,7 @@ public class CreateFromImage extends Command { player.sendMessage(BBC.getPrefix() + "Cancelled!"); return; default: - C.COMMAND_SYNTAX.send(player, "/2 cfi "); + C.COMMAND_SYNTAX.send(player, "/2 cfi "); return; } } catch (IOException e) { diff --git a/core/src/main/java/com/boydti/fawe/util/MathMan.java b/core/src/main/java/com/boydti/fawe/util/MathMan.java index bea5428a..75e40991 100644 --- a/core/src/main/java/com/boydti/fawe/util/MathMan.java +++ b/core/src/main/java/com/boydti/fawe/util/MathMan.java @@ -81,6 +81,10 @@ public class MathMan { return floatNumber > (float) floor ? floor + 1 : floor; } + public static int sqr(int val) { + return val * val; + } + public static int clamp(int check, int min, int max) { return check > max ? max : (check < min ? min : check); } diff --git a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index fb7b7e24..57f7c781 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -19,22 +19,32 @@ package com.sk89q.worldedit.command; +import com.boydti.fawe.Fawe; import com.boydti.fawe.command.FawePrimitiveBinding; import com.boydti.fawe.config.BBC; import com.boydti.fawe.jnbt.anvil.generator.CavesGen; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.TextureUtil; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.Logging; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.internal.expression.ExpressionException; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.TreeGenerator.TreeType; @@ -44,6 +54,10 @@ import com.sk89q.worldedit.util.command.binding.Text; import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.util.command.parametric.ParameterException; import com.sk89q.worldedit.world.biome.BaseBiome; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import javax.imageio.ImageIO; import static com.google.common.base.Preconditions.checkNotNull; @@ -92,13 +106,54 @@ public class GenerationCommands { min = 1, max = 1 ) - @CommandPermissions("worldedit.generation.cylinder") + @CommandPermissions("worldedit.generation.ore") @Logging(PLACEMENT) public void ores(Player player, LocalSession session, EditSession editSession, @Selection Region region, Mask mask) throws WorldEditException, ParameterException { editSession.addOres(region, mask); BBC.VISITOR_BLOCK.send(player, editSession.getBlockChangeCount()); } + @Command( + aliases = { "/image" }, + desc = "Generate an image", + usage = " [randomize=true] [complexity=100]", + min = 1, + max = 3 + ) + @CommandPermissions("worldedit.generation.image") + @Logging(PLACEMENT) + public void image(Player player, LocalSession session, EditSession editSession, String arg, @Optional("true") boolean randomize, @Optional("100") int threshold) throws WorldEditException, ParameterException, IOException { + TextureUtil tu = Fawe.get().getCachedTextureUtil(randomize, 0, threshold); + URL url = new URL(arg); + if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { + throw new IOException("Only i.imgur.com links are allowed!"); + } + FawePlayer fp = FawePlayer.wrap(player); + BufferedImage image = MainUtil.toRGB(ImageIO.read(url)); + MutableBlockVector pos1 = new MutableBlockVector(player.getPosition()); + MutableBlockVector pos2 = new MutableBlockVector(pos1.add(image.getWidth() - 1, 0, image.getHeight() - 1)); + CuboidRegion region = new CuboidRegion(pos1, pos2); + int[] count = new int[1]; + RegionVisitor visitor = new RegionVisitor(region, new RegionFunction() { + @Override + public boolean apply(Vector pos) throws WorldEditException { + try { + int x = pos.getBlockX() - pos1.getBlockX(); + int z = pos.getBlockZ() - pos1.getBlockZ(); + int color = image.getRGB(x, z); + BaseBlock block = tu.getNearestBlock(color); + count[0]++; + return editSession.setBlockFast(pos, block); + } catch (Throwable e) { + e.printStackTrace(); + } + return false; + } + }, editSession); + Operations.completeBlindly(visitor); + BBC.VISITOR_BLOCK.send(player, editSession.getBlockChangeCount()); + } + @Command( aliases = { "/ore" }, usage = " ", @@ -107,7 +162,7 @@ public class GenerationCommands { min = 7, max = 7 ) - @CommandPermissions("worldedit.generation.cylinder") + @CommandPermissions("worldedit.generation.ore") @Logging(PLACEMENT) public void ore(Player player, LocalSession session, EditSession editSession, @Selection Region region, Mask mask, Pattern material, int size, int freq, int rarity, int minY, int maxY) throws WorldEditException, ParameterException { editSession.addOre(region, mask, material, size, freq, rarity, minY, maxY); diff --git a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java index 41caa279..b45ff3c2 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java @@ -158,7 +158,7 @@ public class MaskCommands extends MethodCommands { } @Command( - aliases = {"#existing"}, + aliases = {"#solid"}, desc = "If there is a solid block" ) public Mask solid(EditSession extent) {