///|
/// A two-dimensional vector used by the 3D addon for texture coordinates and
/// screen-space input.
pub(all) struct Vec2 {
x : Double
y : Double
} derive(Eq, Debug, ToJson)
///|
/// A three-dimensional vector. Public scene coordinates use `Double` so
/// camera and picking calculations stay deterministic before GPU packing.
pub(all) struct Vec3 {
x : Double
y : Double
z : Double
} derive(Eq, Debug, ToJson)
///|
/// A four-dimensional vector used for colors and homogeneous coordinates.
pub(all) struct Vec4 {
x : Double
y : Double
z : Double
w : Double
} derive(Eq, Debug, ToJson)
///|
pub fn Vec2::new(x~ : Double, y~ : Double) -> Vec2 {
{ x, y }
}
///|
pub fn Vec3::new(x~ : Double, y~ : Double, z~ : Double) -> Vec3 {
{ x, y, z }
}
///|
pub fn Vec4::new(x~ : Double, y~ : Double, z~ : Double, w~ : Double) -> Vec4 {
{ x, y, z, w }
}
///|
pub fn Vec2::zero() -> Vec2 {
{ x: 0.0, y: 0.0 }
}
///|
pub fn Vec2::x(self : Vec2) -> Double {
self.x
}
///|
pub fn Vec2::y(self : Vec2) -> Double {
self.y
}
///|
pub fn Vec3::zero() -> Vec3 {
{ x: 0.0, y: 0.0, z: 0.0 }
}
///|
pub fn Vec3::x(self : Vec3) -> Double {
self.x
}
///|
pub fn Vec3::y(self : Vec3) -> Double {
self.y
}
///|
pub fn Vec3::z(self : Vec3) -> Double {
self.z
}
///|
pub fn Vec4::zero() -> Vec4 {
{ x: 0.0, y: 0.0, z: 0.0, w: 0.0 }
}
///|
pub fn Vec4::x(self : Vec4) -> Double {
self.x
}
///|
pub fn Vec4::y(self : Vec4) -> Double {
self.y
}
///|
pub fn Vec4::z(self : Vec4) -> Double {
self.z
}
///|
pub fn Vec4::w(self : Vec4) -> Double {
self.w
}
///|
pub fn Vec3::add(self : Vec3, other : Vec3) -> Vec3 {
{ x: self.x + other.x, y: self.y + other.y, z: self.z + other.z }
}
///|
pub fn Vec3::sub(self : Vec3, other : Vec3) -> Vec3 {
{ x: self.x - other.x, y: self.y - other.y, z: self.z - other.z }
}
///|
pub fn Vec3::scale(self : Vec3, amount : Double) -> Vec3 {
{ x: self.x * amount, y: self.y * amount, z: self.z * amount }
}
///|
pub fn Vec3::dot(self : Vec3, other : Vec3) -> Double {
self.x * other.x + self.y * other.y + self.z * other.z
}
///|
pub fn Vec3::cross(self : Vec3, other : Vec3) -> Vec3 {
{
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
}
}
///|
pub fn Vec3::length_squared(self : Vec3) -> Double {
self.dot(self)
}
///|
pub fn Vec3::length(self : Vec3) -> Double {
self.length_squared().sqrt()
}
///|
pub fn Vec3::normalized(self : Vec3) -> Vec3 {
let length = self.length()
if length <= 1.0e-12 {
Vec3::zero()
} else {
self.scale(1.0 / length)
}
}
///|
pub fn Vec3::lerp(self : Vec3, other : Vec3, progress : Double) -> Vec3 {
self.add(other.sub(self).scale(progress))
}
///|
pub fn Vec4::scale(self : Vec4, amount : Double) -> Vec4 {
{
x: self.x * amount,
y: self.y * amount,
z: self.z * amount,
w: self.w * amount,
}
}
///|
/// Row-major 4x4 matrix. Translation occupies indices 3, 7 and 11.
pub(all) struct Mat4 {
values : Array[Double]
} derive(Debug)
///|
pub fn Mat4::identity() -> Mat4 {
let values = Array::make(16, 0.0)
values[0] = 1.0
values[5] = 1.0
values[10] = 1.0
values[15] = 1.0
{ values, }
}
///|
pub fn Mat4::from_values(values : Array[Double]) -> Mat4 {
if values.length() == 16 {
{ values: values.copy() }
} else {
Mat4::identity()
}
}
///|
pub fn Mat4::values(self : Mat4) -> Array[Double] {
self.values.copy()
}
///|
pub fn Mat4::at(self : Mat4, row : Int, column : Int) -> Double {
if row < 0 || row >= 4 || column < 0 || column >= 4 {
0.0
} else {
self.values[row * 4 + column]
}
}
///|
pub fn Mat4::translation(offset : Vec3) -> Mat4 {
let matrix = Mat4::identity()
matrix.values[3] = offset.x
matrix.values[7] = offset.y
matrix.values[11] = offset.z
matrix
}
///|
pub fn Mat4::scale_uniform(amount : Double) -> Mat4 {
let matrix = Mat4::identity()
matrix.values[0] = amount
matrix.values[5] = amount
matrix.values[10] = amount
matrix
}
///|
pub fn Mat4::mul(self : Mat4, other : Mat4) -> Mat4 {
let values = Array::make(16, 0.0)
for row in 0..<4 {
for column in 0..<4 {
let mut value = 0.0
for index in 0..<4 {
value = value +
self.values[row * 4 + index] * other.values[index * 4 + column]
}
values[row * 4 + column] = value
}
}
{ values, }
}
///|
pub fn Mat4::transform_point(self : Mat4, point : Vec3) -> Vec3 {
let transformed = self.transform_homogeneous(point)
let x = transformed.x
let y = transformed.y
let z = transformed.z
let w = transformed.w
if w.abs() <= 1.0e-12 {
Vec3::new(x~, y~, z~)
} else {
Vec3::new(x=x / w, y=y / w, z=z / w)
}
}
///|
/// Transform an arbitrary homogeneous vector without a perspective divide.
pub fn Mat4::transform_vec4(self : Mat4, vector : Vec4) -> Vec4 {
{
x: self.values[0] * vector.x +
self.values[1] * vector.y +
self.values[2] * vector.z +
self.values[3] * vector.w,
y: self.values[4] * vector.x +
self.values[5] * vector.y +
self.values[6] * vector.z +
self.values[7] * vector.w,
z: self.values[8] * vector.x +
self.values[9] * vector.y +
self.values[10] * vector.z +
self.values[11] * vector.w,
w: self.values[12] * vector.x +
self.values[13] * vector.y +
self.values[14] * vector.z +
self.values[15] * vector.w,
}
}
///|
/// Transform a point without performing the perspective divide. Renderers
/// must use this form so clip-space `w` survives until the GPU vertex shader.
pub fn Mat4::transform_homogeneous(self : Mat4, point : Vec3) -> Vec4 {
self.transform_vec4(Vec4::new(x=point.x, y=point.y, z=point.z, w=1.0))
}
///|
pub fn Mat4::look_at(eye~ : Vec3, target~ : Vec3, up~ : Vec3) -> Mat4 {
let forward = target.sub(eye).normalized()
let right = forward.cross(up).normalized()
let corrected_up = right.cross(forward)
let values = [
right.x,
right.y,
right.z,
-right.dot(eye),
corrected_up.x,
corrected_up.y,
corrected_up.z,
-corrected_up.dot(eye),
-forward.x,
-forward.y,
-forward.z,
forward.dot(eye),
0.0,
0.0,
0.0,
1.0,
]
{ values, }
}
///|
pub fn Mat4::perspective(
fov_y_radians~ : Double,
aspect~ : Double,
near~ : Double,
far~ : Double,
) -> Mat4 {
let safe_aspect = if aspect <= 1.0e-12 { 1.0 } else { aspect }
let safe_near = if near <= 1.0e-9 { 1.0e-9 } else { near }
let safe_far = if far <= safe_near { safe_near + 1.0 } else { far }
let f = 1.0 / @math.tan(fov_y_radians / 2.0)
let values = Array::make(16, 0.0)
values[0] = f / safe_aspect
values[5] = f
values[10] = (safe_far + safe_near) / (safe_near - safe_far)
values[11] = 2.0 * safe_far * safe_near / (safe_near - safe_far)
values[14] = -1.0
{ values, }
}
///|
pub(all) struct Quat {
x : Double
y : Double
z : Double
w : Double
} derive(Eq, Debug, ToJson)
///|
pub fn Quat::new(x~ : Double, y~ : Double, z~ : Double, w~ : Double) -> Quat {
{ x, y, z, w }
}
///|
pub fn Quat::x(self : Quat) -> Double {
self.x
}
///|
pub fn Quat::y(self : Quat) -> Double {
self.y
}
///|
pub fn Quat::z(self : Quat) -> Double {
self.z
}
///|
pub fn Quat::w(self : Quat) -> Double {
self.w
}
///|
pub fn Quat::identity() -> Quat {
{ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }
}
///|
pub fn Quat::from_axis_angle(axis~ : Vec3, angle~ : Double) -> Quat {
let half = angle / 2.0
let sine = @math.sin(half)
let unit = axis.normalized()
{ x: unit.x * sine, y: unit.y * sine, z: unit.z * sine, w: @math.cos(half) }
}
///|
pub fn Quat::mul(self : Quat, other : Quat) -> Quat {
{
x: self.w * other.x + self.x * other.w + self.y * other.z - self.z * other.y,
y: self.w * other.y - self.x * other.z + self.y * other.w + self.z * other.x,
z: self.w * other.z + self.x * other.y - self.y * other.x + self.z * other.w,
w: self.w * other.w - self.x * other.x - self.y * other.y - self.z * other.z,
}
}
///|
pub fn Quat::rotate(self : Quat, vector : Vec3) -> Vec3 {
let q_vector = Quat::{ x: vector.x, y: vector.y, z: vector.z, w: 0.0 }
let inverse = Quat::{ x: -self.x, y: -self.y, z: -self.z, w: self.w }
let rotated = self.mul(q_vector).mul(inverse)
Vec3::new(x=rotated.x, y=rotated.y, z=rotated.z)
}
///|
pub fn Quat::to_mat4(self : Quat) -> Mat4 {
let xx = self.x * self.x
let yy = self.y * self.y
let zz = self.z * self.z
let xy = self.x * self.y
let xz = self.x * self.z
let yz = self.y * self.z
let wx = self.w * self.x
let wy = self.w * self.y
let wz = self.w * self.z
Mat4::from_values([
1.0 - 2.0 * (yy + zz),
2.0 * (xy - wz),
2.0 * (xz + wy),
0.0,
2.0 * (xy + wz),
1.0 - 2.0 * (xx + zz),
2.0 * (yz - wx),
0.0,
2.0 * (xz - wy),
2.0 * (yz + wx),
1.0 - 2.0 * (xx + yy),
0.0,
0.0,
0.0,
0.0,
1.0,
])
}
///|
pub(all) struct Aabb {
min : Vec3
max : Vec3
valid : Bool
} derive(Eq, Debug, ToJson)
///|
pub fn Aabb::empty() -> Aabb {
{ min: Vec3::zero(), max: Vec3::zero(), valid: false }
}
///|
pub fn Aabb::from_points(points : Array[Vec3]) -> Aabb {
let mut result = Aabb::empty()
for point in points {
result = result.include_point(point)
}
result
}
///|
pub fn Aabb::include_point(self : Aabb, point : Vec3) -> Aabb {
if !self.valid {
{ min: point, max: point, valid: true }
} else {
{
min: Vec3::new(
x=if point.x < self.min.x { point.x } else { self.min.x },
y=if point.y < self.min.y { point.y } else { self.min.y },
z=if point.z < self.min.z { point.z } else { self.min.z },
),
max: Vec3::new(
x=if point.x > self.max.x { point.x } else { self.max.x },
y=if point.y > self.max.y { point.y } else { self.max.y },
z=if point.z > self.max.z { point.z } else { self.max.z },
),
valid: true,
}
}
}
///|
pub fn Aabb::union(self : Aabb, other : Aabb) -> Aabb {
if !self.valid {
other
} else if !other.valid {
self
} else {
self.include_point(other.min).include_point(other.max)
}
}
///|
pub fn Aabb::is_valid(self : Aabb) -> Bool {
self.valid
}
///|
pub fn Aabb::min(self : Aabb) -> Vec3 {
self.min
}
///|
pub fn Aabb::max(self : Aabb) -> Vec3 {
self.max
}
///|
pub fn Aabb::center(self : Aabb) -> Vec3 {
self.min.add(self.max).scale(0.5)
}
///|
pub fn Aabb::extent(self : Aabb) -> Vec3 {
self.max.sub(self.min)
}
///|
pub fn Aabb::radius(self : Aabb) -> Double {
self.extent().length() * 0.5
}
///|
pub fn Aabb::transformed(self : Aabb, matrix : Mat4) -> Aabb {
if !self.valid {
return self
}
let mut result = Aabb::empty()
for x in [self.min.x, self.max.x] {
for y in [self.min.y, self.max.y] {
for z in [self.min.z, self.max.z] {
result = result.include_point(
matrix.transform_point(Vec3::new(x~, y~, z~)),
)
}
}
}
result
}
///|
pub(all) struct Ray {
origin : Vec3
direction : Vec3
} derive(Eq, Debug, ToJson)
///|
pub fn Ray::new(origin~ : Vec3, direction~ : Vec3) -> Ray {
{ origin, direction: direction.normalized() }
}
///|
pub fn Ray::origin(self : Ray) -> Vec3 {
self.origin
}
///|
pub fn Ray::direction(self : Ray) -> Vec3 {
self.direction
}
///|
pub fn Ray::at(self : Ray, distance : Double) -> Vec3 {
self.origin.add(self.direction.scale(distance))
}
///|
pub fn Ray::intersect_aabb(self : Ray, bounds : Aabb) -> Bool {
if !bounds.valid {
return false
}
let mut near = -1.0e30
let mut far = 1.0e30
for axis in 0..<3 {
let origin = if axis == 0 {
self.origin.x
} else if axis == 1 {
self.origin.y
} else {
self.origin.z
}
let direction = if axis == 0 {
self.direction.x
} else if axis == 1 {
self.direction.y
} else {
self.direction.z
}
let minimum = if axis == 0 {
bounds.min.x
} else if axis == 1 {
bounds.min.y
} else {
bounds.min.z
}
let maximum = if axis == 0 {
bounds.max.x
} else if axis == 1 {
bounds.max.y
} else {
bounds.max.z
}
if direction.abs() <= 1.0e-12 {
if origin < minimum || origin > maximum {
return false
}
} else {
let inverse = 1.0 / direction
let first = (minimum - origin) * inverse
let second = (maximum - origin) * inverse
let lower = if first < second { first } else { second }
let upper = if first > second { first } else { second }
if lower > near {
near = lower
}
if upper < far {
far = upper
}
if near > far || far < 0.0 {
return false
}
}
}
true
}
///|
/// Moller-Trumbore ray/triangle intersection. Returns the positive distance.
pub fn Ray::intersect_triangle(
self : Ray,
a : Vec3,
b : Vec3,
c : Vec3,
) -> Double? {
let edge1 = b.sub(a)
let edge2 = c.sub(a)
let h = self.direction.cross(edge2)
let determinant = edge1.dot(h)
if determinant.abs() <= 1.0e-12 {
return None
}
let inverse = 1.0 / determinant
let s = self.origin.sub(a)
let u = inverse * s.dot(h)
if u < 0.0 || u > 1.0 {
return None
}
let q = s.cross(edge1)
let v = inverse * self.direction.dot(q)
if v < 0.0 || u + v > 1.0 {
return None
}
let distance = inverse * edge2.dot(q)
if distance >= 0.0 {
Some(distance)
} else {
None
}
}