authorgravatar for pancelor@gmail.compancelor <pancelor@gmail.com> 2024-03-12 22:10:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-13 18:35:07-07:00
log7a858257f2c05f0ab8df0debda9f1d58b5ecf251
treefd97c46a20905ce449c5ef6f99931a9974ba995f
parentea8e9e668b8b566a94e8d69476d392458918382a

remove `math.lerp` bounds for t

I think of lerp() as a way to change coordinate systems, essentially remapping the input numberline onto a shifted+rescaled numberline. In my mind the full numberline is remapped, not just the 0-1 segment. An example of how this is useful: in a game, you can write: `myPos = lerp(pos0, pos1, easeOutBack(u))` for some `u` that changes from 0 to 1 over time. (see https://easings.net/#easeOutBack) This will animate `myPos` between `pos0` and `pos1`, overshooting the goal position `pos1` in a nicely-animated way. `easeOutBack(float)->float` is a pure function that overshoots 1, and by combining it with `lerp()` we can remap coordinates in other coordinate systems, making them overshoot in the same way. However, this overshooting is only possible because `easeOutBack(t)` sometimes exceeds the range 0-1 (e.g. `easeOutBack(0.5)` is 1.0877), which is not allowed by the current `math.lerp` implementation. This commit removes the asserts that prevented this use-case. Now, any value can be inputted for t. For example, `lerp(10,20, 2.0)` will now return 30, instead of throwing an assert error.

1 files changed, 16 insertions(+), 14 deletions(-)

lib/std/math.zig+16-14
...@@ -1363,23 +1363,13 @@ test "lossyCast" {...@@ -1363,23 +1363,13 @@ test "lossyCast" {
1363}1363}
13641364
1365/// Performs linear interpolation between *a* and *b* based on *t*.1365/// Performs linear interpolation between *a* and *b* based on *t*.
1366/// *t* must be in range 0.0 to 1.0. Supports floats and vectors of floats.1366/// *t* ranges from 0.0 to 1.0, but may exceed these bounds.
1367/// Supports floats and vectors of floats.
1367///1368///
1368/// This does not guarantee returning *b* if *t* is 1 due to floating-point errors.1369/// This does not guarantee returning *b* if *t* is 1 due to floating-point errors.
1369/// This is monotonic.1370/// This is monotonic.
1370pub fn lerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t) {1371pub fn lerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t) {
1371 const Type = @TypeOf(a, b, t);1372 const Type = @TypeOf(a, b, t);
1372
1373 switch (@typeInfo(Type)) {
1374 .Float, .ComptimeFloat => assert(t >= 0 and t <= 1),
1375 .Vector => {
1376 const lower_bound = @reduce(.And, t >= @as(Type, @splat(0)));
1377 const upper_bound = @reduce(.And, t <= @as(Type, @splat(1)));
1378 assert(lower_bound and upper_bound);
1379 },
1380 else => comptime unreachable,
1381 }
1382
1383 return @mulAdd(Type, b - a, t, a);1373 return @mulAdd(Type, b - a, t, a);
1384}1374}
13851375
...@@ -1392,6 +1382,9 @@ test "lerp" {...@@ -1392,6 +1382,9 @@ test "lerp" {
1392 try testing.expectEqual(@as(f32, 43.75), lerp(50, 25, 0.25));1382 try testing.expectEqual(@as(f32, 43.75), lerp(50, 25, 0.25));
1393 try testing.expectEqual(@as(f64, -31.25), lerp(-50, 25, 0.25));1383 try testing.expectEqual(@as(f64, -31.25), lerp(-50, 25, 0.25));
13941384
1385 try testing.expectEqual(@as(f64, 30), lerp(10, 20, 2.0));
1386 try testing.expectEqual(@as(f64, 5), lerp(10, 20, -0.5));
1387
1395 try testing.expectApproxEqRel(@as(f32, -7.16067345e+03), lerp(-10000.12345, -5000.12345, 0.56789), 1e-19);1388 try testing.expectApproxEqRel(@as(f32, -7.16067345e+03), lerp(-10000.12345, -5000.12345, 0.56789), 1e-19);
1396 try testing.expectApproxEqRel(@as(f64, 7.010987590521e+62), lerp(0.123456789e-64, 0.123456789e64, 0.56789), 1e-33);1389 try testing.expectApproxEqRel(@as(f64, 7.010987590521e+62), lerp(0.123456789e-64, 0.123456789e64, 0.56789), 1e-33);
13971390
...@@ -1405,8 +1398,8 @@ test "lerp" {...@@ -1405,8 +1398,8 @@ test "lerp" {
1405 const b: @Vector(3, f32) = @splat(50);1398 const b: @Vector(3, f32) = @splat(50);
1406 const t: @Vector(3, f32) = @splat(0.5);1399 const t: @Vector(3, f32) = @splat(0.5);
1407 try testing.expectEqual(1400 try testing.expectEqual(
1408 lerp(a, b, t),
1409 @Vector(3, f32){ 25, 25, 25 },1401 @Vector(3, f32){ 25, 25, 25 },
1402 lerp(a, b, t),
1410 );1403 );
1411 }1404 }
1412 {1405 {
...@@ -1414,8 +1407,17 @@ test "lerp" {...@@ -1414,8 +1407,17 @@ test "lerp" {
1414 const b: @Vector(3, f64) = @splat(100);1407 const b: @Vector(3, f64) = @splat(100);
1415 const t: @Vector(3, f64) = @splat(0.5);1408 const t: @Vector(3, f64) = @splat(0.5);
1416 try testing.expectEqual(1409 try testing.expectEqual(
1417 lerp(a, b, t),
1418 @Vector(3, f64){ 75, 75, 75 },1410 @Vector(3, f64){ 75, 75, 75 },
1411 lerp(a, b, t),
1412 );
1413 }
1414 {
1415 const a: @Vector(2, f32) = @splat(40);
1416 const b: @Vector(2, f32) = @splat(80);
1417 const t: @Vector(2, f32) = @Vector(2, f32){ 0.25, 0.75 };
1418 try testing.expectEqual(
1419 @Vector(2, f32){ 50, 70 },
1420 lerp(a, b, t),
1419 );1421 );
1420 }1422 }
1421}1423}