Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ static final class TooLongException extends Exception {
}
}

/** A step along a segment: where the curve is, which way it goes, and how far it is from the segment start. */
private record Sample(Vector3 point, double angle, double distance) {
/**
* A step along a segment: where the curve is, which way is across it on the xz plane (of length 1), and how far it
* is from the segment start.
*/
private record Sample(Vector3 point, Vector3 across, double distance) {
}

private final Selection selection;
Expand Down Expand Up @@ -121,15 +124,23 @@ private static List<Sample> samples(BezierSegment segment, double fineness) {
// At 0 a handle can be at the point itself, where the curve has no direction yet
double at = t == 0 ? 0.00001 : t;
Vector3 point = segment.point(at);
distance += Math.sqrt((point.x() - before.x()) * (point.x() - before.x())
+ (point.y() - before.y()) * (point.y() - before.y())
+ (point.z() - before.z()) * (point.z() - before.z()));
samples.add(new Sample(point, segment.angle(at), distance));
distance += before.distance(point);
samples.add(new Sample(point, across(segment.direction(at)), distance));
before = point;
}
return samples;
}

/**
* {@code direction} on the xz plane, turned a quarter turn the way that takes +z to +x, and of length 1. +x when it
* does not move on the xz plane, as for a curve towards +z.
*/
private static Vector3 across(Vector3 direction) {
double length = Math.hypot(direction.x(), direction.z());
if (length == 0) return Vector3.at(1, 0, 0);
return Vector3.at(direction.z() / length, 0, -direction.x() / length);
}

