jc/math/vec.jai
2025-05-20 20:12:33 -07:00

363 lines
8.7 KiB
Text

/*
Vec is a generic set of N named values of type T (aka. a mathematical vector)
The values can be accessed via array index or their common component names:
- u, v
- x, y, z, w
- r, g, b, a
- c0, c1, c2, c3, ... cN
For most use cases, opt for the named variants of this type:
Vec2 : Vec(2, float)
Vec3 : Vec(3, float)
Vec4 : Vec(4, float)
Quat : Vec(4, float)
Sadly, Vec does *NOT* solve the problem of interfacing with
external vector types (including Jai's 'Math' module).
To fix this, create two helper macros:
to_jai :: (v: Vec4) -> jmath.Vector4 #expand {
return v.(jmath.Vector4,force);
}
to_jai :: (v: *Vec4) -> *jmath.Vector4 #expand {
return v.(*jmath.Vector4);
}
*/
Vec :: struct(N: int, T: Type)
#modify {
info := T.(*Type_Info);
return info.type == .INTEGER || info.type == .FLOAT, "Vec T must be a numeric type (int or float)";
} {
#assert (N > 0) "Vec N cannot be <= 0";
// Vecs are backed by an array internally. The #insert block below
// generates #place'd unions of named fields or cN fields when N is > 4.
#as components: [N]T;
#insert -> string {
b: basic.String_Builder;
basic.append(*b, "#place components;\n");
fields :: []string.[
string.[ "x", "r", "u" ],
string.[ "y", "g", "v" ],
string.[ "z", "b", "" ],
string.[ "w", "a", "" ],
];
for i: 0..N - 1 {
if i < fields.count {
basic.append(*b, "union {\n");
for field: fields[i] if field.count != 0 {
basic.print_to_builder(*b, "\t%: T = ---;\n", field);
}
basic.print_to_builder(*b, "\tc%: T = ---;\n", i);
basic.append(*b, "};\n");
}
else {
basic.print_to_builder(*b, "c%: T = ---;\n", i);
}
}
return basic.builder_to_string(*b);
};
}
operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc {
bounds_check_index(idx, v.N);
return v.components[idx];
}
operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
bounds_check_index(idx, v.N);
return *v.components[idx];
}
operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
bounds_check_index(idx, v.N);
v.components[idx] = value;
}
for_expansion :: (v: *Vec, body: Code, flags: For_Flags) #expand {
for i: 0..v.N - 1 {
`it_index := i;
#if flags & .POINTER {
`it := *v.components[i];
} else {
`it := v.components[i];
}
#insert,scope(body) body;
}
}
operator + :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4
for l res[it_index] = it + r[it_index];
return res;
}
operator - :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4
for l res[it_index] = it - r[it_index];
return res;
}
operator * :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4
for l res[it_index] = it * r[it_index];
return res;
}
operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4
for l res[it_index] = it / r[it_index];
return res;
}
operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return is_type_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---;
for l res[it_index] = it + r;
return res;
}
operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return is_type_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---;
for l res[it_index] = it - r;
return res;
}
operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc
#modify { return is_type_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---;
for l res[it_index] = r - it;
return res;
}
operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return is_type_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---;
for l res[it_index] = it*r;
return res;
}
operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return is_type_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---;
for l res[it_index] = it/r;
return res;
}
operator == :: inline (l: Vec, r: Vec(l.N, l.T)) -> bool #no_abc {
for l if it != r[it_index] return false;
return true;
}
min :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
n := l.N - 1;
while n >= 0 {
if l[n] < r[n]
res[n] = l[n];
else
res[n] = r[n];
n -= 1;
}
return res;
}
max :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
n := l.N - 1;
while n >= 0 {
if l[n] > r[n]
res[n] = l[n];
else
res[n] = r[n];
n -= 1;
}
return res;
}
ceil :: (l: Vec, x: l.T) -> Vec(l.N, l.T) #no_abc {
r: Vec(l.N, l.T) = ---;
n := l.N - 1;
while n >= 0 {
if x < l[n]
r[n] = x;
else
r[n] = l[n];
n -= 1;
}
return r;
}
floor :: (l: Vec, x: l.T) -> Vec(l.N, l.T) #no_abc {
r: Vec(l.N, l.T) = ---;
n := l.N - 1;
while n >= 0 {
if x > l[n]
r[n] = x;
else
r[n] = l[n];
n -= 1;
}
return r;
}
clamp :: (v: Vec, low: v.T, high: v.T) -> Vec(v.N, v.T) #no_abc {
r: Vec(v.N, v.T) = ---;
n := v.N - 1;
while n >= 0 {
if v[n] < low
r[n] = low;
else if v[n] > high
r[n] = high;
else
r[n] = v[n];
n -= 1;
}
return r;
}
dot :: (a: Vec, b: Vec(a.N, a.T)) -> a.T #no_abc {
sum: a.T;
n := a.N - 1;
while n >= 0 {
sum += a[n]*b[n];
n -= 1;
}
return sum;
}
length_squared :: (v: Vec) -> float #no_abc {
return dot(v, v);
}
length :: (v: Vec) -> float #no_abc {
return math.sqrt(dot(v, v));
}
abs :: (v: Vec) -> Vec(v.N, v.T) #no_abc {
r: Vec(v.N, v.T) = ---;
n := v.N - 1;
while n >= 0 {
if v[n] < 0
r[n] = -v[n];
else
r[n] = v[n];
n -= 1;
}
return r;
}
norm :: normalize;
normalize :: (v: Vec) -> Vec(v.N, v.T) #no_abc {
return v/length(v);
}
lerp :: (a: Vec, b: Vec(a.N, a.T), t: float) -> Vec(a.N, a.T) #no_abc {
r: Vec(a.N, a.T) = ---;
n := a.N - 1;
while n >= 0 {
r[n] = a[n] + t*(b[n] - a[n]);
n -= 1;
}
return r;
}
// Note(Jesse): I don't think this is needed for bigger vectors
reflect :: (v: Vec3, p: Vec3) -> Vec3 #no_abc {
projection := p*dot(v, p)/length_squared(p);
return 2*projection - v;
}
reflect :: (v: Vec2, p: Vec2) -> Vec2 #no_abc {
projection := p*dot(v, p)/length_squared(p);
return 2*projection - v;
}
round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc
#modify { return is_type_float(T), "Used non-float vector on round"; } {
r: Vec(N, T) = ---;
n := N - 1;
while n >= 0 {
if v[n] < 0
r[n] = (v[n] - 0.5).(int).(float);
else
r[n] = (v[n] + 0.5).(int).(float);
n -= 1;
}
return r;
}
// Concrete vector types (the usual cases)
Vec2 :: Vec(2, float);
Vec3 :: Vec(3, float);
Vec4 :: Vec(4, float);
Quat :: Vec4;
v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return is_type_float(T), "use v2i for integer arguments"; }
#expand {
return .{ x = x, y = y };
}
v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T)
#modify { return is_type_integer(T), "use v2f for float arguments"; }
#expand {
return .{ x = x, y = y };
}
v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3
#modify { return is_type_float(T), "use v3i for integer arguments"; }
#expand {
return .{ x = x, y = y, z = z };
}
v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T)
#modify { return is_type_integer(T), "use v3f for float arguments"; }
#expand {
return .{ x = x, y = y, z = z };
}
v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
#modify { return is_type_float(T), "use v4i for integer arguments"; }
#expand {
return .{ x = x, y = y, z = z, w = w };
}
v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return is_type_integer(T), "use v4f for float arguments"; }
#expand {
return .{ x = x, y = y, z = z, w = w };
}
quat :: (x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> Quat #expand {
return .{ x = x, y = y, z = z, w = w };
}
#scope_file
math :: #import "Math";
is_type_integer :: (t: Type) -> bool {
return t.(*Type_Info).type == .INTEGER;
}
is_type_float :: (t: Type) -> bool {
return t.(*Type_Info).type == .FLOAT;
}
is_type_scalar :: (t: Type) -> bool {
return is_type_integer(t) || is_type_float(t);
}