206 lines
6.9 KiB
Rust
206 lines
6.9 KiB
Rust
use crate::core::shape::CreateShape;
|
|
use crate::core::texture::FloatTexture;
|
|
use crate::{Arena, FileLoc, ParameterDictionary};
|
|
use anyhow::{anyhow, Result};
|
|
use log::warn;
|
|
use shared::core::geometry::{Normal3f, Point3f};
|
|
use shared::core::shape::Shape;
|
|
use shared::shapes::{CurveCommon, CurveShape, CurveType};
|
|
use shared::utils::math::lerp;
|
|
use shared::utils::splines::{
|
|
cubic_bspline_to_bezier, elevate_quadratic_bezier_to_cubic, quadratic_bspline_to_bezier,
|
|
};
|
|
use shared::Float;
|
|
use shared::{Ptr, Transform};
|
|
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,
|
|
arena: &Arena,
|
|
) -> Vec<Ptr<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<Ptr<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(arena.alloc(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<Ptr<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<Ptr<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(),
|
|
arena,
|
|
);
|
|
|
|
curves.extend(new_curves);
|
|
}
|
|
Ok(curves)
|
|
}
|
|
}
|