daq-view/src/range.zig

196 lines
5.1 KiB
Zig

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)
);
}