pbrt/src/shapes/curves.rs

207 lines
6.8 KiB
Rust

use crate::Arena;
use crate::core::shape::CreateShape;
use crate::core::texture::FloatTexture;
use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::Float;
use shared::core::geometry::{Normal3f, Point3f};
use shared::core::shape::Shape;
use shared::shapes::{CurveCommon, CurveShape, CurveType};
use shared::utils::Transform;
use shared::utils::math::lerp;
use shared::utils::splines::{
cubic_bspline_to_bezier, elevate_quadratic_bezier_to_cubic, quadratic_bspline_to_bezier,
};
use log::warn;
use std::collections::HashMap;
use std::sync::Arc;
pub fn create_curve(
render_from_object: Transform,
object_from_render: Transform,
reverse_orientation: bool,
seg_cp_bezier: &[Point3f],
w0: Float,
w1: Float,
curve_type: CurveType,
seg_normals: &[Normal3f],
split_depth: usize,
) -> Vec<Shape> {
let curve_common = CurveCommon::new(
seg_cp_bezier,
w0,
w1,
curve_type,
seg_normals,
render_from_object,
object_from_render,
reverse_orientation,
);
let n_segments = 1 << split_depth;
let mut segments: Vec<Shape> = Vec::with_capacity(n_segments);
for i in 0..n_segments {
let u_min = i as Float / n_segments as Float;
let u_max = (i + 1) as Float / n_segments as Float;
let curve = CurveShape {
common: curve_common.clone(),
u_min,
u_max,
};
segments.push(Shape::Curve(curve));
}
segments
}
impl CreateShape for CurveShape {
fn create(
render_from_object: Transform,
object_from_render: Transform,
reverse_orientation: bool,
parameters: ParameterDictionary,
_float_textures: &HashMap<String, Arc<FloatTexture>>,
_loc: FileLoc,
_arena: &Arena,
) -> Result<Vec<Shape>> {
let width = parameters.get_one_float("width", 1.0);
let width0 = parameters.get_one_float("width0", width);
let width1 = parameters.get_one_float("width1", width);
let degree = parameters.get_one_int("degree", 3);
if degree != 2 && degree != 3 {
return Err(anyhow!(
"Invalid degree {}: only degree 2 and 3 curves are supported.",
degree
));
}
let basis = parameters.get_one_string("basis", "bezier");
if basis != "bezier" && basis != "bspline" {
return Err(anyhow!(
"Invalid basis \"{}\": only \"bezier\" and \"bspline\" are supported.",
basis
));
}
let cp = parameters.get_point3f_array("P");
let n_segments;
if basis == "bezier" {
if cp.len() <= degree as usize
|| ((cp.len() - 1 - degree as usize) % degree as usize) != 0
{
return Err(anyhow!(
"Invalid number of control points {}: for the degree {} Bezier basis {} + n * {} are required.",
cp.len(),
degree,
degree + 1,
degree
));
}
n_segments = (cp.len() - 1) / degree as usize;
} else {
if cp.len() < (degree + 1) as usize {
return Err(anyhow!(
"Invalid number of control points {}: for the degree {} b-spline basis, must have >= {}.",
cp.len(),
degree,
degree + 1
));
}
n_segments = cp.len() - degree as usize;
}
let curve_type_str = parameters.get_one_string("type", "flat");
let curve_type = match curve_type_str.as_str() {
"flat" => CurveType::Flat,
"ribbon" => CurveType::Ribbon,
"cylinder" => CurveType::Cylinder,
_ => {
return Err(anyhow!("Unknown curve type \"{}\".", curve_type_str));
}
};
let mut n = parameters.get_normal3f_array("N");
if !n.is_empty() {
if curve_type != CurveType::Ribbon {
warn!("Curve normals are only used with \"ribbon\" type curves. Discarding.");
n.clear();
} else if n.len() != n_segments + 1 {
return Err(anyhow!(
"Invalid number of normals {}: must provide {} normals for ribbon curves with {} segments.",
n.len(),
n_segments + 1,
n_segments
));
}
} else if curve_type == CurveType::Ribbon {
return Err(anyhow!(
"Must provide normals \"N\" at curve endpoints with ribbon curves."
));
}
let use_gpu = false; // Replace with actual config check
let split_depth = if use_gpu {
0
} else {
parameters.get_one_int("splitdepth", 3)
};
let mut curves: Vec<Shape> = Vec::new();
let mut cp_offset = 0;
for seg in 0..n_segments {
let seg_cp_bezier: [Point3f; 4];
if basis == "bezier" {
if degree == 2 {
let slice = &cp[cp_offset..cp_offset + 3];
seg_cp_bezier = elevate_quadratic_bezier_to_cubic(slice);
cp_offset += 2; // Advance by degree
} else {
let slice = &cp[cp_offset..cp_offset + 4];
seg_cp_bezier = [slice[0], slice[1], slice[2], slice[3]];
cp_offset += 3; // Advance by degree
}
} else {
if degree == 2 {
let slice = &cp[cp_offset..cp_offset + 3];
let bez_cp = quadratic_bspline_to_bezier(slice);
seg_cp_bezier = elevate_quadratic_bezier_to_cubic(&bez_cp);
} else {
let slice = &cp[cp_offset..cp_offset + 4];
seg_cp_bezier = cubic_bspline_to_bezier(slice);
}
cp_offset += 1; // Advance by 1 for B-Splines
}
let w0 = lerp(seg as Float / n_segments as Float, width0, width1);
let w1 = lerp((seg + 1) as Float / n_segments as Float, width0, width1);
let seg_normals = if !n.is_empty() {
Some(&n[seg..seg + 2])
} else {
None
};
let new_curves = create_curve(
render_from_object,
object_from_render,
reverse_orientation,
&seg_cp_bezier,
w0,
w1,
curve_type,
seg_normals.expect("Could not determine normals to curve segments"),
split_depth.try_into().unwrap(),
);
curves.extend(new_curves);
}
// arena.alloc(curves);
Ok(curves)
}
}