All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fastasyncworldedit.core.command.tool.sweep.Spline Maven / Gradle / Ivy

Go to download

Blazingly fast Minecraft world manipulation for artists, builders and everyone else.

There is a newer version: 2.9.2
Show newest version
package com.fastasyncworldedit.core.command.tool.sweep;

import com.google.common.base.Preconditions;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector2;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.math.interpolation.Interpolation;

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

/**
 * Embodies an abstract implementation for pasting structures along a spline.
* A curve is being interpolated by the provided {@link Interpolation} implementation * and the structure is pasted along this curve by the specific Spline implementation. */ public abstract class Spline { private Vector2 direction = Vector2.at(1, 0); private final int nodeCount; protected EditSession editSession; private final Interpolation interpolation; private List
sections; private final double splineLength; /** * Constructor without position-correction. Use this constructor for an interpolation implementation which does not need position-correction. *

* Be advised that currently subsequent changes to the interpolation parameters may not be supported. * * @param editSession The EditSession which will be used when pasting the structure * @param interpolation An implementation of the interpolation algorithm used to calculate the curve */ protected Spline(EditSession editSession, Interpolation interpolation) { this(editSession, interpolation, -1); } /** * Constructor with position-correction. Use this constructor for an interpolation implementation that needs position-correction. *

* Some interpolation implementations calculate the position on the curve (used by {@link #pastePosition(double)}) * based on an equidistant distribution of the nodes on the curve. For example: on a spline with 5 nodes position 0.0 would refer * to the first node, 0.25 to the second, 0.5 to the third, ... .
* By providing this method with the amount of nodes used by the interpolation implementation the distribution of the * nodes is converted to a proportional distribution based on the length between two adjacent nodes calculated by {@link Interpolation#arcLength(double, double)}.
* This means that the distance between two positions used to paste the clipboard (e.g., 0.75 - 0.5 = 0.25) on the curve * will always amount to that part of the length (e.g. 40 units) of the curve. In this example it would amount to * 0.25 * 40 = 10 units of curve length between these two positions. *

* Be advised that currently subsequent changes to the interpolation parameters may not be supported. * * @param editSession The EditSession which will be used when pasting the structure * @param interpolation An implementation of the interpolation algorithm used to calculate the curve * @param nodeCount The number of nodes provided to the interpolation object */ protected Spline(EditSession editSession, Interpolation interpolation, int nodeCount) { this.editSession = editSession; this.interpolation = interpolation; this.nodeCount = nodeCount; this.splineLength = interpolation.arcLength(0D, 1D); if (nodeCount > 2) { initSections(); } } /** * Set the forward direction of the structure.
* This direction is used to determine the rotation of the clipboard to align to the curve. The horizontal slope * of the curve for a specific point is calculated by {@link Interpolation#get1stDerivative(double)}. * Subsequently, this angle between this vector, and the gradient vector is calculated, and the clipboard content * is rotated by that angle to follow the curve slope. *

* The default direction is a (1;0) vector (pointing in the positive x-direction). * * @param direction A vector representing the horizontal forward direction of the clipboard content */ public void setDirection(Vector2 direction) { this.direction = direction.normalize(); } /** * Get the forward direction of the structure.
* This direction is used to determine the rotation of the clipboard to align to the curve. The horizontal slope * of the curve for a specific point is calculated by {@link Interpolation#get1stDerivative(double)}. * Subsequently, this angle between this vector, and the gradient vector is calculated, and the clipboard content * is rotated by that angle to follow the curve slope. *

* The default direction is a (1;0) vector (pointing in the positive x-direction). * * @return A vector representing the horizontal forward direction of the clipboard content */ public Vector2 getDirection() { return direction; } /** * Paste the structure at the provided position on the curve. The position will be position-corrected if the * nodeCount provided to the constructor is bigger than 2. * * @param position The position on the curve. Must be between 0.0 and 1.0 (both inclusive) * @return The amount of blocks that have been changed * @throws MaxChangedBlocksException Thrown by WorldEdit if the limit of block changes for the {@link EditSession} has been reached */ public int pastePosition(double position) throws MaxChangedBlocksException { Preconditions.checkArgument(position >= 0); Preconditions.checkArgument(position <= 1); if (nodeCount > 2) { return pastePositionDirect(translatePosition(position)); } else { return pastePositionDirect(position); } } /** * 2 dimensional "cross" product. cross2D(v1, v2) = |v1|*|v2|*sin(theta) or v1 X v2 taking Y to be 0 */ private double cross2D(Vector2 v1, Vector2 v2) { return v1.getX() * v2.getZ() - v2.getX() * v1.getZ(); } /** * Paste structure at the provided position on the curve. The position will not be position-corrected * but will be passed directly to the interpolation algorithm. * * @param position The position on the curve. Must be between 0.0 and 1.0 (both inclusive) * @return The amount of blocks that have been changed * @throws MaxChangedBlocksException Thrown by WorldEdit if the limit of block changes for the {@link EditSession} has been reached */ public int pastePositionDirect(double position) throws MaxChangedBlocksException { Preconditions.checkArgument(position >= 0); Preconditions.checkArgument(position <= 1); // Calculate position from spline Vector3 target = interpolation.getPosition(position); BlockVector3 blockTarget = target.toBlockPoint(); Vector3 offset = target.subtract(target.floor()); // Calculate rotation from spline Vector3 deriv = interpolation.get1stDerivative(position); Vector2 deriv2D = Vector2.at(deriv.getX(), deriv.getZ()).normalize(); double angle = Math.toDegrees( -Math.atan2(cross2D(direction, deriv2D), direction.dot(deriv2D)) ); angle = ((angle % 360) + 360) % 360; // Wrap to 360 degrees return pasteBlocks(blockTarget, offset, angle); } protected abstract int pasteBlocks(BlockVector3 target, Vector3 offset, double angle) throws MaxChangedBlocksException; private void initSections() { int sectionCount = nodeCount - 1; sections = new ArrayList<>(sectionCount); double sectionLength = 1D / sectionCount; double position = 0; for (int i = 0; i < sectionCount; i++) { double length; if (i == sectionCount - 1) { // maybe unnecessary precaution length = interpolation.arcLength(i * sectionLength, 1D) / splineLength; } else { length = interpolation.arcLength(i * sectionLength, (i + 1) * sectionLength) / splineLength; } sections.add(new Section(i * sectionLength, sectionLength, position, length)); position += length; } } private double translatePosition(double flexPosition) { Section previousSection = sections.get(0); // start with first section for (int i = 1; i < sections.size(); i++) { Section section = sections.get(i); if (flexPosition < section.flexStart) { // break if the desired position is to the left of the current section -> the previous section contained the position break; } previousSection = section; } double flexOffset = flexPosition - previousSection.flexStart; double uniOffset = flexOffset / previousSection.flexLength * previousSection.uniLength; double finalPosition = previousSection.uniStart + uniOffset; //Really rough fix, but fixes a bug with no visual artifacts so it's probably ok? //flexPosition very close to 1 causes outputs very slightly higher than 1 on rare occasions if (finalPosition > 1) { return 1; } return finalPosition; } private record Section(double uniStart, double uniLength, double flexStart, double flexLength) { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy