feat(04-01): add Vec3.lerp and ParticleTrail sampler with JUnit suite

- Vec3.lerp(other, t) for linear interpolation between two points
- ParticleTrail.sample(from, to, density) emits density-based interpolated
  points strictly between endpoints (endpoints excluded by construction)
- TRAIL_DENSITY = 4.0 particles per block
- 6 ParticleTrail tests + 3 Vec3.lerp tests, all green
- Zero Hytale imports — chain/ frontier preserved
This commit is contained in:
2026-04-27 13:00:47 +02:00
parent 6f8efa94c5
commit 8d868a28ca
4 changed files with 177 additions and 0 deletions
@@ -0,0 +1,82 @@
package com.mythlane.chainlightning.chain;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
final class ParticleTrailTest {
private static final double EPS = 1e-9;
@Test
void zeroDistance() {
Vec3 p = new Vec3(2, 3, 4);
List<Vec3> out = ParticleTrail.sample(p, p, 4.0);
assertTrue(out.isEmpty());
}
@Test
void subBlockDistance() {
// distance 0.5 along X with density 4 → ceil(2.0) = 2
Vec3 from = new Vec3(0, 0, 0);
Vec3 to = new Vec3(0.5, 0, 0);
List<Vec3> out = ParticleTrail.sample(from, to, 4.0);
assertEquals(2, out.size());
// Both points strictly between endpoints
for (Vec3 v : out) {
assertTrue(from.distanceSquared(v) > EPS, "sample equals from");
assertTrue(to.distanceSquared(v) > EPS, "sample equals to");
}
}
@Test
void integerDistanceCount() {
Vec3 from = new Vec3(0, 0, 0);
Vec3 to = new Vec3(5, 0, 0);
List<Vec3> out = ParticleTrail.sample(from, to, 4.0);
assertEquals(20, out.size());
}
@Test
void fractionalDistanceCount() {
Vec3 from = new Vec3(0, 0, 0);
Vec3 to = new Vec3(3.7, 0, 0);
List<Vec3> out = ParticleTrail.sample(from, to, 4.0);
// ceil(3.7 * 4) = ceil(14.8) = 15
assertEquals(15, out.size());
}
@Test
void endpointsExcluded() {
Vec3 from = new Vec3(1, 2, 3);
Vec3 to = new Vec3(7, 8, 9);
List<Vec3> out = ParticleTrail.sample(from, to, 4.0);
assertTrue(out.size() > 0);
for (Vec3 v : out) {
assertTrue(from.distanceSquared(v) > EPS, "sample equals from endpoint");
assertTrue(to.distanceSquared(v) > EPS, "sample equals to endpoint");
}
}
@Test
void uniformSpacing() {
Vec3 from = new Vec3(0, 0, 0);
Vec3 to = new Vec3(5, 0, 0);
List<Vec3> out = ParticleTrail.sample(from, to, 4.0);
assertEquals(20, out.size());
double total = from.distance(to);
double expectedSpacing = total / (out.size() + 1);
// from -> first
assertEquals(expectedSpacing, from.distance(out.get(0)), EPS);
// last -> to
assertEquals(expectedSpacing, out.get(out.size() - 1).distance(to), EPS);
// consecutive
for (int i = 1; i < out.size(); i++) {
assertEquals(expectedSpacing, out.get(i - 1).distance(out.get(i)), EPS);
}
}
}
@@ -41,4 +41,34 @@ final class Vec3Test {
assertEquals(12.0, a.distanceSquared(b), 1e-9);
assertTrue(a.distance(b) > 3.4 && a.distance(b) < 3.5);
}
@Test
void lerpAtZeroReturnsFrom() {
Vec3 from = Vec3.ZERO;
Vec3 to = new Vec3(10, 10, 10);
Vec3 result = from.lerp(to, 0.0);
assertEquals(from.x(), result.x(), 1e-9);
assertEquals(from.y(), result.y(), 1e-9);
assertEquals(from.z(), result.z(), 1e-9);
}
@Test
void lerpAtOneReturnsTo() {
Vec3 from = Vec3.ZERO;
Vec3 to = new Vec3(10, 10, 10);
Vec3 result = from.lerp(to, 1.0);
assertEquals(to.x(), result.x(), 1e-9);
assertEquals(to.y(), result.y(), 1e-9);
assertEquals(to.z(), result.z(), 1e-9);
}
@Test
void lerpAtHalfReturnsMidpoint() {
Vec3 from = new Vec3(0, 0, 0);
Vec3 to = new Vec3(10, 0, 0);
Vec3 result = from.lerp(to, 0.5);
assertEquals(5.0, result.x(), 1e-9);
assertEquals(0.0, result.y(), 1e-9);
assertEquals(0.0, result.z(), 1e-9);
}
}