@@ -2226,68 +2226,72 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
22262226 let subpath_anchors = anchor_count ( control_bezpath) ;
22272227 let max_content_index = content. len ( ) . saturating_sub ( 1 ) ;
22282228
2229- // Compute per-segment arc lengths for spatial positioning along the control path
2230- let segment_lengths: Vec < f64 > = control_bezpath. segments ( ) . map ( |seg| seg. perimeter ( DEFAULT_ACCURACY ) ) . collect ( ) ;
2231-
2232- // Compute segment weights based on the user's chosen spacing metric
2233- let segment_weights: Vec < f64 > = match distribution {
2234- InterpolationDistribution :: Objects => vec ! [ 1. ; segment_count] ,
2235- InterpolationDistribution :: Distances => segment_lengths. clone ( ) ,
2236- InterpolationDistribution :: Angles | InterpolationDistribution :: Sizes | InterpolationDistribution :: Slants => ( 0 ..segment_count)
2237- . map ( |i| {
2238- let source_index = ( content_offset + i) . min ( max_content_index) ;
2239- let target_index = if is_closed && i >= subpath_anchors - 1 {
2240- content_offset
2241- } else {
2242- ( content_offset + i + 1 ) . min ( max_content_index)
2243- } ;
2229+ // Map the fractional progression to a segment index and local blend time using the chosen weights.
2230+ let ( local_source_index, time) = if fractional_progression >= 1. {
2231+ ( segment_count - 1 , 1. )
2232+ } else if matches ! ( distribution, InterpolationDistribution :: Objects ) {
2233+ // Fast path for uniform distribution: direct index calculation without allocation or iteration
2234+ let scaled = fractional_progression * segment_count as f64 ;
2235+ let index = ( scaled. ceil ( ) as usize ) . saturating_sub ( 1 ) ;
2236+ ( index, scaled - index as f64 )
2237+ } else {
2238+ // Compute segment weights based on the user's chosen spacing metric
2239+ let segment_weights: Vec < f64 > = match distribution {
2240+ InterpolationDistribution :: Objects => unreachable ! ( ) ,
2241+ InterpolationDistribution :: Distances => control_bezpath. segments ( ) . map ( |seg| seg. perimeter ( DEFAULT_ACCURACY ) ) . collect ( ) ,
2242+ InterpolationDistribution :: Angles | InterpolationDistribution :: Sizes | InterpolationDistribution :: Slants => ( 0 ..segment_count)
2243+ . map ( |i| {
2244+ let source_index = ( content_offset + i) . min ( max_content_index) ;
2245+ let target_index = if is_closed && i >= subpath_anchors - 1 {
2246+ content_offset
2247+ } else {
2248+ ( content_offset + i + 1 ) . min ( max_content_index)
2249+ } ;
22442250
2245- let ( Some ( source) , Some ( target) ) = ( content. get ( source_index) , content. get ( target_index) ) else {
2246- return 0. ;
2247- } ;
2248- let ( s_angle, s_scale, s_skew) = source. transform . decompose_rotation_scale_skew ( ) ;
2249- let ( t_angle, t_scale, t_skew) = target. transform . decompose_rotation_scale_skew ( ) ;
2250-
2251- match distribution {
2252- InterpolationDistribution :: Angles => {
2253- let mut diff = t_angle - s_angle;
2254- if diff > PI {
2255- diff -= TAU ;
2256- } else if diff < -PI {
2257- diff += TAU ;
2251+ let ( Some ( source) , Some ( target) ) = ( content. get ( source_index) , content. get ( target_index) ) else {
2252+ return 0. ;
2253+ } ;
2254+ let ( s_angle, s_scale, s_skew) = source. transform . decompose_rotation_scale_skew ( ) ;
2255+ let ( t_angle, t_scale, t_skew) = target. transform . decompose_rotation_scale_skew ( ) ;
2256+
2257+ match distribution {
2258+ InterpolationDistribution :: Angles => {
2259+ let mut diff = t_angle - s_angle;
2260+ if diff > PI {
2261+ diff -= TAU ;
2262+ } else if diff < -PI {
2263+ diff += TAU ;
2264+ }
2265+ diff. abs ( )
22582266 }
2259- diff. abs ( )
2267+ InterpolationDistribution :: Sizes => ( t_scale - s_scale) . length ( ) ,
2268+ InterpolationDistribution :: Slants => ( t_skew. atan ( ) - s_skew. atan ( ) ) . abs ( ) ,
2269+ _ => unreachable ! ( ) ,
22602270 }
2261- InterpolationDistribution :: Sizes => ( t_scale - s_scale) . length ( ) ,
2262- InterpolationDistribution :: Slants => ( t_skew. atan ( ) - s_skew. atan ( ) ) . abs ( ) ,
2263- _ => unreachable ! ( ) ,
2264- }
2265- } )
2266- . collect ( ) ,
2267- } ;
2271+ } )
2272+ . collect ( ) ,
2273+ } ;
22682274
2269- let total_weight: f64 = segment_weights. iter ( ) . sum ( ) ;
2275+ let total_weight: f64 = segment_weights. iter ( ) . sum ( ) ;
22702276
2271- // Map the fractional progression to a segment index and local blend time using the chosen weights.
2272- // When all weights are zero (all elements identical in the chosen metric), there's zero interval to traverse.
2273- let ( local_source_index, time) = if total_weight <= f64:: EPSILON {
2274- ( 0 , 0. )
2275- } else if fractional_progression >= 1. {
2276- ( segment_count - 1 , 1. )
2277- } else {
2278- let mut accumulator = 0. ;
2279- let mut found_index = segment_count - 1 ;
2280- let mut found_t = 1. ;
2281- for ( i, weight) in segment_weights. iter ( ) . enumerate ( ) {
2282- let ratio = weight / total_weight;
2283- if fractional_progression <= accumulator + ratio {
2284- found_index = i;
2285- found_t = if ratio > f64:: EPSILON { ( fractional_progression - accumulator) / ratio } else { 0. } ;
2286- break ;
2277+ // When all weights are zero (all elements identical in the chosen metric), there's zero interval to traverse.
2278+ if total_weight <= f64:: EPSILON {
2279+ ( 0 , 0. )
2280+ } else {
2281+ let mut accumulator = 0. ;
2282+ let mut found_index = segment_count - 1 ;
2283+ let mut found_t = 1. ;
2284+ for ( i, weight) in segment_weights. iter ( ) . enumerate ( ) {
2285+ let ratio = weight / total_weight;
2286+ if fractional_progression <= accumulator + ratio {
2287+ found_index = i;
2288+ found_t = if ratio > f64:: EPSILON { ( fractional_progression - accumulator) / ratio } else { 0. } ;
2289+ break ;
2290+ }
2291+ accumulator += ratio;
22872292 }
2288- accumulator += ratio ;
2293+ ( found_index , found_t )
22892294 }
2290- ( found_index, found_t)
22912295 } ;
22922296
22932297 // Convert the blend time to a parametric t for evaluating spatial position on the control path
@@ -2312,11 +2316,6 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
23122316 let source_index = source_index. min ( max_content_index) ;
23132317 let target_index = target_index. min ( max_content_index) ;
23142318
2315- // At the end of an open subpath with no more interpolation needed, return the final element
2316- if !is_closed && time >= 1. && local_source_index >= subpath_anchors - 2 {
2317- return content. into_iter ( ) . nth ( target_index) . into_iter ( ) . collect ( ) ;
2318- }
2319-
23202319 // Use indexed access to borrow only the two rows we need, avoiding collecting the entire table
23212320 let ( Some ( source_row) , Some ( target_row) ) = ( content. get ( source_index) , content. get ( target_index) ) else {
23222321 return content;
@@ -2373,16 +2372,17 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
23732372 }
23742373 }
23752374
2376- // Fast path: when exactly at the source object, clone its geometry directly instead of
2377- // extracting manipulator groups, subdividing, interpolating, and rebuilding the vector.
2378- if time == 0. {
2379- let mut vector = source_row. element . clone ( ) ;
2380- vector. upstream_data = Some ( graphic_table_content) ;
2381-
2375+ // Fast path: when exactly at either endpoint, clone the corresponding geometry directly
2376+ // instead of extracting manipulator groups, subdividing, interpolating, and rebuilding.
2377+ if time == 0. || time == 1. {
2378+ let row = if time == 0. { source_row } else { target_row } ;
23822379 return Table :: new_from_row ( TableRow {
2383- element : vector,
2380+ element : Vector {
2381+ upstream_data : Some ( graphic_table_content) ,
2382+ ..row. element . clone ( )
2383+ } ,
2384+ alpha_blending : * row. alpha_blending ,
23842385 transform : lerped_transform,
2385- alpha_blending : * source_row. alpha_blending ,
23862386 ..Default :: default ( )
23872387 } ) ;
23882388 }
0 commit comments