///|
/// Cubic Bézier geometry in scene space. The timing Curve type is kept
/// separate so geometry can be reused by cameras, particles, and masks.
pub struct CubicPath3D {
start : Point3
control_start : Point3
control_end : Point3
end : Point3
} derive(Debug)
///|
pub fn cubic_path3d(
start : Point3,
control_start : Point3,
control_end : Point3,
end : Point3,
) -> CubicPath3D {
{ start, control_start, control_end, end }
}
///|
pub fn CubicPath3D::start(self : CubicPath3D) -> Point3 {
self.start
}
///|
pub fn CubicPath3D::control_start(self : CubicPath3D) -> Point3 {
self.control_start
}
///|
pub fn CubicPath3D::control_end(self : CubicPath3D) -> Point3 {
self.control_end
}
///|
pub fn CubicPath3D::end(self : CubicPath3D) -> Point3 {
self.end
}
///|
pub fn CubicPath3D::sample(self : CubicPath3D, ratio : Double) -> Point3 {
let t = clamp01(ratio)
let a = self.start.lerp(self.control_start, t)
let b = self.control_start.lerp(self.control_end, t)
let c = self.control_end.lerp(self.end, t)
a.lerp(b, t).lerp(b.lerp(c, t), t)
}
///|
pub fn CubicPath3D::tangent(self : CubicPath3D, ratio : Double) -> Point3 {
let t = clamp01(ratio)
let one = 1.0 - t
let x = 3.0 *
(
one * one * (self.control_start.x() - self.start.x()) +
2.0 * one * t * (self.control_end.x() - self.control_start.x()) +
t * t * (self.end.x() - self.control_end.x())
)
let y = 3.0 *
(
one * one * (self.control_start.y() - self.start.y()) +
2.0 * one * t * (self.control_end.y() - self.control_start.y()) +
t * t * (self.end.y() - self.control_end.y())
)
let z = 3.0 *
(
one * one * (self.control_start.z() - self.start.z()) +
2.0 * one * t * (self.control_end.z() - self.control_start.z()) +
t * t * (self.end.z() - self.control_end.z())
)
point3(x~, y~, z~)
}
///|
pub fn CubicPath3D::curvature(self : CubicPath3D, ratio : Double) -> Double {
let t = clamp01(ratio)
let tangent = self.tangent(t)
let second_x = 6.0 *
(
(1.0 - t) *
(self.control_end.x() - 2.0 * self.control_start.x() + self.start.x()) +
t * (self.end.x() - 2.0 * self.control_end.x() + self.control_start.x())
)
let second_y = 6.0 *
(
(1.0 - t) *
(self.control_end.y() - 2.0 * self.control_start.y() + self.start.y()) +
t * (self.end.y() - 2.0 * self.control_end.y() + self.control_start.y())
)
let second_z = 6.0 *
(
(1.0 - t) *
(self.control_end.z() - 2.0 * self.control_start.z() + self.start.z()) +
t * (self.end.z() - 2.0 * self.control_end.z() + self.control_start.z())
)
let second = point3(x=second_x, y=second_y, z=second_z)
let cross = point3_cross(tangent, second)
let speed = point3_length(tangent)
if speed <= 0.000000000001 {
0.0
} else {
point3_length(cross) / (speed * speed * speed)
}
}
///|
pub fn CubicPath3D::length(self : CubicPath3D, samples : Int) -> Double {
let count = samples.max(1)
let mut total = 0.0
let mut previous = self.sample(0.0)
for index in 1..<=count {
let current = self.sample(index.to_double() / count.to_double())
total = total + point3_distance(previous, current)
previous = current
}
total
}
///|
pub fn CubicPath3D::sample_by_distance(
self : CubicPath3D,
distance : Double,
samples? : Int = 64,
) -> Point3 {
let count = samples.max(1)
let total = self.length(count)
if total <= 0.0 {
return self.start()
}
let target = clamp(distance, 0.0, total)
let mut travelled = 0.0
let mut previous = self.start()
for index in 1..<=count {
let current = self.sample(index.to_double() / count.to_double())
let span = point3_distance(previous, current)
if travelled + span >= target {
let ratio = if span == 0.0 { 0.0 } else { (target - travelled) / span }
return previous.lerp(current, ratio)
}
travelled = travelled + span
previous = current
}
self.end
}
///|
pub fn CubicPath3D::polyline(
self : CubicPath3D,
samples : Int,
) -> Array[Point3] {
let count = samples.max(1)
let values : Array[Point3] = []
for index in 0..<=count {
values.push(self.sample(index.to_double() / count.to_double()))
}
values
}
///|
pub enum PathSegment3D {
Line3D(Point3, Point3)
Cubic3D(CubicPath3D)
} derive(Debug)
///|
pub fn path_line3d(start : Point3, end : Point3) -> PathSegment3D {
Line3D(start, end)
}
///|
pub fn path_cubic3d(
start : Point3,
control_start : Point3,
control_end : Point3,
end : Point3,
) -> PathSegment3D {
Cubic3D(cubic_path3d(start, control_start, control_end, end))
}
///|
pub fn PathSegment3D::start(self : PathSegment3D) -> Point3 {
match self {
Line3D(start, _) => start
Cubic3D(path) => path.start
}
}
///|
pub fn PathSegment3D::end(self : PathSegment3D) -> Point3 {
match self {
Line3D(_, end) => end
Cubic3D(path) => path.end
}
}
///|
pub fn PathSegment3D::sample(self : PathSegment3D, ratio : Double) -> Point3 {
match self {
Line3D(start, end) => start.lerp(end, clamp01(ratio))
Cubic3D(path) => path.sample(ratio)
}
}
///|
pub fn PathSegment3D::tangent(self : PathSegment3D, ratio : Double) -> Point3 {
match self {
Line3D(start, end) =>
point3(
x=end.x() - start.x(),
y=end.y() - start.y(),
z=end.z() - start.z(),
)
Cubic3D(path) => path.tangent(ratio)
}
}
///|
pub fn PathSegment3D::length(self : PathSegment3D, samples : Int) -> Double {
match self {
Line3D(start, end) => point3_distance(start, end)
Cubic3D(path) => path.length(samples)
}
}
///|
pub fn PathSegment3D::curvature(self : PathSegment3D, ratio : Double) -> Double {
match self {
Line3D(_, _) => 0.0
Cubic3D(path) => path.curvature(ratio)
}
}
///|
pub fn PathSegment3D::polyline(
self : PathSegment3D,
samples : Int,
) -> Array[Point3] {
let count = samples.max(1)
let values : Array[Point3] = []
for index in 0..<=count {
values.push(self.sample(index.to_double() / count.to_double()))
}
values
}
///|
pub struct Bounds3D {
minimum : Point3
maximum : Point3
} derive(Debug)
///|
pub fn bounds3d(minimum : Point3, maximum : Point3) -> Bounds3D {
{ minimum, maximum }
}
///|
pub fn Bounds3D::minimum(self : Bounds3D) -> Point3 {
self.minimum
}
///|
pub fn Bounds3D::maximum(self : Bounds3D) -> Point3 {
self.maximum
}
///|
pub fn Bounds3D::size(self : Bounds3D) -> Point3 {
point3(
x=self.maximum.x() - self.minimum.x(),
y=self.maximum.y() - self.minimum.y(),
z=self.maximum.z() - self.minimum.z(),
)
}
///|
pub fn Bounds3D::center(self : Bounds3D) -> Point3 {
self.minimum.lerp(self.maximum, 0.5)
}
///|
pub fn Bounds3D::contains(self : Bounds3D, value : Point3) -> Bool {
(value.x() >= self.minimum.x() && value.x() <= self.maximum.x()) &&
(value.y() >= self.minimum.y() && value.y() <= self.maximum.y()) &&
value.z() >= self.minimum.z() &&
value.z() <= self.maximum.z()
}
///|
pub fn Bounds3D::expanded(self : Bounds3D, amount : Double) -> Bounds3D {
bounds3d(
point3(
x=self.minimum.x() - amount,
y=self.minimum.y() - amount,
z=self.minimum.z() - amount,
),
point3(
x=self.maximum.x() + amount,
y=self.maximum.y() + amount,
z=self.maximum.z() + amount,
),
)
}
///|
pub fn Bounds3D::union(self : Bounds3D, other : Bounds3D) -> Bounds3D {
bounds3d(
point3(
x=self.minimum.x().min(other.minimum.x()),
y=self.minimum.y().min(other.minimum.y()),
z=self.minimum.z().min(other.minimum.z()),
),
point3(
x=self.maximum.x().max(other.maximum.x()),
y=self.maximum.y().max(other.maximum.y()),
z=self.maximum.z().max(other.maximum.z()),
),
)
}
///|
pub struct Path3D {
segments : Array[PathSegment3D]
lengths : Array[Double]
total_length : Double
} derive(Debug)
///|
pub fn Path3D::new(
segments : Array[PathSegment3D],
samples_per_curve? : Int = 32,
) -> Path3D raise MotionError {
if segments.length() == 0 {
raise MotionError::EmptyTrack
}
let lengths : Array[Double] = []
let mut total = 0.0
for segment in segments {
let length = segment.length(samples_per_curve.max(1))
lengths.push(length)
total = total + length
}
{ segments: segments.copy(), lengths, total_length: total }
}
///|
pub fn Path3D::segments(self : Path3D) -> Array[PathSegment3D] {
self.segments.copy()
}
///|
pub fn Path3D::length(self : Path3D) -> Int {
self.segments.length()
}
///|
pub fn Path3D::total_length(self : Path3D) -> Double {
self.total_length
}
///|
pub fn Path3D::start(self : Path3D) -> Point3 {
self.segments[0].start()
}
///|
pub fn Path3D::end(self : Path3D) -> Point3 {
self.segments[self.segments.length() - 1].end()
}
///|
fn Path3D::locate(self : Path3D, distance : Double) -> (Int, Double) {
if self.total_length <= 0.0 {
return (0, 0.0)
}
let target = clamp(distance, 0.0, self.total_length)
let mut offset = 0.0
for index in 0.. Point3 {
let (index, ratio) = self.locate(clamp01(parameter) * self.total_length)
self.segments[index].sample(ratio)
}
///|
pub fn Path3D::sample_by_distance(self : Path3D, distance : Double) -> Point3 {
let (index, ratio) = self.locate(distance)
self.segments[index].sample(ratio)
}
///|
pub fn Path3D::tangent(self : Path3D, parameter : Double) -> Point3 {
let (index, ratio) = self.locate(clamp01(parameter) * self.total_length)
self.segments[index].tangent(ratio)
}
///|
pub fn Path3D::curvature(self : Path3D, parameter : Double) -> Double {
let (index, ratio) = self.locate(clamp01(parameter) * self.total_length)
self.segments[index].curvature(ratio)
}
///|
pub fn Path3D::polyline(
self : Path3D,
samples_per_segment : Int,
) -> Array[Point3] {
let values : Array[Point3] = []
for segment_index in 0.. 0 {
values.push(points[point_index])
}
}
}
values
}
///|
pub fn Path3D::bounds(
self : Path3D,
samples_per_segment? : Int = 32,
) -> Bounds3D {
let first = self.start()
let mut minimum = first
let mut maximum = first
for segment in self.segments {
let points = segment.polyline(samples_per_segment.max(1))
for point in points {
minimum = point3(
x=minimum.x().min(point.x()),
y=minimum.y().min(point.y()),
z=minimum.z().min(point.z()),
)
maximum = point3(
x=maximum.x().max(point.x()),
y=maximum.y().max(point.y()),
z=maximum.z().max(point.z()),
)
}
}
bounds3d(minimum, maximum)
}
///|
pub struct PathProjection3D {
parameter : Double
distance : Double
value : Point3
error : Double
} derive(Debug)
///|
pub fn PathProjection3D::parameter(self : PathProjection3D) -> Double {
self.parameter
}
///|
pub fn PathProjection3D::distance(self : PathProjection3D) -> Double {
self.distance
}
///|
pub fn PathProjection3D::value(self : PathProjection3D) -> Point3 {
self.value
}
///|
pub fn PathProjection3D::error(self : PathProjection3D) -> Double {
self.error
}
///|
pub fn Path3D::project(
self : Path3D,
target : Point3,
samples_per_segment? : Int = 32,
) -> PathProjection3D {
let steps = samples_per_segment.max(1)
let mut best_parameter = 0.0
let mut best_value = self.start()
let mut best_distance = point3_distance(best_value, target)
let mut offset = 0.0
for segment_index in 0.. PathBuilder3D {
{ current: start, segments: [] }
}
///|
pub fn PathBuilder3D::current(self : PathBuilder3D) -> Point3 {
self.current
}
///|
pub fn PathBuilder3D::line_to(self : PathBuilder3D, end : Point3) -> Unit {
self.segments.push(Line3D(self.current, end))
self.current = end
}
///|
pub fn PathBuilder3D::cubic_to(
self : PathBuilder3D,
control_start : Point3,
control_end : Point3,
end : Point3,
) -> Unit {
self.segments.push(
Cubic3D(cubic_path3d(self.current, control_start, control_end, end)),
)
self.current = end
}
///|
pub fn PathBuilder3D::close(self : PathBuilder3D) -> Unit {
self.segments.push(Line3D(self.current, self.segments[0].start()))
self.current = self.segments[0].start()
}
///|
pub fn PathBuilder3D::build(
self : PathBuilder3D,
samples_per_curve? : Int = 32,
) -> Path3D raise MotionError {
Path3D::new(self.segments, samples_per_curve~)
}
///|
pub fn Path3D::reversed(self : Path3D) -> Path3D raise MotionError {
let reversed : Array[PathSegment3D] = []
let mut index = self.segments.length() - 1
while index >= 0 {
match self.segments[index] {
Line3D(start, end) => reversed.push(Line3D(end, start))
Cubic3D(path) =>
reversed.push(
Cubic3D(
cubic_path3d(
path.end,
path.control_end,
path.control_start,
path.start,
),
),
)
}
index = index - 1
}
Path3D::new(reversed)
}
///|
pub fn Path3D::append(
self : Path3D,
segment : PathSegment3D,
) -> Path3D raise MotionError {
let values = self.segments.copy()
values.push(segment)
Path3D::new(values)
}
///|
pub fn Path3D::sample_many(
self : Path3D,
count : Int,
) -> Array[Point3] raise MotionError {
if count < 2 {
raise MotionError::InvalidSampleCount(count)
}
let values : Array[Point3] = []
for index in 0..