From 147cfeed102c6cfc954e688a87fdc037f82b49f8 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Mon, 13 Mar 2017 03:17:22 +1100 Subject: [PATCH] Add surface spline --- .../boydti/fawe/object/brush/SplineBrush.java | 7 -- .../fawe/object/brush/SurfaceSpline.java | 82 +++++++++++++++++++ .../clipboard/DiskOptimizedClipboard.java | 1 - .../java/com/boydti/fawe/util/MathMan.java | 16 ++++ .../java/com/sk89q/worldedit/EditSession.java | 80 +++++++++--------- .../worldedit/command/BrushCommands.java | 21 +++++ .../factory/HashTagPatternParser.java | 1 - 7 files changed, 158 insertions(+), 50 deletions(-) create mode 100644 core/src/main/java/com/boydti/fawe/object/brush/SurfaceSpline.java diff --git a/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java index 273464c5..fdce7979 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/SplineBrush.java @@ -18,7 +18,6 @@ import com.sk89q.worldedit.function.mask.MaskIntersection; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.interpolation.Node; -import com.sk89q.worldedit.math.transform.AffineTransform; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -125,12 +124,6 @@ public class SplineBrush implements Brush { n.setContinuity(continuity); nodes.add(n); } - - Vector up = new Vector(0, 1, 0); - AffineTransform transform = new AffineTransform(); - - // TODO index offset based on transform - int samples = numSplines; for (int i = 0; i < numSplines; i++) { List currentSpline = new ArrayList<>(); diff --git a/core/src/main/java/com/boydti/fawe/object/brush/SurfaceSpline.java b/core/src/main/java/com/boydti/fawe/object/brush/SurfaceSpline.java new file mode 100644 index 00000000..fd77ac7f --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/SurfaceSpline.java @@ -0,0 +1,82 @@ +package com.boydti.fawe.object.brush; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.collection.LocalBlockVectorSet; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.command.tool.brush.Brush; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; +import com.sk89q.worldedit.math.interpolation.Node; +import java.util.ArrayList; +import java.util.List; + +public class SurfaceSpline implements Brush { + final double tension, bias,continuity, quality; + + public SurfaceSpline(final double tension, final double bias, final double continuity, final double quality) { + this.tension = tension; + this.bias = bias; + this.continuity = continuity; + this.quality = quality; + } + + private ArrayList path = new ArrayList<>(); + + @Override + public void build(EditSession editSession, Vector pos, Pattern pattern, double radius) throws MaxChangedBlocksException { + int maxY = editSession.getMaxY(); + if (path.isEmpty() || !pos.equals(path.get(path.size() - 1))) { + int max = editSession.getNearestSurfaceTerrainBlock(pos.getBlockX(), pos.getBlockZ(), pos.getBlockY(), 0, editSession.getMaxY()); + pos.mutY(max); + path.add(pos); + editSession.getPlayer().sendMessage(BBC.getPrefix() + BBC.BRUSH_SPLINE_PRIMARY_2.s()); + } else{ + LocalBlockVectorSet vset = new LocalBlockVectorSet(); + final List nodes = new ArrayList<>(path.size()); + final KochanekBartelsInterpolation interpol = new KochanekBartelsInterpolation(); + + for (final Vector nodevector : path) { + final Node n = new Node(nodevector); + n.setTension(tension); + n.setBias(bias); + n.setContinuity(continuity); + nodes.add(n); + } + interpol.setNodes(nodes); + final double splinelength = interpol.arcLength(0, 1); + for (double loop = 0; loop <= 1; loop += 1D / splinelength / quality) { + final Vector tipv = interpol.getPosition(loop); + final int tipx = (int) Math.round(tipv.getX()); + final int tipz = (int) tipv.getZ(); + int tipy = (int) Math.round(tipv.getY()); + tipy = editSession.getNearestSurfaceTerrainBlock(tipx, tipz, tipy, 0, maxY); + if (radius == 0) { + editSession.setBlock(tipx, tipy, tipz, pattern.next(tipx, tipy, tipz)); + } else { + vset.add(tipx, tipy, tipz); + } + } + if (radius != 0) { + double radius2 = (radius * radius); + LocalBlockVectorSet newSet = new LocalBlockVectorSet(); + final int ceilrad = (int) Math.ceil(radius); + for (final Vector v : vset) { + final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ(); + for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) { + for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) { + if (MathMan.hypot2(loopx - tipx, 0, loopz - tipz) <= radius2) { + int y = editSession.getNearestSurfaceTerrainBlock(loopx, loopz, v.getBlockY(), 0, maxY); + newSet.add(loopx, y, loopz); + } + } + } + } + editSession.setBlocks(newSet, pattern); + } + editSession.getPlayer().sendMessage(BBC.getPrefix() + BBC.BRUSH_SPLINE_SECONDARY.s()); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java index a5180461..91bb3337 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java @@ -36,7 +36,6 @@ import java.util.UUID; * - Uses an auto closable RandomAccessFile for getting / setting id / data * - I don't know how to reduce nbt / entities to O(2) complexity, so it is stored in memory. * - * TODO load on join */ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable { 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 24512c15..ff438efd 100644 --- a/core/src/main/java/com/boydti/fawe/util/MathMan.java +++ b/core/src/main/java/com/boydti/fawe/util/MathMan.java @@ -43,6 +43,22 @@ public class MathMan { 253, 254, 254, 255 }; + public static double hypot(final double... pars) { + double sum = 0; + for (final double d : pars) { + sum += Math.pow(d, 2); + } + return Math.sqrt(sum); + } + + public static double hypot2(final double... pars) { + double sum = 0; + for (final double d : pars) { + sum += Math.pow(d, 2); + } + return sum; + } + public static final int wrap(int value, int min, int max) { if (max < min) { return value; diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 8da155eb..4e7587f1 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -58,6 +58,7 @@ import com.boydti.fawe.object.progress.DefaultProgressTracker; import com.boydti.fawe.object.visitor.FastChunkIterator; import com.boydti.fawe.util.ExtentTraverser; import com.boydti.fawe.util.MaskTraverser; +import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.Perm; import com.boydti.fawe.util.SetQueue; @@ -1142,7 +1143,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } @SuppressWarnings("deprecation") - private int setBlocks(final Set vset, final Pattern pattern) throws MaxChangedBlocksException { + public int setBlocks(final Set vset, final Pattern pattern) throws MaxChangedBlocksException { RegionVisitor visitor = new RegionVisitor(vset, new BlockReplace(extent, pattern), this); Operations.completeBlindly(visitor); changes += visitor.getAffected(); @@ -1713,7 +1714,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } // public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) throws MaxChangedBlocksException { - // TODO + // TODO fast replace // } /** @@ -2663,7 +2664,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting for (int y = basePosition.getBlockY(); y >= (basePosition.getBlockY() - 10); --y) { final int t = getLazyBlock(x, y, z).getType(); if ((t == BlockID.GRASS) || (t == BlockID.DIRT)) { - treeGenerator.generate(EditSession.this, new Vector(x, y + 1, z)); + treeGenerator.generate(EditSession.this, mutable.setComponents(x, y + 1, z)); break; } else if (t == BlockID.SNOW) { setBlock(x, y, z, nullBlock); @@ -2810,7 +2811,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting final ArbitraryShape shape = new ArbitraryShape(region) { @Override public BaseBlock getMaterial(final int x, final int y, final int z, final BaseBlock defaultMaterial) { - final Vector current = new Vector(x, y, z); + //TODO Optimize - avoid vector creation (math) + final Vector current = mutable.setComponents(x, y, z); environment.setCurrentBlock(current); final Vector scaled = current.subtract(zero).divide(unit); @@ -2973,7 +2975,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting */ public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) throws MaxChangedBlocksException { - Set vset = new LocalBlockVectorSet(); + LocalBlockVectorSet vset = new LocalBlockVectorSet(); boolean notdrawn = true; final int x1 = pos1.getBlockX(), y1 = pos1.getBlockY(), z1 = pos1.getBlockZ(); @@ -2982,7 +2984,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting final int dx = Math.abs(x2 - x1), dy = Math.abs(y2 - y1), dz = Math.abs(z2 - z1); if ((dx + dy + dz) == 0) { - vset.add(new Vector(tipx, tipy, tipz)); + vset.add(tipx, tipy, tipz); notdrawn = false; } @@ -2991,7 +2993,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting tipx = x1 + (domstep * ((x2 - x1) > 0 ? 1 : -1)); tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dx)) * ((y2 - y1) > 0 ? 1 : -1))); tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dx)) * ((z2 - z1) > 0 ? 1 : -1))); - vset.add(new Vector(tipx, tipy, tipz)); + vset.add(tipx, tipy, tipz); } notdrawn = false; } @@ -3002,7 +3004,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dy)) * ((x2 - x1) > 0 ? 1 : -1))); tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dy)) * ((z2 - z1) > 0 ? 1 : -1))); - vset.add(new Vector(tipx, tipy, tipz)); + vset.add(tipx, tipy, tipz); } notdrawn = false; } @@ -3012,21 +3014,22 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting tipz = z1 + (domstep * ((z2 - z1) > 0 ? 1 : -1)); tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dz)) * ((y2 - y1) > 0 ? 1 : -1))); tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dz)) * ((x2 - x1) > 0 ? 1 : -1))); - vset.add(new Vector(tipx, tipy, tipz)); + vset.add(tipx, tipy, tipz); } } + Set newVset; if (flat) { - vset = this.getStretched(vset, radius); + newVset = this.getStretched(vset, radius); if (!filled) { - vset = this.getOutline(vset); + newVset = this.getOutline(newVset); } } else { - vset = this.getBallooned(vset, radius); + newVset = this.getBallooned(vset, radius); if (!filled) { - vset = this.getHollowed(vset); + newVset = this.getHollowed(newVset); } } - return this.setBlocks(vset, pattern); + return this.setBlocks(newVset, pattern); } /** @@ -3044,10 +3047,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting * @return number of blocks affected * @throws MaxChangedBlocksException thrown if too many blocks are changed */ - public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, - final boolean filled) throws MaxChangedBlocksException { - - Set vset = new LocalBlockVectorSet(); + public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) throws MaxChangedBlocksException { + LocalBlockVectorSet vset = new LocalBlockVectorSet(); final List nodes = new ArrayList(nodevectors.size()); final KochanekBartelsInterpolation interpol = new KochanekBartelsInterpolation(); @@ -3070,40 +3071,33 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting if (radius == 0) { setBlock(tipx, tipy, tipz, pattern.next(tipx, tipy, tipz)); } else { - vset.add(new Vector(tipx, tipy, tipz)); + vset.add(tipx, tipy, tipz); } } + Set newVset; if (radius != 0) { - vset = this.getBallooned(vset, radius); + newVset = this.getBallooned(vset, radius); if (!filled) { - vset = this.getHollowed(vset); + newVset = this.getHollowed(newVset); } - return this.setBlocks(vset, pattern); + return this.setBlocks(newVset, pattern); } return changes; } - private double hypot(final double... pars) { - double sum = 0; - for (final double d : pars) { - sum += Math.pow(d, 2); - } - return Math.sqrt(sum); - } - private Set getBallooned(final Set vset, final double radius) { if (radius < 1) { return vset; } - final Set returnset = new LocalBlockVectorSet(); + final LocalBlockVectorSet returnset = new LocalBlockVectorSet(); final int ceilrad = (int) Math.ceil(radius); for (final Vector v : vset) { final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ(); for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) { for (int loopy = tipy - ceilrad; loopy <= (tipy + ceilrad); loopy++) { for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) { - if (this.hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) { - returnset.add(new Vector(loopx, loopy, loopz)); + if (MathMan.hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) { + returnset.add(loopx, loopy, loopz); } } } @@ -3112,18 +3106,18 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return returnset; } - private Set getStretched(final Set vset, final double radius) { + public Set getStretched(final Set vset, final double radius) { if (radius < 1) { return vset; } - final Set returnset = new LocalBlockVectorSet(); + final LocalBlockVectorSet returnset = new LocalBlockVectorSet(); final int ceilrad = (int) Math.ceil(radius); for (final Vector v : vset) { final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ(); for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) { for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) { - if (this.hypot(loopx - tipx, 0, loopz - tipz) <= radius) { - returnset.add(new Vector(loopx, v.getY(), loopz)); + if (MathMan.hypot(loopx - tipx, 0, loopz - tipz) <= radius) { + returnset.add(loopx, v.getBlockY(), loopz); } } } @@ -3131,8 +3125,9 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return returnset; } - private Set getOutline(final Set vset) { - final Set returnset = new LocalBlockVectorSet(); + public Set getOutline(final Set vset) { + // TODO optimize - vset instanceof LocalBlockVectorSet -> avoid Vector creation + final LocalBlockVectorSet returnset = new LocalBlockVectorSet(); for (final Vector v : vset) { final double x = v.getX(), y = v.getY(), z = v.getZ(); if (!(vset.contains(new Vector(x + 1, y, z)) @@ -3144,7 +3139,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return returnset; } - private Set getHollowed(final Set vset) { + public Set getHollowed(final Set vset) { + //TODO Optimize - avoid vector creation final Set returnset = new LocalBlockVectorSet(); for (final Vector v : vset) { final double x = v.getX(), y = v.getY(), z = v.getZ(); @@ -3159,7 +3155,8 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return returnset; } - private void recurseHollow(final Region region, final BlockVector origin, final Set outside) { + public void recurseHollow(final Region region, final BlockVector origin, final Set outside) { + //TODO Optimize - avoid vector creation final ArrayDeque queue = new ArrayDeque(); queue.addLast(origin); @@ -3283,6 +3280,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } public boolean regenerate(final Region region, final BaseBiome biome, final Long seed) { + //TODO Optimize - avoid Vector2D creation (make mutable) final FaweQueue queue = this.getQueue(); queue.setChangeTask(null); final FaweChangeSet fcs = (FaweChangeSet) this.getChangeSet(); 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 b94576bc..20e627cf 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -41,6 +41,7 @@ import com.boydti.fawe.object.brush.ShatterBrush; import com.boydti.fawe.object.brush.SplatterBrush; import com.boydti.fawe.object.brush.SplineBrush; import com.boydti.fawe.object.brush.StencilBrush; +import com.boydti.fawe.object.brush.SurfaceSpline; import com.boydti.fawe.object.brush.TargetMode; import com.boydti.fawe.object.brush.heightmap.ScalableHeightMap; import com.boydti.fawe.object.brush.scroll.ScrollClipboard; @@ -407,6 +408,26 @@ public class BrushCommands { player.print(BBC.getPrefix() + BBC.BRUSH_SPLINE.f(radius)); } + // final double tension, final double bias, final double continuity, final double quality + + @Command( + aliases = { "sspl", "sspline", "surfacespline" }, + usage = " [size] [tension] [bias] [continuity] [quality]", + desc = "Draws a spline on the surface", + help = "Chooses the surface spline brush", + min = 0, + max = 2 + ) + @CommandPermissions("worldedit.brush.surfacespline") // 0, 0, 0, 10, 0, + public void surfaceSpline(Player player, LocalSession session, Pattern fill, @Optional("0") double radius, @Optional("0") double tension, @Optional("0") double bias, @Optional("0") double continuity, @Optional("10") double quality) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + BrushTool tool = session.getBrushTool(player); + tool.setFill(fill); + tool.setSize(radius); + tool.setBrush(new SurfaceSpline(tension, bias, continuity, quality), "worldedit.brush.spline", player); + player.print(BBC.getPrefix() + BBC.BRUSH_SPLINE.f(radius)); + } + @Command( aliases = { "sphere", "s" }, usage = " [radius]", diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java index 42e16420..e6115c34 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java @@ -148,7 +148,6 @@ public class HashTagPatternParser extends FaweParser { try { ClipboardHolder[] clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(context.getActor(), context.requireWorld().getWorldData(), location, true); if (clipboards == null) { - System.out.println("NULL!"); throw new InputParseException("#fullcopy:"); } boolean random = split2.length == 3 && split2[2].equalsIgnoreCase("true");