Skip to content

Commit

Permalink
Merge pull request #275 from sk89q/linecurve
Browse files Browse the repository at this point in the history
//line and //curve by @orthoplex64
  • Loading branch information
sk89q committed Nov 1, 2013
2 parents 9930fb7 + 22d6c51 commit 60f6580
Show file tree
Hide file tree
Showing 7 changed files with 970 additions and 6 deletions.
174 changes: 174 additions & 0 deletions src/main/java/com/sk89q/worldedit/EditSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
import com.sk89q.worldedit.expression.Expression;
import com.sk89q.worldedit.expression.ExpressionException;
import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.interpolation.Interpolation;
import com.sk89q.worldedit.interpolation.KochanekBartelsInterpolation;
import com.sk89q.worldedit.interpolation.Node;
import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.patterns.Pattern;
import com.sk89q.worldedit.regions.CuboidRegion;
Expand Down Expand Up @@ -2996,6 +2999,177 @@ public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws
return affected;
}

/**
* Draws a line (out of blocks) between two vectors.
*
* @param pattern The block pattern used to draw the line.
* @param pos1 One of the points that define the line.
* @param pos2 The other point that defines the line.
* @param radius The radius (thickness) of the line.
* @param filled If false, only a shell will be generated.
*
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int drawLine(Pattern pattern, Vector pos1, Vector pos2, double radius, boolean filled)
throws MaxChangedBlocksException {

Set<Vector> vset = new HashSet<Vector>();
boolean notdrawn = true;

int x1 = pos1.getBlockX(), y1 = pos1.getBlockY(), z1 = pos1.getBlockZ();
int x2 = pos2.getBlockX(), y2 = pos2.getBlockY(), z2 = pos2.getBlockZ();
int tipx = x1, tipy = y1, tipz = z1;
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));
notdrawn = false;
}

if (Math.max(Math.max(dx, dy), dz) == dx && notdrawn) {
for (int domstep = 0; domstep <= dx; domstep++) {
tipx = x1 + domstep * (x2 - x1 > 0 ? 1 : -1);
tipy = (int) Math.round(y1 + domstep * ((double) dy) / ((double) dx) * (y2 - y1 > 0 ? 1 : -1));
tipz = (int) Math.round(z1 + domstep * ((double) dz) / ((double) dx) * (z2 - z1 > 0 ? 1 : -1));

vset.add(new Vector(tipx, tipy, tipz));
}
notdrawn = false;
}

if (Math.max(Math.max(dx, dy), dz) == dy && notdrawn) {
for (int domstep = 0; domstep <= dy; domstep++) {
tipy = y1 + domstep * (y2 - y1 > 0 ? 1 : -1);
tipx = (int) Math.round(x1 + domstep * ((double) dx) / ((double) dy) * (x2 - x1 > 0 ? 1 : -1));
tipz = (int) Math.round(z1 + domstep * ((double) dz) / ((double) dy) * (z2 - z1 > 0 ? 1 : -1));

vset.add(new Vector(tipx, tipy, tipz));
}
notdrawn = false;
}

if (Math.max(Math.max(dx, dy), dz) == dz && notdrawn) {
for (int domstep = 0; domstep <= dz; domstep++) {
tipz = z1 + domstep * (z2 - z1 > 0 ? 1 : -1);
tipy = (int) Math.round(y1 + domstep * ((double) dy) / ((double) dz) * (y2-y1>0 ? 1 : -1));
tipx = (int) Math.round(x1 + domstep * ((double) dx) / ((double) dz) * (x2-x1>0 ? 1 : -1));

vset.add(new Vector(tipx, tipy, tipz));
}
notdrawn = false;
}

vset = getBallooned(vset, radius);
if (!filled) {
vset = getHollowed(vset);
}
return setBlocks(vset, pattern);
}

/**
* Draws a spline (out of blocks) between specified vectors.
*
* @param pattern The block pattern used to draw the spline.
* @param nodevectors The list of vectors to draw through.
* @param tension The tension of every node.
* @param bias The bias of every node.
* @param continuity The continuity of every node.
* @param quality The quality of the spline. Must be greater than 0.
* @param radius The radius (thickness) of the spline.
* @param filled If false, only a shell will be generated.
*
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int drawSpline(Pattern pattern, List<Vector> nodevectors, double tension, double bias, double continuity, double quality, double radius, boolean filled)
throws MaxChangedBlocksException {

Set<Vector> vset = new HashSet<Vector>();
List<Node> nodes = new ArrayList(nodevectors.size());

Interpolation interpol = new KochanekBartelsInterpolation();

for (int loop = 0; loop < nodevectors.size(); loop++) {
Node n = new Node(nodevectors.get(loop));
n.setTension(tension);
n.setBias(bias);
n.setContinuity(continuity);
nodes.add(n);
}

interpol.setNodes(nodes);
double splinelength = interpol.arcLength(0, 1);
for (double loop = 0; loop <= 1; loop += 1D / splinelength / quality) {
Vector tipv = interpol.getPosition(loop);
int tipx = (int) Math.round(tipv.getX());
int tipy = (int) Math.round(tipv.getY());
int tipz = (int) Math.round(tipv.getZ());

vset.add(new Vector(tipx, tipy, tipz));
}

vset = getBallooned(vset, radius);
if (!filled) {
vset = getHollowed(vset);
}
return setBlocks(vset, pattern);
}

private static double hypot(double... pars) {
double sum = 0;
for (double d : pars) {
sum += Math.pow(d, 2);
}
return Math.sqrt(sum);
}

private static Set<Vector> getBallooned(Set<Vector> vset, double radius) {
Set<Vector> returnset = new HashSet<Vector>();
int ceilrad = (int) Math.ceil(radius);

for (Vector v : vset) {
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 (hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) {
returnset.add(new Vector(loopx, loopy, loopz));
}
}
}
}
}
return returnset;
}

private static Set<Vector> getHollowed(Set<Vector> vset) {
Set<Vector> returnset = new HashSet<Vector>();
for (Vector v : vset) {
double x = v.getX(), y = v.getY(), z = v.getZ();
if (!(vset.contains(new Vector(x + 1, y, z)) &&
vset.contains(new Vector(x - 1, y, z)) &&
vset.contains(new Vector(x, y + 1, z)) &&
vset.contains(new Vector(x, y - 1, z)) &&
vset.contains(new Vector(x, y, z + 1)) &&
vset.contains(new Vector(x, y, z - 1)))) {
returnset.add(v);
}
}
return returnset;
}

private int setBlocks(Set<Vector> vset, Pattern pattern)
throws MaxChangedBlocksException {

int affected = 0;
for (Vector v : vset) {
affected += setBlock(v, pattern) ? 1 : 0;
}
return affected;
}

private void recurseHollow(Region region, BlockVector origin, Set<BlockVector> outside) {
final LinkedList<BlockVector> queue = new LinkedList<BlockVector>();
queue.addLast(origin);
Expand Down
89 changes: 83 additions & 6 deletions src/main/java/com/sk89q/worldedit/commands/RegionCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import static com.sk89q.minecraft.util.commands.Logging.LogMode.ORIENTATION_REGION;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.sk89q.minecraft.util.commands.Command;
Expand All @@ -41,12 +43,13 @@
import com.sk89q.worldedit.expression.ExpressionException;
import com.sk89q.worldedit.filtering.GaussianKernel;
import com.sk89q.worldedit.filtering.HeightMapFilter;
import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.patterns.Pattern;
import com.sk89q.worldedit.patterns.SingleBlockPattern;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.patterns.Pattern;
import com.sk89q.worldedit.patterns.SingleBlockPattern;
import com.sk89q.worldedit.regions.ConvexPolyhedralRegion;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;

/**
* Region related commands.
Expand Down Expand Up @@ -85,6 +88,80 @@ public void set(CommandContext args, LocalSession session, LocalPlayer player,

player.print(affected + " block(s) have been changed.");
}

@Command(
aliases = { "/line" },
usage = "<block> [thickness]",
desc = "Draws a line segment between cuboid selection corners",
help =
"Draws a line segment between cuboid selection corners.\n" +
"Can only be used with cuboid selections.\n" +
"Flags:\n" +
" -h generates only a shell",
flags = "h",
min = 1,
max = 2
)
@CommandPermissions("worldedit.region.line")
@Logging(REGION)
public void line(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {

Region region = session.getSelection(session.getSelectionWorld());
if (!(region instanceof CuboidRegion)) {
player.printError("Invalid region type");
return;
}
if (args.argsLength() < 2 ? false : args.getDouble(1) < 0) {
player.printError("Invalid thickness. Must not be negative");
return;
}

Pattern pattern = we.getBlockPattern(player, args.getString(0));
CuboidRegion cuboidregion = (CuboidRegion) region;
Vector pos1 = cuboidregion.getPos1();
Vector pos2 = cuboidregion.getPos2();
int blocksChanged = editSession.drawLine(pattern, pos1, pos2, args.argsLength() < 2 ? 0 : args.getDouble(1), !args.hasFlag('h'));

player.print(blocksChanged + " block(s) have been changed.");
}

@Command(
aliases = { "/curve" },
usage = "<block> [thickness]",
desc = "Draws a spline through selected points",
help =
"Draws a spline through selected points.\n" +
"Can only be uesd with convex polyhedral selections.\n" +
"Flags:\n" +
" -h generates only a shell",
flags = "h",
min = 1,
max = 2
)
@CommandPermissions("worldedit.region.curve")
@Logging(REGION)
public void curve(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {

Region region = session.getSelection(session.getSelectionWorld());
if (!(region instanceof ConvexPolyhedralRegion)) {
player.printError("Invalid region type");
return;
}
if (args.argsLength() < 2 ? false : args.getDouble(1) < 0) {
player.printError("Invalid thickness. Must not be negative");
return;
}

Pattern pattern = we.getBlockPattern(player, args.getString(0));
ConvexPolyhedralRegion cpregion = (ConvexPolyhedralRegion) region;
List<Vector> vectors = new ArrayList<Vector>(cpregion.getVertices());

int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, args.argsLength() < 2 ? 0 : args.getDouble(1), !args.hasFlag('h'));

player.print(blocksChanged + " block(s) have been changed.");
}

@Command(
aliases = { "/replace", "/re", "/rep" },
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/com/sk89q/worldedit/interpolation/Interpolation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// $Id$
/*
* WorldEditLibrary
* Copyright (C) 2010 sk89q <http://www.sk89q.com> and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.interpolation;

import java.util.List;

import com.sk89q.worldedit.Vector;

/**
* Represents an arbitrary function in &#8477; &rarr; &#8477;<sup>3</sup>
*
* @author TomyLobo
*
*/
public interface Interpolation {
/**
* Sets nodes to be used by subsequent calls to
* {@link #getPosition(double)} and the other methods.
*
* @param nodes
*/
public void setNodes(List<Node> nodes);

/**
* Gets the result of f(position)
*
* @param position
* @return
*/
public Vector getPosition(double position);

/**
* Gets the result of f'(position).
*
* @param position
* @return
*/
public Vector get1stDerivative(double position);

/**
* Gets the result of &int;<sub>a</sub><sup style="position: relative; left: -1ex">b</sup>|f'(t)| dt.<br />
* That means it calculates the arc length (in meters) between positionA
* and positionB.
*
* @param positionA lower limit
* @param positionB upper limit
* @return
*/
double arcLength(double positionA, double positionB);

int getSegment(double position);
}
Loading

0 comments on commit 60f6580

Please sign in to comment.