From eedc3f4069765a7a55b71b032715b6c66f8e80fc Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Tue, 14 Mar 2017 11:23:50 +1100 Subject: [PATCH] Snow heightmap! --- core/src/main/java/com/boydti/fawe/Fawe.java | 2 + .../fawe/object/brush/FlattenBrush.java | 6 +- .../boydti/fawe/object/brush/HeightBrush.java | 10 +- .../fawe/object/brush/StencilBrush.java | 2 +- .../object/brush/heightmap/HeightMap.java | 54 +++- .../java/com/sk89q/worldedit/EditSession.java | 46 ++++ .../worldedit/command/BrushCommands.java | 34 ++- .../worldedit/math/convolution/HeightMap.java | 242 ++++++++++++++++++ 8 files changed, 361 insertions(+), 35 deletions(-) create mode 100644 core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index 71f7a0f0..99aa4f4a 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -99,6 +99,7 @@ import com.sk89q.worldedit.function.visitor.RecursiveVisitor; import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.history.change.EntityCreate; import com.sk89q.worldedit.history.change.EntityRemove; +import com.sk89q.worldedit.math.convolution.HeightMap; import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.regions.CuboidRegion; @@ -432,6 +433,7 @@ public class Fawe { ExtentEntityCopy.inject(); // Async entity create fix // Transforms FlattenedClipboardTransform.inject(); // public access + HeightMap.inject(); // Optimizations + Features // Entity create/remove EntityCreate.inject(); // Optimizations EntityRemove.inject(); // Optimizations diff --git a/core/src/main/java/com/boydti/fawe/object/brush/FlattenBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/FlattenBrush.java index d1af0a20..071b7f5f 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/FlattenBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/FlattenBrush.java @@ -13,8 +13,8 @@ import java.io.InputStream; public class FlattenBrush extends HeightBrush { - public FlattenBrush(InputStream stream, int rotation, double yscale, Clipboard clipboard, ScalableHeightMap.Shape shape) { - super(stream, rotation, yscale, clipboard, shape); + public FlattenBrush(InputStream stream, int rotation, double yscale, boolean layers, Clipboard clipboard, ScalableHeightMap.Shape shape) { + super(stream, rotation, yscale, layers, clipboard, shape); } @Override @@ -26,6 +26,6 @@ public class FlattenBrush extends HeightBrush { } HeightMap map = getHeightMap(); map.setSize(size); - map.perform(editSession, mask, position, size, rotation, yscale, true, true); + map.perform(editSession, mask, position, size, rotation, yscale, true, true, layers); } } diff --git a/core/src/main/java/com/boydti/fawe/object/brush/HeightBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/HeightBrush.java index aeb85e82..49ece76e 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/HeightBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/HeightBrush.java @@ -23,14 +23,16 @@ public class HeightBrush implements Brush { private boolean randomRotate; public final int rotation; public final double yscale; + public final boolean layers; - public HeightBrush(InputStream stream, int rotation, double yscale, Clipboard clipboard) { - this(stream, rotation, yscale, clipboard, ScalableHeightMap.Shape.CONE); + public HeightBrush(InputStream stream, int rotation, double yscale, boolean layers, Clipboard clipboard) { + this(stream, rotation, yscale, layers, clipboard, ScalableHeightMap.Shape.CONE); } - public HeightBrush(InputStream stream, int rotation, double yscale, Clipboard clipboard, ScalableHeightMap.Shape shape) { + public HeightBrush(InputStream stream, int rotation, double yscale, boolean layers, Clipboard clipboard, ScalableHeightMap.Shape shape) { this.rotation = (rotation / 90) % 4; this.yscale = yscale; + this.layers = layers; if (stream != null) { try { heightMap = ScalableHeightMap.fromPNG(stream); @@ -68,6 +70,6 @@ public class HeightBrush implements Brush { } HeightMap map = getHeightMap(); map.setSize(size); - map.perform(editSession, mask, position, size, rotation, yscale, true, false); + map.perform(editSession, mask, position, size, rotation, yscale, true, false, layers); } } diff --git a/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java index 85335a4a..d0f825b9 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java @@ -24,7 +24,7 @@ public class StencilBrush extends HeightBrush { private final boolean onlyWhite; public StencilBrush(InputStream stream, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard) { - super(stream, rotation, yscale, clipboard); + super(stream, rotation, yscale, false, clipboard); this.onlyWhite = onlyWhite; } diff --git a/core/src/main/java/com/boydti/fawe/object/brush/heightmap/HeightMap.java b/core/src/main/java/com/boydti/fawe/object/brush/heightmap/HeightMap.java index f2bb476c..162afd77 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/heightmap/HeightMap.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/heightmap/HeightMap.java @@ -5,6 +5,7 @@ import com.boydti.fawe.util.MainUtil; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldVector; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.LocalWorldAdapter; @@ -19,12 +20,12 @@ public interface HeightMap { public void setSize(int size); - default void perform(EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards) throws MaxChangedBlocksException { - int[] data = generateHeightData(session, mask, pos, size, rotationMode, yscale, smooth, towards); - applyHeightMapData(data, session, mask, pos, size, rotationMode, yscale, smooth, towards); + default void perform(EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards, boolean layers) throws MaxChangedBlocksException { + int[][] data = generateHeightData(session, mask, pos, size, rotationMode, yscale, smooth, towards, layers); + applyHeightMapData(data, session, mask, pos, size, rotationMode, yscale, smooth, towards, layers); } - default void applyHeightMapData(int[] data, EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards) throws MaxChangedBlocksException { + default void applyHeightMapData(int[][] data, EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards, boolean layers) throws MaxChangedBlocksException { Vector top = session.getMaximumPoint(); int maxY = top.getBlockY(); int diameter = 2 * size + 1; @@ -32,19 +33,29 @@ public interface HeightMap { WorldVector min = new WorldVector(LocalWorldAdapter.adapt(session.getWorld()), pos.subtract(size, maxY, size)); Vector max = pos.add(size, maxY, size); Region region = new CuboidRegion(session.getWorld(), min, max); - com.sk89q.worldedit.math.convolution.HeightMap heightMap = new com.sk89q.worldedit.math.convolution.HeightMap(session, region, false); + com.sk89q.worldedit.math.convolution.HeightMap heightMap = new com.sk89q.worldedit.math.convolution.HeightMap(session, region, data[0]); if (smooth) { try { HeightMapFilter filter = (HeightMapFilter) HeightMapFilter.class.getConstructors()[0].newInstance(GaussianKernel.class.getConstructors()[0].newInstance(5, 1)); - data = filter.filter(data, diameter, diameter); + data[1] = filter.filter(data[1], diameter, diameter); } catch (Throwable e) { MainUtil.handleError(e); } } - heightMap.apply(data); + try { + if (layers) { + heightMap.applyLayers(data[1]); + } else { + heightMap.apply(data[1]); + } + } catch (MaxChangedBlocksException e) { + throw e; + } catch (WorldEditException e2) { + throw new RuntimeException(e2); + } } - default int[] generateHeightData(EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards) { + default int[][] generateHeightData(EditSession session, Mask mask, Vector pos, int size, int rotationMode, double yscale, boolean smooth, boolean towards, final boolean layers) { Vector top = session.getMaximumPoint(); int maxY = top.getBlockY(); int diameter = 2 * size + 1; @@ -53,7 +64,12 @@ public interface HeightMap { int centerY = pos.getBlockY(); int endY = pos.getBlockY() + size; int startY = pos.getBlockY() - size; - int[] newData = new int[diameter * diameter]; + int[] oldData = new int[diameter * diameter]; + int[] newData = new int[oldData.length]; + if (layers) { // Pixel accuracy + centerY <<= 3; + maxY <<= 3; + } Vector mutablePos = new Vector(0, 0, 0); if (towards) { double sizePow = Math.pow(size, yscale); @@ -79,7 +95,13 @@ public interface HeightMap { raise = getHeight(-z, -x); break; } - int height = session.getNearestSurfaceTerrainBlock(xx, zz, pos.getBlockY(), 0, 255); + int height; + if (layers) { + height = session.getNearestSurfaceLayer(xx, zz, pos.getBlockY(), 0, maxY); + } else { + height = session.getNearestSurfaceTerrainBlock(xx, zz, pos.getBlockY(), 0, maxY); + } + oldData[index] = height; if (height == 0) { newData[index] = centerY; continue; @@ -115,18 +137,24 @@ public interface HeightMap { raise = getHeight(-z, -x); break; } - int height = session.getNearestSurfaceTerrainBlock(xx, zz, pos.getBlockY(), 0, maxY); + int height; + if (layers) { + height = session.getNearestSurfaceLayer(xx, zz, pos.getBlockY(), 0, maxY); + } else { + height = session.getNearestSurfaceTerrainBlock(xx, zz, pos.getBlockY(), 0, 255); + } + oldData[index] = height; if (height == 0) { newData[index] = centerY; continue; } raise = (yscale * raise); - int random = PseudoRandom.random.random(maxY + 1) < (int) ((raise - (int) raise) * (maxY + 1)) ? 1 : 0; + int random = PseudoRandom.random.random(256) < (int) ((raise - (int) raise) * (256)) ? 1 : 0; int newHeight = height + (int) raise + random; newData[index] = newHeight; } } } - return newData; + return new int[][] {oldData, newData}; } } diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 33ad67e3..e50e9c7a 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -981,6 +981,52 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return this.getHighestTerrainBlock(x, z, minY, maxY, false); } + public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { + int clearanceAbove = maxY - y; + int clearanceBelow = y - minY; + int clearance = Math.min(clearanceAbove, clearanceBelow); + + BaseBlock block = getBlock(x, y, z); + boolean state = FaweCache.isLiquidOrGas(block.getId()); + int data1 = block.getData(); + int data2 = block.getData(); + int offset = state ? 0 : 1; + for (int d = 0; d <= clearance; d++) { + int y1 = y + d; + block = getLazyBlock(x, y1, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((y1 - offset) << 3) - (7 - (state ? block.getData() : data1)); + } + data1 = block.getData(); + int y2 = y - d; + block = getLazyBlock(x, y2, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((y2 + offset) << 3) - (7 - (state ? block.getData() : data2)); + } + data2 = block.getData(); + } + if (clearanceAbove != clearanceBelow) { + if (clearanceAbove < clearanceBelow) { + for (int layer = y - clearance - 1; layer >= minY; layer--) { + block = getLazyBlock(x, layer, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((layer + offset) << 3) - (7 - (state ? block.getData() : data1)); + } + data1 = block.getData(); + } + } else { + for (int layer = y + clearance + 1; layer <= maxY; layer++) { + block = getLazyBlock(x, layer, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((layer - offset) << 3) - (7 - (state ? block.getData() : data2)); + } + data2 = block.getData(); + } + } + } + return maxY << 4; + } + public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { int clearanceAbove = maxY - y; int clearanceBelow = y - minY; diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 20e627cf..07d3f10c 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -714,13 +714,15 @@ public class BrushCommands { flags = "h", desc = "Height brush", help = - "This brush raises and lowers land.\n", + "This brush raises and lowers land.\n" + + "The -r flag enables random off-axis rotation\n" + + "The -l flag will work on snow layers", min = 1, max = 4 ) @CommandPermissions("worldedit.brush.height") - public void heightBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate) throws WorldEditException { - terrainBrush(player, session, radius, filename, rotation, yscale, false, randomRotate, ScalableHeightMap.Shape.CONE); + public void heightBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate, @Switch('l') boolean layers) throws WorldEditException { + terrainBrush(player, session, radius, filename, rotation, yscale, false, randomRotate, layers, ScalableHeightMap.Shape.CONE); } @Command( @@ -729,28 +731,32 @@ public class BrushCommands { flags = "h", desc = "Cliff brush", help = - "This brush flattens terrain and creates cliffs.\n", + "This brush flattens terrain and creates cliffs.\n" + + "The -r flag enables random off-axis rotation\n" + + "The -l flag will work on snow layers", min = 1, max = 4 ) @CommandPermissions("worldedit.brush.height") - public void cliffBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate) throws WorldEditException { - terrainBrush(player, session, radius, filename, rotation, yscale, true, randomRotate, ScalableHeightMap.Shape.CYLINDER); + public void cliffBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate, @Switch('l') boolean layers) throws WorldEditException { + terrainBrush(player, session, radius, filename, rotation, yscale, true, randomRotate, layers, ScalableHeightMap.Shape.CYLINDER); } @Command( aliases = { "flatten", "flatmap", "flat" }, usage = "[radius] [file|#clipboard|null] [rotation] [yscale]", flags = "h", - desc = "Flatten brush", + desc = "Flatten brush makes terrain flatter\n" + + "The -r flag enables random off-axis rotation\n" + + "The -l flag will work on snow layers", help = "This brush raises and lowers land towards the clicked point\n", min = 1, max = 4 ) @CommandPermissions("worldedit.brush.height") - public void flattenBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate) throws WorldEditException { - terrainBrush(player, session, radius, filename, rotation, yscale, true, randomRotate, ScalableHeightMap.Shape.CONE); + public void flattenBrush(Player player, LocalSession session, @Optional("5") double radius, @Optional("") final String filename, @Optional("0") final int rotation, @Optional("1") final double yscale, @Switch('r') boolean randomRotate, @Switch('l') boolean layers) throws WorldEditException { + terrainBrush(player, session, radius, filename, rotation, yscale, true, randomRotate, layers, ScalableHeightMap.Shape.CONE); } private InputStream getHeightmapStream(String filename) { @@ -784,7 +790,7 @@ public class BrushCommands { return null; } - private void terrainBrush(Player player, LocalSession session, double radius, String filename, int rotation, double yscale, boolean flat, boolean randomRotate, ScalableHeightMap.Shape shape) throws WorldEditException { + private void terrainBrush(Player player, LocalSession session, double radius, String filename, int rotation, double yscale, boolean flat, boolean randomRotate, boolean layers, ScalableHeightMap.Shape shape) throws WorldEditException { worldEdit.checkMaxBrushRadius(radius); InputStream stream = getHeightmapStream(filename); BrushTool tool = session.getBrushTool(player); @@ -792,15 +798,15 @@ public class BrushCommands { HeightBrush brush; if (flat) { try { - brush = new FlattenBrush(stream, rotation, yscale, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null, shape); + brush = new FlattenBrush(stream, rotation, yscale, layers, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null, shape); } catch (EmptyClipboardException ignore) { - brush = new FlattenBrush(stream, rotation, yscale, null, shape); + brush = new FlattenBrush(stream, rotation, yscale, layers, null, shape); } } else { try { - brush = new HeightBrush(stream, rotation, yscale, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null); + brush = new HeightBrush(stream, rotation, yscale, layers, filename.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null); } catch (EmptyClipboardException ignore) { - brush = new HeightBrush(stream, rotation, yscale, null); + brush = new HeightBrush(stream, rotation, yscale, layers, null); } } tool.setBrush(brush, "worldedit.brush.height", player); diff --git a/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java b/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java new file mode 100644 index 00000000..7315b2c6 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java @@ -0,0 +1,242 @@ +package com.sk89q.worldedit.math.convolution; + +import com.boydti.fawe.FaweCache; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.regions.Region; + + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Allows applications of Kernels onto the region's height map. + * + *

Currently only used for smoothing (with a GaussianKernel)

. + */ +public class HeightMap { + + private int[] data; + private int width; + private int height; + + private Region region; + private EditSession session; + + /** + * Constructs the HeightMap + * + * @param session an edit session + * @param region the region + */ + public HeightMap(EditSession session, Region region) { + this(session, region, false); + } + + public HeightMap(EditSession session, Region region, boolean naturalOnly) { + checkNotNull(session); + checkNotNull(region); + + this.session = session; + this.region = region; + + this.width = region.getWidth(); + this.height = region.getLength(); + + int minX = region.getMinimumPoint().getBlockX(); + int minY = region.getMinimumPoint().getBlockY(); + int minZ = region.getMinimumPoint().getBlockZ(); + int maxY = region.getMaximumPoint().getBlockY(); + + // Store current heightmap data + data = new int[width * height]; + for (int z = 0; z < height; ++z) { + for (int x = 0; x < width; ++x) { + data[z * width + x] = session.getHighestTerrainBlock(x + minX, z + minZ, minY, maxY, naturalOnly); + } + } + } + + public HeightMap(EditSession session, Region region, int[] data) { + this.session = session; + this.region = region; + + this.width = region.getWidth(); + this.height = region.getLength(); + + this.data = data; + } + + /** + * Apply the filter 'iterations' amount times. + * + * @param filter the filter + * @param iterations the number of iterations + * @return number of blocks affected + * @throws MaxChangedBlocksException + */ + + public int applyFilter(HeightMapFilter filter, int iterations) throws WorldEditException { + checkNotNull(filter); + + int[] newData = new int[data.length]; + System.arraycopy(data, 0, newData, 0, data.length); + + for (int i = 0; i < iterations; ++i) { + newData = filter.filter(newData, width, height); + } + + return apply(newData); + } + + public int applyLayers(int[] data) throws WorldEditException { + checkNotNull(data); + + Vector minY = region.getMinimumPoint(); + int originX = minY.getBlockX(); + int originY = minY.getBlockY(); + int originZ = minY.getBlockZ(); + + int maxY = region.getMaximumPoint().getBlockY(); + BaseBlock fillerAir = EditSession.nullBlock; + + int blocksChanged = 0; + + // Apply heightmap + int maxY4 = maxY << 4; + int index = 0; + for (int z = 0; z < height; ++z) { + int zr = z + originZ; + for (int x = 0; x < width; ++x) { + int curHeight = this.data[index]; + int newHeight = Math.min(maxY4, data[index++]); + int curBlock = (curHeight) >> 3; + int newBlock = (newHeight + 7) >> 3; + int xr = x + originX; + + // We are keeping the topmost blocks so take that in account for the scale + double scale = (double) (curHeight - originY) / (double) (newHeight - originY); + + // Depending on growing or shrinking we need to start at the bottom or top + if (newHeight > curHeight) { + // Set the top block of the column to be the same type (this might go wrong with rounding) + BaseBlock existing = session.getBlock(xr, curBlock, zr); + + // Skip water/lava + if (!FaweCache.isLiquidOrGas(existing.getId())) { + // Grow -- start from 1 below top replacing airblocks + for (int y = newBlock - 1 - originY; y >= curBlock; --y) { + int copyFrom = (int) (y * scale); + session.setBlock(xr, originY + y, zr, session.getBlock(xr, originY + copyFrom, zr)); + ++blocksChanged; + } + int setData = newHeight & 7; + if (setData != 0) { + existing = FaweCache.getBlock(existing.getId(), setData - 1); + session.setBlock(xr, newBlock, zr, existing); + ++blocksChanged; + } else { + existing = FaweCache.getBlock(existing.getId(), 7); + session.setBlock(xr, newBlock, zr, existing); + ++blocksChanged; + } + } + } else if (curHeight > newHeight) { + // Fill rest with air + for (int y = newBlock + 1; y <= ((curHeight + 7) >> 3); ++y) { + session.setBlock(xr, y, zr, fillerAir); + ++blocksChanged; + } + // Set the top block of the column to be the same type + // (this could otherwise go wrong with rounding) + int setData = newHeight & 7; + BaseBlock existing = session.getBlock(xr, curBlock, zr); + if (setData != 0) { + existing = FaweCache.getBlock(existing.getId(), setData - 1); + session.setBlock(xr, newBlock, zr, existing); + } else { + existing = FaweCache.getBlock(existing.getId(), 7); + session.setBlock(xr, newBlock, zr, existing); + } + ++blocksChanged; + } + } + } + + return blocksChanged; + } + + public int apply(int[] data) throws WorldEditException { + checkNotNull(data); + + Vector minY = region.getMinimumPoint(); + int originX = minY.getBlockX(); + int originY = minY.getBlockY(); + int originZ = minY.getBlockZ(); + + int maxY = region.getMaximumPoint().getBlockY(); + BaseBlock fillerAir = EditSession.nullBlock; + + int blocksChanged = 0; + + // Apply heightmap + int index = 0; + for (int z = 0; z < height; ++z) { + int zr = z + originZ; + for (int x = 0; x < width; ++x) { + int curHeight = this.data[index]; + int newHeight = Math.min(maxY, data[index++]); + int xr = x + originX; + + // We are keeping the topmost blocks so take that in account for the scale + double scale = (double) (curHeight - originY) / (double) (newHeight - originY); + + // Depending on growing or shrinking we need to start at the bottom or top + if (newHeight > curHeight) { + // Set the top block of the column to be the same type (this might go wrong with rounding) + BaseBlock existing = session.getBlock(xr, curHeight, zr); + + // Skip water/lava + if (!FaweCache.isLiquidOrGas(existing.getId())) { + session.setBlock(xr, newHeight, zr, existing); + ++blocksChanged; + + // Grow -- start from 1 below top replacing airblocks + for (int y = newHeight - 1 - originY; y >= 0; --y) { + int copyFrom = (int) (y * scale); + session.setBlock(xr, originY + y, zr, session.getBlock(xr, originY + copyFrom, zr)); + ++blocksChanged; + } + } + } else if (curHeight > newHeight) { + // Shrink -- start from bottom + for (int y = 0; y < newHeight - originY; ++y) { + int copyFrom = (int) (y * scale); + session.setBlock(xr, originY + y, zr, session.getBlock(xr, originY + copyFrom, zr)); + ++blocksChanged; + } + + // Set the top block of the column to be the same type + // (this could otherwise go wrong with rounding) + session.setBlock(xr, newHeight, zr, session.getBlock(xr, curHeight, zr)); + ++blocksChanged; + + // Fill rest with air + for (int y = newHeight + 1; y <= curHeight; ++y) { + session.setBlock(xr, y, zr, fillerAir); + ++blocksChanged; + } + } + } + } + + return blocksChanged; + } + + public static Class inject() { + return HeightMap.class; + } + +}