S-VIEW-10: velocity-curve editor UI — draggable spline box beside the Sample hero + in the Zone panel, all math through the pure velocity_curve module
This commit is contained in:
@@ -274,6 +274,70 @@ static void testFromPointsSubTwoFallsBackToFlat() {
|
||||
CHECK(c1.equals(VelocityCurve::flat()));
|
||||
}
|
||||
|
||||
// --- S-VIEW-10 pixel maps (the editor draw/add seam) -----------------------------
|
||||
|
||||
static void testPixelFromPointMapsCornersAndMidpoint() {
|
||||
// Box 100 px wide, 51 px tall at (10, 20). velToX spans the WIDTH (frac * w); ampToY spans
|
||||
// (h - 1) rows with amp 1 at the top — assert the drawn corners land where the module's own
|
||||
// hit-test mapping puts them.
|
||||
const Box box{10, 20, 100, 51};
|
||||
const auto tl = VelocityCurve::pixelFromPoint(box, {0.0, 1.0});
|
||||
CHECK(tl.x == 10 && tl.y == 20);
|
||||
const auto br = VelocityCurve::pixelFromPoint(box, {127.0, 0.0});
|
||||
CHECK(br.x == 110 && br.y == 70);
|
||||
const auto mid = VelocityCurve::pixelFromPoint(box, {63.5, 0.5});
|
||||
CHECK(mid.x == 60 && mid.y == 45);
|
||||
// Out-of-box values are clamped by the mapping (velocity 200 draws at the right edge).
|
||||
const auto clamped = VelocityCurve::pixelFromPoint(box, {200.0, 2.0});
|
||||
CHECK(clamped.x == 110 && clamped.y == 20);
|
||||
}
|
||||
|
||||
static void testPointFromPixelInvertsAndClamps() {
|
||||
const Box box{10, 20, 100, 51};
|
||||
// Exact corners invert exactly.
|
||||
const VelocityPoint tl = VelocityCurve::pointFromPixel(box, 10, 20);
|
||||
CHECK(near(tl.velocity, 0.0) && near(tl.amp, 1.0));
|
||||
const VelocityPoint br = VelocityCurve::pointFromPixel(box, 110, 70);
|
||||
CHECK(near(br.velocity, 127.0) && near(br.amp, 0.0));
|
||||
// A pixel OUTSIDE the box clamps into the domain (never an invariant-violating point).
|
||||
const VelocityPoint out = VelocityCurve::pointFromPixel(box, -50, 500);
|
||||
CHECK(near(out.velocity, 0.0) && near(out.amp, 0.0));
|
||||
const VelocityPoint out2 = VelocityCurve::pointFromPixel(box, 500, -50);
|
||||
CHECK(near(out2.velocity, 127.0) && near(out2.amp, 1.0));
|
||||
}
|
||||
|
||||
static void testPixelMapsRoundTripWithinOnePixelQuantum() {
|
||||
// Forward-then-inverse must agree within one pixel's worth of value (the rounding quantum) —
|
||||
// this is what keeps an added point under the cursor and a drawn node grabbable where drawn.
|
||||
const Box box{3, 7, 160, 120};
|
||||
const double velQuantum = 127.0 / 160.0;
|
||||
const double ampQuantum = 1.0 / 119.0;
|
||||
const VelocityPoint pts[] = {{0.0, 1.0}, {127.0, 0.0}, {40.0, 0.25}, {90.5, 0.66}, {63.5, 0.5}};
|
||||
for (const VelocityPoint& p : pts) {
|
||||
const auto px = VelocityCurve::pixelFromPoint(box, p);
|
||||
const VelocityPoint back = VelocityCurve::pointFromPixel(box, px.x, px.y);
|
||||
CHECK(std::fabs(back.velocity - p.velocity) <= velQuantum);
|
||||
CHECK(std::fabs(back.amp - p.amp) <= ampQuantum);
|
||||
}
|
||||
}
|
||||
|
||||
static void testPixelFromPointAgreesWithPointAtPixel() {
|
||||
// A node drawn at pixelFromPoint's coordinates must hit-test back to that same node — the
|
||||
// draw/grab no-drift contract the two helpers exist to guarantee.
|
||||
VelocityCurve c = VelocityCurve::linear();
|
||||
const std::size_t idx = c.addPoint(70.0, 0.3);
|
||||
const Box box{0, 0, 200, 100};
|
||||
const auto px = VelocityCurve::pixelFromPoint(box, c.points()[idx]);
|
||||
CHECK(c.pointAtPixel(box, px.x, px.y) == static_cast<int>(idx));
|
||||
}
|
||||
|
||||
static void testPointFromPixelDegenerateBox() {
|
||||
// Zero width -> velocity 0; height <= 1 -> amp 1 (mirrors the forward map's degenerate pins).
|
||||
const Box flat{5, 5, 0, 0};
|
||||
const VelocityPoint p = VelocityCurve::pointFromPixel(flat, 50, 50);
|
||||
CHECK(near(p.velocity, 0.0) && near(p.amp, 1.0));
|
||||
}
|
||||
|
||||
static void testFromPointsRoundTripsAValidCurve() {
|
||||
VelocityCurve orig = VelocityCurve::linear();
|
||||
orig.addPoint(40.0, 0.2);
|
||||
@@ -302,6 +366,11 @@ int main() {
|
||||
testResolveDragDegenerateBoxNoMotion();
|
||||
testFromPointsSortsClampsAndForcesEndpoints();
|
||||
testFromPointsSubTwoFallsBackToFlat();
|
||||
testPixelFromPointMapsCornersAndMidpoint();
|
||||
testPointFromPixelInvertsAndClamps();
|
||||
testPixelMapsRoundTripWithinOnePixelQuantum();
|
||||
testPixelFromPointAgreesWithPointAtPixel();
|
||||
testPointFromPixelDegenerateBox();
|
||||
testFromPointsRoundTripsAValidCurve();
|
||||
|
||||
if (g_fail == 0) std::printf("velocity_curve: all tests passed\n");
|
||||
|
||||
Reference in New Issue
Block a user