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 { 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 = 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>, _loc: FileLoc, _arena: &Arena, ) -> Result> { 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 = 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) } }