///|
/// Projection model used by a media camera.
pub enum ProjectionModel {
Perspective
Orthographic
} derive(Debug)
///|
/// A viewport in physical pixels. The camera keeps this separate from the
/// animation timeline so a composition can be rendered at several sizes.
pub struct Viewport {
width : Double
height : Double
pixel_ratio : Double
} derive(Debug)
///|
pub fn viewport(
width : Double,
height : Double,
pixel_ratio? : Double = 1.0,
) -> Viewport {
{
width: width.max(1.0),
height: height.max(1.0),
pixel_ratio: pixel_ratio.max(0.01),
}
}
///|
pub fn Viewport::width(self : Viewport) -> Double {
self.width
}
///|
pub fn Viewport::height(self : Viewport) -> Double {
self.height
}
///|
pub fn Viewport::pixel_ratio(self : Viewport) -> Double {
self.pixel_ratio
}
///|
pub fn Viewport::aspect(self : Viewport) -> Double {
self.width / self.height
}
///|
pub fn Viewport::physical_width(self : Viewport) -> Double {
self.width * self.pixel_ratio
}
///|
pub fn Viewport::physical_height(self : Viewport) -> Double {
self.height * self.pixel_ratio
}
///|
pub fn Viewport::center(self : Viewport) -> Point2 {
point2(x=self.width * 0.5, y=self.height * 0.5)
}
///|
/// Lens parameters shared by 2D and 3D camera tracks.
pub struct CameraLens {
model : ProjectionModel
field_of_view : Double
orthographic_height : Double
near_plane : Double
far_plane : Double
aperture : Double
focus_distance : Double
} derive(Debug)
///|
pub fn perspective_lens(
field_of_view : Double,
near_plane? : Double = 0.01,
far_plane? : Double = 10000.0,
) -> CameraLens {
{
model: Perspective,
field_of_view: clamp(field_of_view, 0.01, 3.13),
orthographic_height: 10.0,
near_plane: near_plane.max(0.0001),
far_plane: far_plane.max(near_plane.max(0.0001) + 0.001),
aperture: 0.0,
focus_distance: 10.0,
}
}
///|
pub fn orthographic_lens(
height : Double,
near_plane? : Double = -10000.0,
far_plane? : Double = 10000.0,
) -> CameraLens {
{
model: Orthographic,
field_of_view: 1.0,
orthographic_height: height.max(0.0001),
near_plane,
far_plane: far_plane.max(near_plane + 0.001),
aperture: 0.0,
focus_distance: 10.0,
}
}
///|
pub fn CameraLens::model(self : CameraLens) -> ProjectionModel {
self.model
}
///|
pub fn CameraLens::field_of_view(self : CameraLens) -> Double {
self.field_of_view
}
///|
pub fn CameraLens::orthographic_height(self : CameraLens) -> Double {
self.orthographic_height
}
///|
pub fn CameraLens::near_plane(self : CameraLens) -> Double {
self.near_plane
}
///|
pub fn CameraLens::far_plane(self : CameraLens) -> Double {
self.far_plane
}
///|
pub fn CameraLens::aperture(self : CameraLens) -> Double {
self.aperture
}
///|
pub fn CameraLens::focus_distance(self : CameraLens) -> Double {
self.focus_distance
}
///|
pub fn CameraLens::with_aperture(
self : CameraLens,
aperture : Double,
) -> CameraLens {
{
model: self.model,
field_of_view: self.field_of_view,
orthographic_height: self.orthographic_height,
near_plane: self.near_plane,
far_plane: self.far_plane,
aperture: aperture.max(0.0),
focus_distance: self.focus_distance,
}
}
///|
pub fn CameraLens::with_focus_distance(
self : CameraLens,
distance : Double,
) -> CameraLens {
{
model: self.model,
field_of_view: self.field_of_view,
orthographic_height: self.orthographic_height,
near_plane: self.near_plane,
far_plane: self.far_plane,
aperture: self.aperture,
focus_distance: distance.max(self.near_plane),
}
}
///|
/// A camera pose with an explicit target. Target-based poses are convenient
/// for editorial tools because camera orientation remains stable when the
/// tracked object moves.
pub struct CameraPose {
position : Point3
target : Point3
up : Point3
roll : Double
} derive(Debug)
///|
pub fn camera_pose(
position : Point3,
target : Point3,
up? : Point3 = point3(x=0.0, y=1.0, z=0.0),
roll? : Double = 0.0,
) -> CameraPose {
{ position, target, up, roll }
}
///|
pub fn CameraPose::position(self : CameraPose) -> Point3 {
self.position
}
///|
pub fn CameraPose::target(self : CameraPose) -> Point3 {
self.target
}
///|
pub fn CameraPose::up(self : CameraPose) -> Point3 {
self.up
}
///|
pub fn CameraPose::roll(self : CameraPose) -> Double {
self.roll
}
///|
pub fn CameraPose::forward(self : CameraPose) -> Point3 {
point3(
x=self.target.x() - self.position.x(),
y=self.target.y() - self.position.y(),
z=self.target.z() - self.position.z(),
)
|> point3_normalized()
}
///|
pub fn CameraPose::right(self : CameraPose) -> Point3 {
point3_cross(self.forward(), point3_normalized(self.up))
|> point3_normalized()
}
///|
pub fn CameraPose::true_up(self : CameraPose) -> Point3 {
point3_cross(self.right(), self.forward()) |> point3_normalized()
}
///|
pub fn CameraPose::look_at(self : CameraPose, target : Point3) -> CameraPose {
{ position: self.position, target, up: self.up, roll: self.roll }
}
///|
pub fn CameraPose::translated(self : CameraPose, offset : Point3) -> CameraPose {
{
position: point3(
x=self.position.x() + offset.x(),
y=self.position.y() + offset.y(),
z=self.position.z() + offset.z(),
),
target: point3(
x=self.target.x() + offset.x(),
y=self.target.y() + offset.y(),
z=self.target.z() + offset.z(),
),
up: self.up,
roll: self.roll,
}
}
///|
pub fn CameraPose::lerp(
self : CameraPose,
other : CameraPose,
ratio : Double,
) -> CameraPose {
let t = clamp01(ratio)
{
position: self.position.lerp(other.position, t),
target: self.target.lerp(other.target, t),
up: self.up.lerp(other.up, t),
roll: self.roll + (other.roll - self.roll) * t,
}
}
///|
/// A camera rig combines animated pose, lens, and viewport policies.
pub struct CameraRig {
pose : CameraPose
lens : CameraLens
viewport : Viewport
jitter : Point2
} derive(Debug)
///|
pub fn camera_rig(
pose : CameraPose,
lens : CameraLens,
viewport : Viewport,
) -> CameraRig {
{ pose, lens, viewport, jitter: point2(x=0.0, y=0.0) }
}
///|
pub fn CameraRig::pose(self : CameraRig) -> CameraPose {
self.pose
}
///|
pub fn CameraRig::lens(self : CameraRig) -> CameraLens {
self.lens
}
///|
pub fn CameraRig::viewport(self : CameraRig) -> Viewport {
self.viewport
}
///|
pub fn CameraRig::jitter(self : CameraRig) -> Point2 {
self.jitter
}
///|
pub fn CameraRig::with_jitter(self : CameraRig, jitter : Point2) -> CameraRig {
{ pose: self.pose, lens: self.lens, viewport: self.viewport, jitter }
}
///|
fn CameraRig::camera_coordinates(self : CameraRig, world : Point3) -> Point3 {
let delta = point3(
x=world.x() - self.pose.position.x(),
y=world.y() - self.pose.position.y(),
z=world.z() - self.pose.position.z(),
)
let right = self.pose.right()
let up = self.pose.true_up()
let forward = self.pose.forward()
point3(
x=point3_dot(delta, right),
y=point3_dot(delta, up),
z=point3_dot(delta, forward),
)
}
///|
pub fn CameraRig::project(self : CameraRig, world : Point3) -> Point2 {
let camera = self.camera_coordinates(world)
let aspect = self.viewport.aspect()
match self.lens.model {
Perspective => {
let depth = camera.z().max(self.lens.near_plane)
let scale = @math.tan(self.lens.field_of_view * 0.5)
let screen_x = (camera.x() / (depth * scale * aspect) * 0.5 + 0.5) *
self.viewport.width +
self.jitter.x()
let screen_y = (0.5 - camera.y() / (depth * scale) * 0.5) *
self.viewport.height +
self.jitter.y()
point2(x=screen_x, y=screen_y)
}
Orthographic => {
let half_height = self.lens.orthographic_height * 0.5
let half_width = half_height * aspect
point2(
x=(camera.x() / half_width * 0.5 + 0.5) * self.viewport.width +
self.jitter.x(),
y=(0.5 - camera.y() / half_height * 0.5) * self.viewport.height +
self.jitter.y(),
)
}
}
}
///|
pub fn CameraRig::visible(self : CameraRig, world : Point3) -> Bool {
let camera = self.camera_coordinates(world)
camera.z() >= self.lens.near_plane && camera.z() <= self.lens.far_plane
}
///|
pub fn CameraRig::unproject(
self : CameraRig,
screen : Point2,
depth : Double,
) -> Point3 {
let aspect = self.viewport.aspect()
let x = (screen.x() - self.jitter.x()) / self.viewport.width * 2.0 - 1.0
let y = 1.0 - (screen.y() - self.jitter.y()) / self.viewport.height * 2.0
let camera = match self.lens.model {
Perspective => {
let scale = @math.tan(self.lens.field_of_view * 0.5)
point3(x=x * depth * scale * aspect, y=y * depth * scale, z=depth)
}
Orthographic => {
let half_height = self.lens.orthographic_height * 0.5
let half_width = half_height * aspect
point3(x=x * half_width, y=y * half_height, z=depth)
}
}
let right = self.pose.right()
let up = self.pose.true_up()
let forward = self.pose.forward()
point3(
x=self.pose.position.x() +
right.x() * camera.x() +
up.x() * camera.y() +
forward.x() * camera.z(),
y=self.pose.position.y() +
right.y() * camera.x() +
up.y() * camera.y() +
forward.y() * camera.z(),
z=self.pose.position.z() +
right.z() * camera.x() +
up.z() * camera.y() +
forward.z() * camera.z(),
)
}
///|
pub fn CameraRig::ray(self : CameraRig, screen : Point2) -> (Point3, Point3) {
let origin = match self.lens.model {
Perspective => self.pose.position
Orthographic => self.unproject(screen, self.lens.near_plane)
}
let far = self.unproject(screen, self.lens.far_plane)
let direction = point3_normalized(
point3(
x=far.x() - origin.x(),
y=far.y() - origin.y(),
z=far.z() - origin.z(),
),
)
(origin, direction)
}
///|
pub fn CameraRig::fit_distance(self : CameraRig, radius : Double) -> Double {
match self.lens.model {
Perspective => radius.max(0.0) / @math.tan(self.lens.field_of_view * 0.5)
Orthographic => radius.max(0.0)
}
}
///|
pub fn CameraRig::dolly_to_fit(
self : CameraRig,
center : Point3,
radius : Double,
) -> CameraRig {
let direction = self.pose.forward()
let distance = self.fit_distance(radius)
let position = point3(
x=center.x() - direction.x() * distance,
y=center.y() - direction.y() * distance,
z=center.z() - direction.z() * distance,
)
camera_rig(
camera_pose(position, center, up=self.pose.up, roll=self.pose.roll),
self.lens,
self.viewport,
).with_jitter(self.jitter)
}
///|
/// A camera keyframe carries pose and lens changes as one editorial unit.
pub struct CameraKeyframe {
time : Double
pose : CameraPose
lens : CameraLens
curve : Curve
} derive(Debug)
///|
pub fn camera_keyframe(
time : Double,
pose : CameraPose,
lens : CameraLens,
curve? : Curve = Curve::builtin(Linear),
) -> CameraKeyframe raise MotionError {
ensure_finite(time)
{ time, pose, lens, curve }
}
///|
pub fn CameraKeyframe::time(self : CameraKeyframe) -> Double {
self.time
}
///|
pub fn CameraKeyframe::pose(self : CameraKeyframe) -> CameraPose {
self.pose
}
///|
pub fn CameraKeyframe::lens(self : CameraKeyframe) -> CameraLens {
self.lens
}
///|
pub fn CameraKeyframe::curve(self : CameraKeyframe) -> Curve {
self.curve
}
///|
pub struct CameraTrack {
frames : Array[CameraKeyframe]
viewport : Viewport
} derive(Debug)
///|
pub fn CameraTrack::new(
frames : Array[CameraKeyframe],
viewport : Viewport,
) -> CameraTrack raise MotionError {
if frames.length() == 0 {
raise MotionError::EmptyTrack
}
for index in 1.. Int {
self.frames.length()
}
///|
pub fn CameraTrack::start_time(self : CameraTrack) -> Double {
self.frames[0].time
}
///|
pub fn CameraTrack::end_time(self : CameraTrack) -> Double {
self.frames[self.frames.length() - 1].time
}
///|
pub fn CameraTrack::duration(self : CameraTrack) -> Double {
self.end_time() - self.start_time()
}
///|
pub fn CameraTrack::keyframes(self : CameraTrack) -> Array[CameraKeyframe] {
self.frames.copy()
}
///|
fn CameraTrack::segment(
self : CameraTrack,
time : Double,
) -> (CameraKeyframe, CameraKeyframe, Double) {
let last = self.frames.length() - 1
if time <= self.frames[0].time {
return (self.frames[0], self.frames[0], 0.0)
}
if time >= self.frames[last].time {
return (self.frames[last], self.frames[last], 0.0)
}
for index in 0.. CameraRig {
let (left, right, ratio) = self.segment(time)
let pose = left.pose.lerp(right.pose, ratio)
let lens = if ratio < 0.5 { left.lens } else { right.lens }
camera_rig(pose, lens, self.viewport)
}
///|
pub fn CameraTrack::sample_many(
self : CameraTrack,
count : Int,
) -> Array[CameraRig] raise MotionError {
if count < 2 {
raise MotionError::InvalidSampleCount(count)
}
let values : Array[CameraRig] = []
for index in 0.. CameraTrack raise MotionError {
let frames : Array[CameraKeyframe] = []
for frame in self.frames {
frames.push({
time: frame.time + delta,
pose: frame.pose,
lens: frame.lens,
curve: frame.curve,
})
}
CameraTrack::new(frames, self.viewport)
}