///|
/// Differential samples for camera rails and particle emitters.
pub struct PathSample3D {
parameter : Double
distance : Double
position : Point3
tangent : Point3
normal : Point3
curvature : Double
} derive(Debug)
///|
pub fn PathSample3D::parameter(self : PathSample3D) -> Double {
self.parameter
}
///|
pub fn PathSample3D::distance(self : PathSample3D) -> Double {
self.distance
}
///|
pub fn PathSample3D::position(self : PathSample3D) -> Point3 {
self.position
}
///|
pub fn PathSample3D::tangent(self : PathSample3D) -> Point3 {
self.tangent
}
///|
pub fn PathSample3D::normal(self : PathSample3D) -> Point3 {
self.normal
}
///|
pub fn PathSample3D::curvature(self : PathSample3D) -> Double {
self.curvature
}
///|
fn tangent_normal(
path : Path3D,
parameter : Double,
) -> (Point3, Point3, Double) {
let tangent = point3_normalized(path.tangent(parameter))
let epsilon = 0.0001
let left = point3_normalized(path.tangent((parameter - epsilon).max(0.0)))
let right = point3_normalized(path.tangent((parameter + epsilon).min(1.0)))
let normal = point3_normalized(
point3(
x=right.x() - left.x(),
y=right.y() - left.y(),
z=right.z() - left.z(),
),
)
(tangent, normal, path.curvature(parameter))
}
///|
pub fn sample_path3d(path : Path3D, parameter : Double) -> PathSample3D {
let t = clamp01(parameter)
let position = path.sample(t)
let tangent = tangent_normal(path, t)
{
parameter: t,
distance: t * path.total_length(),
position,
tangent: tangent.0,
normal: tangent.1,
curvature: tangent.2,
}
}
///|
pub fn sample_path3d_by_distance(
path : Path3D,
distance : Double,
) -> PathSample3D {
let parameter = if path.total_length() == 0.0 {
0.0
} else {
clamp(distance, 0.0, path.total_length()) / path.total_length()
}
sample_path3d(path, parameter)
}
///|
pub struct PathMetricProfile3D {
samples : Array[PathSample3D]
total_length : Double
maximum_curvature : Double
average_curvature : Double
} derive(Debug)
///|
pub fn build_path_metric_profile(
path : Path3D,
count : Int,
) -> PathMetricProfile3D {
let actual = count.max(2)
let samples : Array[PathSample3D] = []
let mut maximum = 0.0
let mut sum = 0.0
for index in 0.. Array[PathSample3D] {
self.samples.copy()
}
///|
pub fn PathMetricProfile3D::total_length(self : PathMetricProfile3D) -> Double {
self.total_length
}
///|
pub fn PathMetricProfile3D::maximum_curvature(
self : PathMetricProfile3D,
) -> Double {
self.maximum_curvature
}
///|
pub fn PathMetricProfile3D::average_curvature(
self : PathMetricProfile3D,
) -> Double {
self.average_curvature
}
///|
pub fn PathMetricProfile3D::sample(
self : PathMetricProfile3D,
parameter : Double,
) -> PathSample3D {
if self.samples.length() == 0 {
return {
parameter: 0.0,
distance: 0.0,
position: point3(x=0.0, y=0.0, z=0.0),
tangent: point3(x=0.0, y=0.0, z=1.0),
normal: point3(x=0.0, y=1.0, z=0.0),
curvature: 0.0,
}
}
let position = clamp01(parameter) * (self.samples.length() - 1).to_double()
let index = @math.floor(position).to_int().min(self.samples.length() - 2)
let ratio = position - index.to_double()
let left = self.samples[index]
let right = self.samples[index + 1]
{
parameter: left.parameter + (right.parameter - left.parameter) * ratio,
distance: left.distance + (right.distance - left.distance) * ratio,
position: left.position.lerp(right.position, ratio),
tangent: point3_normalized(left.tangent.lerp(right.tangent, ratio)),
normal: point3_normalized(left.normal.lerp(right.normal, ratio)),
curvature: left.curvature + (right.curvature - left.curvature) * ratio,
}
}
///|
pub struct PathFrame3D {
position : Point3
tangent : Point3
normal : Point3
binormal : Point3
} derive(Debug)
///|
pub fn PathFrame3D::position(self : PathFrame3D) -> Point3 {
self.position
}
///|
pub fn PathFrame3D::tangent(self : PathFrame3D) -> Point3 {
self.tangent
}
///|
pub fn PathFrame3D::normal(self : PathFrame3D) -> Point3 {
self.normal
}
///|
pub fn PathFrame3D::binormal(self : PathFrame3D) -> Point3 {
self.binormal
}
///|
pub fn build_path_frame(sample : PathSample3D, up : Point3) -> PathFrame3D {
let tangent = point3_normalized(sample.tangent)
let binormal = point3_normalized(point3_cross(tangent, up))
let normal = point3_normalized(point3_cross(binormal, tangent))
{ position: sample.position, tangent, normal, binormal }
}
///|
pub fn PathFrame3D::offset(
self : PathFrame3D,
horizontal : Double,
vertical : Double,
) -> Point3 {
point3(
x=self.position.x() +
self.normal.x() * horizontal +
self.binormal.x() * vertical,
y=self.position.y() +
self.normal.y() * horizontal +
self.binormal.y() * vertical,
z=self.position.z() +
self.normal.z() * horizontal +
self.binormal.z() * vertical,
)
}
///|
pub fn PathFrame3D::look_target(
self : PathFrame3D,
distance : Double,
) -> Point3 {
point3(
x=self.position.x() + self.tangent.x() * distance,
y=self.position.y() + self.tangent.y() * distance,
z=self.position.z() + self.tangent.z() * distance,
)
}
///|
pub fn path_chord_error(path : Path3D, samples : Int) -> Double {
let count = samples.max(2)
let mut maximum = 0.0
let mut previous = path.sample(0.0)
for index in 1..<=count {
let current = path.sample(index.to_double() / count.to_double())
let midpoint = previous.lerp(current, 0.5)
let actual = path.sample((index.to_double() - 0.5) / count.to_double())
maximum = maximum.max(point3_distance(midpoint, actual))
previous = current
}
maximum
}
///|
pub fn path_speed_at(path : Path3D, parameter : Double) -> Double {
point3_length(path.tangent(parameter))
}
///|
pub fn path_speed_profile(path : Path3D, count : Int) -> Array[Double] {
let values : Array[Double] = []
let actual = count.max(1)
for index in 0.. Bool {
point3_distance(path.start(), path.end()) <= tolerance.abs()
}
///|
pub fn PathMetricProfile3D::max_speed(self : PathMetricProfile3D) -> Double {
let mut maximum = 0.0
for sample in self.samples {
maximum = maximum.max(1.0 / (sample.parameter + 0.000001))
}
maximum
}