Optimize spline
Translate spline brush
Add various new patterns (#nox #noy #noz #rel `[stone,wood,blah`
#existing
Can now use percentages with patterns, not just blocks e.g.
50%#clipboard,50%stone
Add resettable patterns
This commit is contained in:
Jesse Boyd 2016-09-28 17:03:08 +10:00
parent 5b96a52e99
commit 1e79ae4a0f
18 changed files with 649 additions and 20 deletions

View File

@ -41,6 +41,7 @@ import com.sk89q.worldedit.command.tool.RecursivePickaxe;
import com.sk89q.worldedit.command.tool.brush.GravityBrush;
import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.extension.factory.DefaultMaskParser;
import com.sk89q.worldedit.extension.factory.HashTagPatternParser;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
@ -71,6 +72,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.interpolation.KochanekBartelsInterpolation;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.SessionManager;
@ -386,6 +388,7 @@ public class Fawe {
Patterns.inject(); // Optimizations (reduce object creation)
RandomPattern.inject(); // Optimizations
ClipboardPattern.inject(); // Optimizations
HashTagPatternParser.inject(); // Add new patterns
// Mask
BlockMask.inject(); // Optimizations
SolidBlockMask.inject(); // Optimizations
@ -403,6 +406,8 @@ public class Fawe {
// NBT
NBTInputStream.inject(); // Add actual streaming + Optimizations + New methods
NBTOutputStream.inject(); // New methods
// Math
KochanekBartelsInterpolation.inject(); // Optimizations
try {
CommandManager.inject(); // Async commands
PlatformManager.inject(); // Async brushes / tools

View File

@ -93,7 +93,10 @@ public enum BBC {
BRUSH_SMOOTH("Smooth brush equipped (%s0 x %s1 using %s2).", "WorldEdit.Brush"),
BRUSH_SPHERE("Sphere brush shape equipped (%s0).", "WorldEdit.Brush"),
BRUSH_LINE("Line brush shape equipped (%s0).", "WorldEdit.Brush"),
BRUSH_SPLINE("Line brush shape equipped (%s0). Right click to select points, left click to execute.", "WorldEdit.Brush"),
BRUSH_SPLINE("Line brush shape equipped (%s0). Right click an end to add a shape", "WorldEdit.Brush"),
BRUSH_SPLINE_PRIMARY("Added position, left click to spline them together!", "WorldEdit.Brush"),
BRUSH_SPLINE_SECONDARY_ERROR("Not enough positions set!", "WorldEdit.Brush"),
BRUSH_SPLINE_SECONDARY("Created spline", "WorldEdit.Brush"),
BRUSH_BLEND_BALL("Blend ball brush equipped (%s0).", "WorldEdit.Brush"),
BRUSH_ERODE("Erode brush equipped (%s0).", "WorldEdit.Brush"),
BRUSH_PASTE_NONE("Nothing to paste", "WorldEdit.Brush"),

View File

@ -68,12 +68,12 @@ public class SplineBrush implements DoubleActionBrush {
numSplines = points.size();
}
this.positionSets.add(points);
player.print("Added position, right click to spline them together!");
BBC.BRUSH_SPLINE_PRIMARY.send(player);
break;
}
case SECONDARY: {
if (positionSets.size() < 2) {
player.print("Not enough positions set!");
BBC.BRUSH_SPLINE_SECONDARY_ERROR.send(player);
return;
}
List<Vector> centroids = new ArrayList<>();
@ -112,7 +112,7 @@ public class SplineBrush implements DoubleActionBrush {
}
editSession.drawSpline(Patterns.wrap(pattern), currentSpline, 0, 0, 0, 10, 0, true);
}
player.print("Created spline");
BBC.BRUSH_SPLINE_SECONDARY.send(player);
positionSets.clear();
numSplines = 0;
break;

View File

@ -0,0 +1,19 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
public class ExistingPattern extends AbstractPattern {
private final Extent extent;
public ExistingPattern(Extent extent) {
this.extent = extent;
}
@Override
public BaseBlock apply(Vector position) {
return extent.getBlock(position);
}
}

View File

@ -0,0 +1,23 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
public class LinearBlockPattern extends AbstractPattern {
private final BaseBlock[] blocks;
private int index;
public LinearBlockPattern(BaseBlock[] blocks) {
this.blocks = blocks;
}
@Override
public BaseBlock apply(Vector position) {
if (index == blocks.length) {
index = 0;
}
return blocks[index++];
}
}

View File

@ -0,0 +1,24 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class NoXPattern extends AbstractPattern {
private final Pattern pattern;
public NoXPattern(Pattern pattern) {
this.pattern = pattern;
}
private Vector mutable = new Vector();
@Override
public BaseBlock apply(Vector pos) {
mutable.y = pos.y;
mutable.z = pos.z;
return pattern.apply(mutable);
}
}

View File

@ -0,0 +1,24 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class NoYPattern extends AbstractPattern {
private final Pattern pattern;
public NoYPattern(Pattern pattern) {
this.pattern = pattern;
}
private Vector mutable = new Vector();
@Override
public BaseBlock apply(Vector pos) {
mutable.x = pos.x;
mutable.z = pos.z;
return pattern.apply(mutable);
}
}

View File

@ -0,0 +1,24 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class NoZPattern extends AbstractPattern {
private final Pattern pattern;
public NoZPattern(Pattern pattern) {
this.pattern = pattern;
}
private Vector mutable = new Vector();
@Override
public BaseBlock apply(Vector pos) {
mutable.x = pos.x;
mutable.y = pos.y;
return pattern.apply(mutable);
}
}

View File

@ -0,0 +1,58 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.pattern.Pattern;
import java.lang.reflect.Field;
import java.util.Collection;
public class PatternTraverser {
private final Object pattern;
public PatternTraverser(Object start) {
this.pattern = start;
}
public void reset(Extent newExtent) {
reset(pattern, newExtent);
}
private void reset(Object pattern, Extent newExtent) {
if (pattern == null) {
return;
}
if (pattern instanceof ResettablePattern) {
((ResettablePattern) pattern).reset();
}
Class<?> current = pattern.getClass();
while(current.getSuperclass() != null) {
if (newExtent != null) {
try {
Field field = current.getDeclaredField("extent");
field.setAccessible(true);
field.set(pattern, newExtent);
} catch (NoSuchFieldException | IllegalAccessException ignore) {}
}
try {
Field field = current.getDeclaredField("pattern");
field.setAccessible(true);
Pattern next = (Pattern) field.get(pattern);
reset(next, newExtent);
} catch (NoSuchFieldException | IllegalAccessException ignore) {}
try {
Field field = current.getDeclaredField("material");
field.setAccessible(true);
Pattern next = (Pattern) field.get(pattern);
reset(next, newExtent);
} catch (NoSuchFieldException | IllegalAccessException ignore) {}
try {
Field field = current.getDeclaredField("patterns");
field.setAccessible(true);
Collection<Pattern> patterns = (Collection<Pattern>) field.get(pattern);
for (Pattern next : patterns) {
reset(next, newExtent);
}
} catch (NoSuchFieldException | IllegalAccessException ignore) {}
current = current.getSuperclass();
}
}
}

View File

@ -0,0 +1,34 @@
package com.boydti.fawe.object.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
public class RelativePattern extends AbstractPattern implements ResettablePattern {
private final Pattern pattern;
public RelativePattern(Pattern pattern) {
this.pattern = pattern;
}
private Vector origin;
private Vector mutable = new Vector();
@Override
public BaseBlock apply(Vector pos) {
if (origin == null) {
origin = new Vector(pos);
}
mutable.x = pos.x - origin.x;
mutable.y = pos.y - origin.y;
mutable.z = pos.z - origin.z;
return pattern.apply(mutable);
}
@Override
public void reset() {
origin = null;
}
}

View File

@ -0,0 +1,5 @@
package com.boydti.fawe.object.pattern;
public interface ResettablePattern {
void reset();
}

View File

@ -18,6 +18,9 @@ public class MaskTraverser {
}
private void reset(Mask mask, Extent newExtent) {
if (mask == null) {
return;
}
if (mask instanceof ResettableMask) {
((ResettableMask) mask).reset();
}

View File

@ -2700,14 +2700,20 @@ public class EditSession extends AbstractWorld implements HasFaweQueue {
final int tipx = (int) Math.round(tipv.getX());
final int tipy = (int) Math.round(tipv.getY());
final int tipz = (int) Math.round(tipv.getZ());
vset.add(new Vector(tipx, tipy, tipz));
if (radius == 0) {
setBlock(tipx, tipy, tipz, pattern.next(tipx, tipy, tipz));
} else {
vset.add(new Vector(tipx, tipy, tipz));
}
}
vset = this.getBallooned(vset, radius);
if (!filled) {
vset = this.getHollowed(vset);
if (radius != 0) {
vset = this.getBallooned(vset, radius);
if (!filled) {
vset = this.getHollowed(vset);
}
return this.setBlocks(vset, pattern);
}
return this.setBlocks(vset, pattern);
return changes;
}
private double hypot(final double... pars) {

View File

@ -0,0 +1,123 @@
package com.sk89q.worldedit.extension.factory;
import com.boydti.fawe.object.pattern.ExistingPattern;
import com.boydti.fawe.object.pattern.LinearBlockPattern;
import com.boydti.fawe.object.pattern.NoXPattern;
import com.boydti.fawe.object.pattern.NoYPattern;
import com.boydti.fawe.object.pattern.NoZPattern;
import com.boydti.fawe.object.pattern.RelativePattern;
import com.sk89q.worldedit.EmptyClipboardException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.ClipboardPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.RandomPattern;
import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.session.ClipboardHolder;
import java.util.ArrayList;
public class HashTagPatternParser extends InputParser<Pattern> {
public HashTagPatternParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
switch (input.toLowerCase().charAt(0)) {
case '#': {
switch (input) {
case "#existing": {
return new ExistingPattern(context.requireExtent());
}
case "#clipboard":
case "#copy": {
LocalSession session = context.requireSession();
if (session != null) {
try {
ClipboardHolder holder = session.getClipboard();
Clipboard clipboard = holder.getClipboard();
return new ClipboardPattern(clipboard);
} catch (EmptyClipboardException e) {
throw new InputParseException("To use #clipboard, please first copy something to your clipboard");
}
} else {
throw new InputParseException("No session is available, so no clipboard is available");
}
}
}
String[] split2 = input.split(":");
if (split2.length > 1) {
switch (split2[0]) {
case "#relative":
case "#rel": {
String rest = input.substring(5);
return new RelativePattern(parseFromInput(rest, context));
}
case "#nox": {
String rest = input.substring(5);
return new NoXPattern(parseFromInput(rest, context));
}
case "#noy": {
String rest = input.substring(5);
return new NoYPattern(parseFromInput(rest, context));
}
case "#noz": {
String rest = input.substring(5);
return new NoZPattern(parseFromInput(rest, context));
}
}
}
throw new InputParseException("Invalid, see: https://github.com/boy0001/FastAsyncWorldedit/wiki/WorldEdit-and-FAWE-patterns");
}
case '[': {
ArrayList<BaseBlock> blocks = new ArrayList<>();
for (String token : input.substring(1).split(",")) {
BlockFactory blockRegistry = worldEdit.getBlockFactory();
BaseBlock block = blockRegistry.parseFromInput(token, context);
blocks.add(block);
}
if (blocks.isEmpty()) {
throw new InputParseException("No blocks provided for linear pattern e.g. [stone,wood");
}
return new LinearBlockPattern(blocks.toArray(new BaseBlock[blocks.size()]));
}
default:
String[] items = input.split(",");
if (items.length == 1) {
return new BlockPattern(worldEdit.getBlockFactory().parseFromInput(items[0], context));
}
BlockFactory blockRegistry = worldEdit.getBlockFactory();
RandomPattern randomPattern = new RandomPattern();
for (String token : input.split(",")) {
Pattern pattern;
double chance;
// Parse special percentage syntax
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
String[] p = token.split("%");
if (p.length < 2) {
throw new InputParseException("Missing the pattern after the % symbol for '" + input + "'");
} else {
chance = Double.parseDouble(p[0]);
pattern = parseFromInput(p[1], context);
}
} else {
chance = 1;
pattern = parseFromInput(token, context);
}
randomPattern.add(pattern, chance);
}
return randomPattern;
}
}
public static Class<?> inject() {
return HashTagPatternParser.class;
}
}

View File

@ -22,6 +22,7 @@ package com.sk89q.worldedit.extension.platform;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.object.pattern.PatternTraverser;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.wrappers.PlayerWrapper;
import com.sk89q.worldedit.LocalConfiguration;
@ -330,6 +331,11 @@ public class PlatformManager {
}
}
private <T extends Tool> T reset(T tool) {
new PatternTraverser(tool).reset(null);
return tool;
}
@SuppressWarnings("deprecation")
@Subscribe
public void handleBlockInteract(BlockInteractEvent event) {
@ -371,7 +377,7 @@ public class PlatformManager {
fp.runAction(new Runnable() {
@Override
public void run() {
superPickaxe.actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
reset(superPickaxe).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
}
}, true, true);
event.setCancelled(true);
@ -385,7 +391,7 @@ public class PlatformManager {
fp.runAction(new Runnable() {
@Override
public void run() {
((DoubleActionBlockTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
reset(((DoubleActionBlockTool) tool)).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
}
}, true, true);
event.setCancelled(true);
@ -417,7 +423,7 @@ public class PlatformManager {
fp.runAction(new Runnable() {
@Override
public void run() {
((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
reset((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
}
}, true, true);
event.setCancelled(true);
@ -443,6 +449,7 @@ public class PlatformManager {
// Create a proxy actor with a potentially different world for
// making changes to the world
final Player player = PlayerWrapper.wrap(createProxyActor(event.getPlayer()));
try {
switch (event.getInputType()) {
case PRIMARY: {
@ -475,7 +482,7 @@ public class PlatformManager {
fp.runAsyncIfFree(new Runnable() {
@Override
public void run() {
((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
reset((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
}
});
event.setCancelled(true);
@ -513,7 +520,7 @@ public class PlatformManager {
fp.runAsyncIfFree(new Runnable() {
@Override
public void run() {
((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
reset((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
}
});
event.setCancelled(true);

View File

@ -12,7 +12,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class ClipboardPattern extends AbstractPattern {
private final Clipboard clipboard;
private final Vector size;
private final int sx, sy, sz;
private final Vector min;
/**
@ -23,7 +23,10 @@ public class ClipboardPattern extends AbstractPattern {
public ClipboardPattern(Clipboard clipboard) {
checkNotNull(clipboard);
this.clipboard = clipboard;
this.size = clipboard.getMaximumPoint().subtract(clipboard.getMinimumPoint()).add(1, 1, 1);
Vector size = clipboard.getMaximumPoint().subtract(clipboard.getMinimumPoint()).add(1, 1, 1);
this.sx = size.getBlockX();
this.sy = size.getBlockY();
this.sz = size.getBlockZ();
this.min = clipboard.getMinimumPoint();
}
@ -31,9 +34,12 @@ public class ClipboardPattern extends AbstractPattern {
@Override
public BaseBlock apply(Vector position) {
int xp = Math.abs(position.getBlockX()) % size.getBlockX();
int yp = Math.abs(position.getBlockY()) % size.getBlockY();
int zp = Math.abs(position.getBlockZ()) % size.getBlockZ();
int xp = position.getBlockX() % sx;
int yp = position.getBlockY() % sy;
int zp = position.getBlockZ() % sz;
if (xp < 0) xp += sx;
if (yp < 0) yp += sy;
if (zp < 0) zp += sz;
mutable.x = min.x + xp;
mutable.y = min.y + yp;
mutable.z = min.z + zp;

View File

@ -5,6 +5,7 @@ import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@ -16,6 +17,7 @@ public class RandomPattern extends AbstractPattern {
private Map<Pattern, Double> weights = new HashMap<>();
private RandomCollection<Pattern> collection;
private Set<Pattern> patterns;
/**
* Add a pattern to the weight list of patterns.
@ -30,6 +32,7 @@ public class RandomPattern extends AbstractPattern {
checkNotNull(pattern);
weights.put(pattern, chance);
collection = RandomCollection.of(weights);
this.patterns = weights.keySet();
}
@Override

View File

@ -0,0 +1,262 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// $Id$
package com.sk89q.worldedit.math.interpolation;
import com.sk89q.worldedit.Vector;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A Kochanek-Bartels interpolation; continuous in the 2nd derivative.
*
* <p>Supports {@link Node#tension tension}, {@link Node#bias bias} and
* {@link Node#continuity continuity} parameters per {@link Node}.</p>
*/
public class KochanekBartelsInterpolation implements Interpolation {
private List<Node> nodes;
private Vector[] coeffA;
private Vector[] coeffB;
private Vector[] coeffC;
private Vector[] coeffD;
private double scaling;
public KochanekBartelsInterpolation() {
setNodes(Collections.<Node>emptyList());
}
@Override
public void setNodes(List<Node> nodes) {
checkNotNull(nodes);
this.nodes = nodes;
recalc();
}
private void recalc() {
final int nNodes = nodes.size();
coeffA = new Vector[nNodes];
coeffB = new Vector[nNodes];
coeffC = new Vector[nNodes];
coeffD = new Vector[nNodes];
if (nNodes == 0)
return;
Node nodeB = nodes.get(0);
double tensionB = nodeB.getTension();
double biasB = nodeB.getBias();
double continuityB = nodeB.getContinuity();
for (int i = 0; i < nNodes; ++i) {
final double tensionA = tensionB;
final double biasA = biasB;
final double continuityA = continuityB;
if (i + 1 < nNodes) {
nodeB = nodes.get(i + 1);
tensionB = nodeB.getTension();
biasB = nodeB.getBias();
continuityB = nodeB.getContinuity();
}
// Kochanek-Bartels tangent coefficients
final double ta = (1-tensionA)*(1+biasA)*(1+continuityA)/2; // Factor for lhs of d[i]
final double tb = (1-tensionA)*(1-biasA)*(1-continuityA)/2; // Factor for rhs of d[i]
final double tc = (1-tensionB)*(1+biasB)*(1-continuityB)/2; // Factor for lhs of d[i+1]
final double td = (1-tensionB)*(1-biasB)*(1+continuityB)/2; // Factor for rhs of d[i+1]
coeffA[i] = linearCombination(i, -ta, ta- tb-tc+2, tb+tc-td-2, td);
coeffB[i] = linearCombination(i, 2*ta, -2*ta+2*tb+tc-3, -2*tb-tc+td+3, -td);
coeffC[i] = linearCombination(i, -ta, ta- tb , tb , 0);
//coeffD[i] = linearCombination(i, 0, 1, 0, 0);
coeffD[i] = retrieve(i); // this is an optimization
}
scaling = nodes.size() - 1;
}
/**
* Returns the linear combination of the given coefficients with the nodes adjacent to baseIndex.
*
* @param baseIndex node index
* @param f1 coefficient for baseIndex-1
* @param f2 coefficient for baseIndex
* @param f3 coefficient for baseIndex+1
* @param f4 coefficient for baseIndex+2
* @return linear combination of nodes[n-1..n+2] with f1..4
*/
private Vector linearCombination(int baseIndex, double f1, double f2, double f3, double f4) {
final Vector r1 = retrieve(baseIndex - 1).multiply(f1);
final Vector r2 = retrieve(baseIndex ).multiply(f2);
final Vector r3 = retrieve(baseIndex + 1).multiply(f3);
final Vector r4 = retrieve(baseIndex + 2).multiply(f4);
return r1.add(r2).add(r3).add(r4);
}
/**
* Retrieves a node. Indexes are clamped to the valid range.
*
* @param index node index to retrieve
* @return nodes[clamp(0, nodes.length-1)]
*/
private Vector retrieve(int index) {
if (index < 0)
return fastRetrieve(0);
if (index >= nodes.size())
return fastRetrieve(nodes.size()-1);
return fastRetrieve(index);
}
private Vector fastRetrieve(int index) {
return nodes.get(index).getPosition();
}
private Vector mutable = new Vector();
@Override
public Vector getPosition(double position) {
if (coeffA == null)
throw new IllegalStateException("Must call setNodes first.");
if (position > 1)
return null;
position *= scaling;
final int index = (int) Math.floor(position);
final double remainder = position - index;
final Vector a = coeffA[index];
final Vector b = coeffB[index];
final Vector c = coeffC[index];
final Vector d = coeffD[index];
double r2 = remainder * remainder;
double r3 = r2 * remainder;
mutable.x = a.x * r3 + b.x * r2 + c.x * remainder + d.x;
mutable.y = a.y * r3 + b.y * r2 + c.y * remainder + d.y;
mutable.z = a.z * r3 + b.z * r2 + c.z * remainder + d.z;
return mutable;
}
@Override
public Vector get1stDerivative(double position) {
if (coeffA == null)
throw new IllegalStateException("Must call setNodes first.");
if (position > 1)
return null;
position *= scaling;
final int index = (int) Math.floor(position);
//final double remainder = position - index;
final Vector a = coeffA[index];
final Vector b = coeffB[index];
final Vector c = coeffC[index];
return a.multiply(1.5*position - 3.0*index).add(b).multiply(2.0*position).add(a.multiply(1.5*index).subtract(b).multiply(2.0*index)).add(c).multiply(scaling);
}
@Override
public double arcLength(double positionA, double positionB) {
if (coeffA == null)
throw new IllegalStateException("Must call setNodes first.");
if (positionA > positionB)
return arcLength(positionB, positionA);
positionA *= scaling;
positionB *= scaling;
final int indexA = (int) Math.floor(positionA);
final double remainderA = positionA - indexA;
final int indexB = (int) Math.floor(positionB);
final double remainderB = positionB - indexB;
return arcLengthRecursive(indexA, remainderA, indexB, remainderB);
}
/**
* Assumes a < b
*/
private double arcLengthRecursive(int indexLeft, double remainderLeft, int indexRight, double remainderRight) {
switch (indexRight - indexLeft) {
case 0:
return arcLengthRecursive(indexLeft, remainderLeft, remainderRight);
case 1:
// This case is merely a speed-up for a very common case
return
arcLengthRecursive(indexLeft, remainderLeft, 1.0) +
arcLengthRecursive(indexRight, 0.0, remainderRight);
default:
return
arcLengthRecursive(indexLeft, remainderLeft, indexRight - 1, 1.0) +
arcLengthRecursive(indexRight, 0.0, remainderRight);
}
}
private double arcLengthRecursive(int index, double remainderLeft, double remainderRight) {
final Vector a = coeffA[index].multiply(3.0);
final Vector b = coeffB[index].multiply(2.0);
final Vector c = coeffC[index];
final int nPoints = 8;
double accum = a.multiply(remainderLeft).add(b).multiply(remainderLeft).add(c).length() / 2.0;
for (int i = 1; i < nPoints-1; ++i) {
double t = ((double) i) / nPoints;
t = (remainderRight-remainderLeft)*t + remainderLeft;
accum += a.multiply(t).add(b).multiply(t).add(c).length();
}
accum += a.multiply(remainderRight).add(b).multiply(remainderRight).add(c).length() / 2.0;
return accum * (remainderRight - remainderLeft) / nPoints;
}
@Override
public int getSegment(double position) {
if (coeffA == null)
throw new IllegalStateException("Must call setNodes first.");
if (position > 1)
return Integer.MAX_VALUE;
position *= scaling;
return (int) Math.floor(position);
}
public static Class<?> inject() {
return KochanekBartelsInterpolation.class;
}
}