/**
* Lays row {@code y} of the cross section, from its middle outwards on both sides. The middle column is laid from
* both sides, so it counts twice.
Expand Down Expand Up @@ -167,8 +178,8 @@ private void layRow(List<Sample> samples, int y, Vector3 middle) throws MaxChang
private void layColumn(List<Sample> samples, int side, int y, Vector3 column) throws MaxChangedBlocksException {
BlockVector3 before = null;
for (Sample sample : samples) {
Vector3 across = Vector3.at(side * Math.cos(-sample.angle()), y, side * Math.sin(-sample.angle()));
BlockVector3 target = BlockVector3.round(sample.point().add(across));
Vector3 fromCurve = sample.across().multiply(side).add(0, y, 0);
BlockVector3 target = BlockVector3.round(sample.point().add(fromCurve));

if (!target.equals(before)) {
// How far into the repeating selection this block is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,13 @@ public Vector3 blockCenter(double t) {
}

/**
* The direction of the curve at {@code t} on the xz plane, as the angle in radians from +z towards +x, from
* {@code -Math.PI} to {@code Math.PI}. 0 when the curve does not move on the xz plane there.
* The direction that the curve goes at {@code t}: its derivative divided by 3, the steps between the points
* weighted by the quadratic Bernstein polynomials. Zero at an end whose handle is at its point.
*
* @param t where on the segment, from 0 to 1
* @return the angle in radians
* @return the direction, not scaled to any length
*/
public double angle(double t) {
// The direction of the curve, its derivative divided by 3: the steps between the points, weighted by the
// quadratic Bernstein polynomials
public Vector3 direction(double t) {
double u = 1 - t;
double quadratic0 = u * u;
double quadratic1 = 2 * u * t;
Expand All @@ -108,13 +106,9 @@ public double angle(double t) {
Vector3 step1 = p2.subtract(p1);
Vector3 step2 = p3.subtract(p2);

Vector3 direction = step0.multiply(quadratic0)
return step0.multiply(quadratic0)
.add(step1.multiply(quadratic1))
.add(step2.multiply(quadratic2));

// atan2 gives Math.PI or -Math.PI for a zero vector when z is -0.0
if (direction.x() == 0 && direction.z() == 0) return 0;
return Math.atan2(direction.x(), direction.z());
}

/**
Expand All @@ -134,9 +128,7 @@ public double length(double fineness) {
Vector3 before = blockCenter(0);
for (int step = 1; step <= fineness; step++) {
Vector3 p = blockCenter(step / fineness);
length += Math.sqrt((p.x() - before.x()) * (p.x() - before.x())
+ (p.y() - before.y()) * (p.y() - before.y())
+ (p.z() - before.z()) * (p.z() - before.z()));
length += before.distance(p);
before = p;
}
return length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,16 @@ private void addCross(Vector3 block, Kind kind) {
// Each line of the cross goes from a corner of the block to its center, sqrt(3) / 2 blocks, and has at least
// its two ends
int density = Math.max(1, (int) (settings.pointDensity() * Math.sqrt(3) / 2));
Vector3 center = block.add(0.5, 0.5, 0.5);

for (int x = 0; x <= 1; x++) {
for (int y = 0; y <= 1; y++) {
for (int z = 0; z <= 1; z++) {
Vector3 corner = block.add(x, y, z);
Vector3 toCenter = center.subtract(corner);
for (int i = 0; i <= density; i++) {
double ax = 0.5 * x + (double) (density * x + (-x * 2 + 1) * i) / density * 0.5;
double ay = 0.5 * y + (double) (density * y + (-y * 2 + 1) * i) / density * 0.5;
double az = 0.5 * z + (double) (density * z + (-z * 2 + 1) * i) / density * 0.5;
particles.add(new Particle(block.add(ax, ay, az), kind));
Vector3 position = corner.add(toCenter.multiply((double) i / density));
particles.add(new Particle(position, kind));
}
}
}
Expand All @@ -153,12 +154,11 @@ private void addHandleLine(@Nullable Vector3 point, @Nullable Vector3 handle) {
int distance = (int) point.distance(handle);
if (distance > settings.maxLength()) return;

Vector3 start = point.add(0.5, 0.5, 0.5);
Vector3 toHandle = handle.subtract(point);
List<Vector3> candidates = new ArrayList<>();
for (double t = 0; t <= 1; t += 1.0 / (distance * settings.lineDensity())) {
candidates.add(Vector3.at(
(1 - t) * point.x() + t * handle.x() + 0.5,
(1 - t) * point.y() + t * handle.y() + 0.5,
(1 - t) * point.z() + t * handle.z() + 0.5));
candidates.add(start.add(toHandle.multiply(t)));
}
addDots(candidates, point, Kind.HANDLE_LINE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ void crossSectionIsLaidAcrossTheCurve() throws Exception {
assertEquals(5 * 9, blocks.size());
}

@Test
void crossSectionTurnsWithTheCurve() throws Exception {
CurveLayout.Selection selection = new CurveLayout.Selection(1, 3, 3, Vector3.at(0, 1, 1));
BezierSegment line = straight(Vector3.at(0, 64, 0), Vector3.at(0, 64, 4));
NavigableMap<BlockVector3, BlockVector3> blocks = layout(selection, NO_ARGUMENTS, 0, line).blocks();

assertEquals(Set.of(-1, 0, 1), xs(blocks));
assertEquals(5 * 9, blocks.size());
}

/** Which of the four blocks of {@link #FOUR_ALONG_X} each block of the curve gets, from the start of the curve. */
private static List<Integer> blocksUsed(int offset, BezierSegment... segments) throws Exception {
List<Integer> used = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;
Expand Down Expand Up @@ -70,22 +69,24 @@ void rejectsPositionOutsideCurve(double t) {
}

@Nested
class Angle {
// dx and dz of the direction; the angle goes from +z towards +x
@ParameterizedTest
@CsvSource({
"0, 1, 0",
"1, 0, 1.5707963267948966",
"0, -1, 3.141592653589793",
"-1, 0, -1.5707963267948966",
"1, 1, 0.7853981633974483",
"-1, -1, -2.356194490192345",
"0, 0, 0",
})
void followsTheCurveOnTheXzPlane(double dx, double dz, double expected) {
BezierSegment segment = straight(Vector3.at(0, 64, 0), Vector3.at(dx * 10, 70, dz * 10));
// The middle, as a straight segment without handles has no direction at its ends
assertEquals(expected, segment.angle(0.5), 1e-12);
class Direction {
@Test
void endsGoTowardsTheirHandles() {
assertVector(0, 10, 0, ARCH.direction(0));
assertVector(0, -10, 0, ARCH.direction(1));
}

@Test
void midpointUsesQuadraticBernsteinWeights() {
// weights at t = 0.5 are 1/4, 1/2, 1/4 on the steps (0, 10, 0), (10, 0, 0) and (0, -10, 0)
assertVector(5, 0, 0, ARCH.direction(0.5));
}

@Test
void endWithoutHandleHasNoDirection() {
BezierSegment segment = straight(Vector3.at(0, 64, 0), Vector3.at(10, 64, 0));
assertVector(0, 0, 0, segment.direction(0));
assertVector(5, 0, 0, segment.direction(0.5));
}
}

Expand Down
Loading