const std = @import("std"); const rl = @import("raylib"); const remap_number = @import("./utils.zig").remap; const assert = std.debug.assert; pub fn Range(Number: type) type { return struct { const Self = @This(); lower: Number, upper: Number, pub fn init(lower: Number, upper: Number) Self { return Self{ .lower = lower, .upper = upper }; } pub fn initRect(rect: rl.Rectangle) [2]Self { return .{ initRectX(rect), initRectY(rect) }; } pub fn initRectX(rect: rl.Rectangle) Self { return init(rect.x, rect.x + rect.width); } pub fn initRectY(rect: rl.Rectangle) Self { return init(rect.y, rect.y + rect.height); } pub fn flip(self: Self) Self { return init(self.upper, self.lower); } pub fn size(self: Self) Number { return @abs(self.upper - self.lower); } pub fn hasExclusive(self: Self, number: Number) bool { var upper = self.upper; var lower = self.lower; if (self.lower > self.upper) { lower = self.upper; upper = self.lower; } assert(lower <= upper); return lower < number and number < upper; } pub fn hasInclusive(self: Self, number: Number) bool { var upper = self.upper; var lower = self.lower; if (self.lower > self.upper) { lower = self.upper; upper = self.lower; } assert(lower <= upper); return lower <= number and number <= upper; } pub fn remapTo(from: Self, to: Self, value: Number) Number { return remap_number(Number, from.lower, from.upper, to.lower, to.upper, value); } pub fn add(self: Self, amount: Number) Self { return init( self.lower + amount, self.upper + amount ); } pub fn sub(self: Self, amount: Number) Self { return init( self.lower - amount, self.upper - amount ); } pub fn mul(self: Self, factor: Number) Self { return init( self.lower * factor, self.upper * factor ); } pub fn zoom(self: Self, center: Number, factor: Number) Self { return init( (self.lower - center) * factor + center, (self.upper - center) * factor + center ); } pub fn intersectPositive(self: Self, other: Self) Self { // TODO: Figure out how would an intersection of "negative" ranges should look // For now just coerce the negative ranges to positive ones. const self_positive = self.toPositive(); const other_positive = other.toPositive(); return init( @max(self_positive.lower, other_positive.lower), @min(self_positive.upper, other_positive.upper) ); } pub fn toPositive(self: Self) Self { if (self.isPositive()) { return self; } else { return self.flip(); } } pub fn isPositive(self: Self) bool { return self.upper >= self.lower; } pub fn isNegative(self: Self) bool { return self.lower >= self.upper; } pub fn grow(self: Self, amount: Number) Self { if (self.isPositive()) { return init( self.lower - amount, self.upper + amount ); } else { return init( self.lower + amount, self.upper - amount ); } } }; } pub const RangeF32 = Range(f32); pub const RangeF64 = Range(f64); test "math operations" { try std.testing.expectEqual( RangeF32.init(0, 10).mul(5), RangeF32.init(0, 50) ); try std.testing.expectEqual( RangeF32.init(0, 10).add(5), RangeF32.init(5, 15) ); try std.testing.expectEqual( RangeF32.init(0, 10).sub(5), RangeF32.init(-5, 5) ); } test "size" { try std.testing.expectEqual( RangeF32.init(0, 10).size(), 10 ); try std.testing.expectEqual( RangeF32.init(-10, 0).size(), 10 ); } test "intersection" { try std.testing.expectEqual( RangeF32.init(0, 10).intersectPositive(RangeF32.init(5, 8)), RangeF32.init(5, 8) ); try std.testing.expectEqual( RangeF32.init(0, 10).intersectPositive(RangeF32.init(-5, 8)), RangeF32.init(0, 8) ); try std.testing.expectEqual( RangeF32.init(0, 10).intersectPositive(RangeF32.init(5, 15)), RangeF32.init(5, 10) ); try std.testing.expectEqual( RangeF32.init(0, 10).intersectPositive(RangeF32.init(20, 30)), RangeF32.init(20, 10) ); }