Skip to main content

iridia_exec/
tessellator.rs

1//! The Direct3D 11 fixed-function tessellator: from the tessellation factors of a patch, the points of its domain and
2//! how they connect.
3//!
4//! This is a port to Rust of the reference tessellator Microsoft published with the Direct3D 11.3 functional
5//! specification (`CHWTessellator` of `tessellator.cpp`, "Microsoft D3D11 Fixed Function Tessellator Reference - May 7,
6//! 2012", in <https://github.com/microsoft/DirectX-Specs>, `d3d/archive/images/d3d11/`), whose repository licenses its
7//! code under the MIT License:
8//!
9//! > The MIT License (MIT)
10//! > Copyright (c) Microsoft Corporation
11//! >
12//! > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13//! > documentation files (the "Software"), to deal in the Software without restriction, including without limitation
14//! > the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
15//! > permit persons to whom the Software is furnished to do so, subject to the following conditions:
16//! >
17//! > The above copyright notice and this permission notice shall be included in all copies or substantial portions of
18//! > the Software.
19//! >
20//! > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
21//! > THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22//! > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23//! > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24//! > SOFTWARE.
25//!
26//! The structure follows the reference so that the two can be read side by side: the domain locations are 16.16
27//! fixed-point numbers converted to `f32` at the end, the points of the outermost ring come first (edge by edge), the
28//! inner rings follow, spiralling in, and the triangles stitch ring to ring, with the "ruler function" order in which
29//! the points of an edge appear as its tessellation factor grows. What is checked against it (and against the
30//! Direct3D 11 reference device of WARP) is in the tests.
31
32/// How the tessellator spaces the segments along an edge (`partitioning(...)` of the hull program).
33pub use iridia::Partitioning;
34/// What the tessellator emits.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum OutputPrimitive {
37    /// Points.
38    Point,
39    /// Lines.
40    Line,
41    /// Triangles wound clockwise.
42    TriangleCw,
43    /// Triangles wound counter-clockwise.
44    TriangleCcw,
45}
46
47/// The tessellation factors of a patch: the edges' (a triangle uses 3, a quad 4, an isoline the line density then the
48/// line detail) and the inside ones (a triangle 1, a quad 2: the factor along u, then along v).
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct Factors {
51    /// The edge factors.
52    pub edges: [f32; 4],
53    /// The inside factors.
54    pub inside: [f32; 2],
55}
56
57/// The points and connections of a tessellated patch: `points` are (u, v) domain locations (a triangle's third
58/// barycentric coordinate is `1 - u - v`), `indices` are points by their number, as the points of a point list, the
59/// ends of lines of a line list, or the corners of triangles of a triangle list, as the output primitive says.
60#[derive(Clone, Debug, Default, PartialEq)]
61pub struct Tessellation {
62    /// The domain locations (u, v) of the points.
63    pub points: Vec<[f32; 2]>,
64    /// Point numbers into `points`, in groups of the output primitive's size.
65    pub indices: Vec<u32>,
66}
67
68/// Tessellate a triangle patch.
69pub fn tessellate_triangle(
70    partitioning: Partitioning,
71    output: OutputPrimitive,
72    factors: Factors,
73) -> Tessellation {
74    let mut t = Tessellator::new(partitioning, output);
75    t.triangle(
76        factors.edges[0],
77        factors.edges[1],
78        factors.edges[2],
79        factors.inside[0],
80    );
81    t.finish()
82}
83
84/// Tessellate a quad patch.
85pub fn tessellate_quad(
86    partitioning: Partitioning,
87    output: OutputPrimitive,
88    factors: Factors,
89) -> Tessellation {
90    let mut t = Tessellator::new(partitioning, output);
91    t.quad(
92        factors.edges[0],
93        factors.edges[1],
94        factors.edges[2],
95        factors.edges[3],
96        factors.inside[0],
97        factors.inside[1],
98    );
99    t.finish()
100}
101
102/// Tessellate an isoline patch: `density` lines (the first factor), each of `detail` segments (the second).
103pub fn tessellate_isolines(
104    partitioning: Partitioning,
105    output: OutputPrimitive,
106    density: f32,
107    detail: f32,
108) -> Tessellation {
109    let mut t = Tessellator::new(partitioning, output);
110    t.isolines(density, detail);
111    t.finish()
112}
113
114const MIN_ODD: f32 = 1.0;
115const MAX_ODD: f32 = 63.0;
116const MIN_EVEN: f32 = 2.0;
117const MAX_EVEN: f32 = 64.0;
118const MIN_ISOLINE_DENSITY: f32 = 1.0;
119const MAX_ISOLINE_DENSITY: f32 = 64.0;
120/// 2^-16, the smallest positive fixed-point fraction.
121const EPSILON: f32 = 1.0 / 65536.0;
122const MAX_FACTOR: usize = 64;
123const MAX_POINTS: usize = (MAX_FACTOR + 1) * (MAX_FACTOR + 1);
124const MAX_INDICES: usize = MAX_FACTOR * MAX_FACTOR * 2 * 3;
125
126/// A 16.16 fixed-point number (15 integer bits and 16 fraction bits, unsigned).
127type Fxp = u32;
128const FRACTION_BITS: u32 = 16;
129const FRACTION_MASK: Fxp = 0x0000_ffff;
130const INTEGER_MASK: Fxp = 0x7fff_0000;
131const FXP_ONE: Fxp = 1 << FRACTION_BITS;
132const FXP_ONE_THIRD: Fxp = 0x0000_5555;
133const FXP_TWO_THIRDS: Fxp = 0x0000_aaaa;
134const FXP_ONE_HALF: Fxp = 0x0000_8000;
135/// 1 / n in 16.16 for n in 1..=64 (entry 0 is unused).
136const FIXED_RECIPROCAL: [Fxp; MAX_FACTOR + 1] = [
137    0xffff_ffff,
138    0x10000,
139    0x8000,
140    0x5555,
141    0x4000,
142    0x3333,
143    0x2aab,
144    0x2492,
145    0x2000,
146    0x1c72,
147    0x199a,
148    0x1746,
149    0x1555,
150    0x13b1,
151    0x1249,
152    0x1111,
153    0x1000,
154    0xf0f,
155    0xe39,
156    0xd79,
157    0xccd,
158    0xc31,
159    0xba3,
160    0xb21,
161    0xaab,
162    0xa3d,
163    0x9d9,
164    0x97b,
165    0x925,
166    0x8d4,
167    0x889,
168    0x842,
169    0x800,
170    0x7c2,
171    0x788,
172    0x750,
173    0x71c,
174    0x6eb,
175    0x6bd,
176    0x690,
177    0x666,
178    0x63e,
179    0x618,
180    0x5f4,
181    0x5d1,
182    0x5b0,
183    0x591,
184    0x572,
185    0x555,
186    0x539,
187    0x51f,
188    0x505,
189    0x4ec,
190    0x4d5,
191    0x4be,
192    0x4a8,
193    0x492,
194    0x47e,
195    0x46a,
196    0x457,
197    0x444,
198    0x432,
199    0x421,
200    0x410,
201    0x400,
202];
203
204/// The reference's Direct3D-compliant float helpers: denormals flush, NaN is passed over.
205fn is_nan(a: f32) -> bool {
206    a.is_nan()
207}
208fn flush(a: f32) -> f32 {
209    if a.to_bits() & 0x7fff_ffff < 0x0080_0000 {
210        f32::from_bits(a.to_bits() & 0x8000_0000)
211    } else {
212        a
213    }
214}
215/// Whether an edge factor culls the patch: not above 0 (a NaN too, as the reference's `!(factor > 0)`).
216fn culls(factor: f32) -> bool {
217    factor.is_nan() || factor <= 0.0
218}
219/// IEEE 754R min.
220fn fmin(a: f32, b: f32) -> f32 {
221    let (fa, fb) = (flush(a), flush(b));
222    if is_nan(fb) {
223        a
224    } else if fa == 0.0 && fb == 0.0 {
225        if fa.to_bits() & 0x8000_0000 != 0 { a } else { b }
226    } else if fa < fb {
227        a
228    } else {
229        b
230    }
231}
232/// IEEE 754R max.
233fn fmax(a: f32, b: f32) -> f32 {
234    let (fa, fb) = (flush(a), flush(b));
235    if is_nan(fb) {
236        a
237    } else if fa == 0.0 && fb == 0.0 {
238        if fb.to_bits() & 0x8000_0000 != 0 { a } else { b }
239    } else if fa >= fb {
240        a
241    } else {
242        b
243    }
244}
245
246/// `floatToFixed`: a float to 16.16 unsigned fixed point, to nearest even, NaN and negative to 0, saturating.
247#[allow(clippy::arithmetic_side_effects)] // bit-field arithmetic on the float's own exponent and fraction, whose ranges the comparisons above bound
248fn float_to_fixed(input: f32) -> Fxp {
249    const BIAS: i32 = 127;
250    const HIDDEN: i32 = 1 << 23;
251    const MANTISSA_MASK: i32 = HIDDEN - 1;
252    const EXPONENT_MASK: i32 = 0xff << 23;
253    const SIGN: i32 = i32::MIN;
254    // the largest positive value as a float's bits: 2^15 (the fraction needs more than the float's 24 bits of
255    // precision to reach below it, so nothing is subtracted)
256    const MAX_POSITIVE: i32 = (BIAS + 15) << 23;
257    let mut output = input.to_bits() as i32;
258    let unbiased = ((output & EXPONENT_MASK) >> 23) - BIAS;
259    let negative = output & SIGN != 0;
260    if unbiased == BIAS + 1 && output & MANTISSA_MASK != 0 {
261        output = 0;
262    } else if !negative && output >= MAX_POSITIVE {
263        output = -1;
264    } else if negative {
265        // every negative value is below the smallest unsigned one
266        output = 0;
267    } else if unbiased < -(FRACTION_BITS as i32) - 1 {
268        output = 0;
269    } else {
270        output = (output & MANTISSA_MASK) | HIDDEN;
271        let extra = 23 - FRACTION_BITS as i32 - unbiased;
272        if extra >= 0 {
273            let lsb = 1i32 << extra;
274            let extra_mask = lsb - 1;
275            let half = lsb >> 1;
276            if output & lsb != 0 || (output & extra_mask) > half {
277                output += half;
278            }
279            output >>= extra;
280        } else {
281            output <<= -extra;
282        }
283    }
284    output as Fxp
285}
286
287fn fixed_to_float(input: Fxp) -> f32 {
288    (input >> FRACTION_BITS) as f32 + (input & FRACTION_MASK) as f32 / (1u32 << FRACTION_BITS) as f32
289}
290
291#[allow(clippy::cast_possible_truncation)] // the float is a clamped factor, at most 64
292fn is_even(input: f32) -> bool {
293    (input as i32) & 1 == 0
294}
295
296fn fxp_ceil(input: Fxp) -> Fxp {
297    if input & FRACTION_MASK != 0 {
298        (input & INTEGER_MASK).wrapping_add(FXP_ONE)
299    } else {
300        input
301    }
302}
303
304fn fxp_floor(input: Fxp) -> Fxp {
305    input & INTEGER_MASK
306}
307
308/// The most significant set bit removed (0 stays 0).
309#[allow(clippy::arithmetic_side_effects)] // the argument is a positive i32; the shift is by a leading-zeros count below 32
310fn remove_msb(val: i32) -> i32 {
311    if val <= 0 {
312        0
313    } else {
314        val & !(1 << (31 - val.leading_zeros()))
315    }
316}
317
318#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319enum Parity {
320    Even,
321    Odd,
322}
323
324#[derive(Clone, Copy, Debug, Default)]
325struct FactorContext {
326    inv_segments_on_floor: Fxp,
327    inv_segments_on_ceil: Fxp,
328    half_fraction: Fxp,
329    num_half_points: i32,
330    split_point_on_floor: i32,
331}
332
333/// A row of points to stitch: the number of its first point, how many points are on half of its edge, its parity.
334#[derive(Clone, Copy)]
335struct Row {
336    base: i32,
337    half_points: i32,
338    parity: Parity,
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Eq)]
342enum Diagonals {
343    InsideToOutside,
344    InsideToOutsideExceptMiddle,
345    Mirrored,
346}
347
348#[derive(Clone, Copy, Debug, Default)]
349struct IndexPatch {
350    inside_delta: i32,
351    inside_bad: i32,
352    inside_replacement: i32,
353    outside_base: i32,
354    outside_delta: i32,
355    outside_bad: i32,
356    outside_replacement: i32,
357}
358
359#[derive(Clone, Copy, Debug, Default)]
360struct IndexPatch2 {
361    base_to_invert: i32,
362    inversion_end: i32,
363    corner_bad: i32,
364    corner_replacement: i32,
365}
366
367struct Tessellator {
368    original_parity: Parity,
369    parity: Parity,
370    original_partitioning: Partitioning,
371    partitioning: Partitioning,
372    output: OutputPrimitive,
373    points: Vec<[f32; 2]>,
374    index: Vec<i32>,
375    num_points: i32,
376    num_indices: i32,
377    using_patch: bool,
378    using_patch2: bool,
379    patch: IndexPatch,
380    patch2: IndexPatch2,
381}
382
383struct Quad {
384    outside: [Fxp; 4],
385    inside: [Fxp; 2],
386    outside_parity: [Parity; 4],
387    inside_parity: [Parity; 2],
388    outside_ctx: [FactorContext; 4],
389    inside_ctx: [FactorContext; 2],
390    just_minimum: bool,
391    culled: bool,
392    points_for_outside_edge: [i32; 4],
393    points_for_inside: [i32; 2],
394    inside_edge_point_base: i32,
395}
396
397struct Tri {
398    outside: [Fxp; 3],
399    inside: Fxp,
400    outside_parity: [Parity; 3],
401    inside_parity: Parity,
402    outside_ctx: [FactorContext; 3],
403    inside_ctx: FactorContext,
404    just_minimum: bool,
405    culled: bool,
406    points_for_outside_edge: [i32; 3],
407    points_for_inside: i32,
408    inside_edge_point_base: i32,
409}
410
411struct Isolines {
412    line_density_parity: Parity,
413    line_detail_parity: Parity,
414    line_density_ctx: FactorContext,
415    line_detail_ctx: FactorContext,
416    culled: bool,
417    points_per_line: i32,
418    lines: i32,
419}
420
421impl Tessellator {
422    fn new(partitioning: Partitioning, output: OutputPrimitive) -> Self {
423        let parity = match partitioning {
424            Partitioning::FractionalOdd => Parity::Odd,
425            // integer and pow2 take either, per factor; the reference leaves the initial parity at even
426            _ => Parity::Even,
427        };
428        Self {
429            original_parity: parity,
430            parity,
431            original_partitioning: partitioning,
432            partitioning,
433            output,
434            points: vec![[0.0; 2]; MAX_POINTS],
435            index: vec![0; MAX_INDICES],
436            num_points: 0,
437            num_indices: 0,
438            using_patch: false,
439            using_patch2: false,
440            patch: IndexPatch::default(),
441            patch2: IndexPatch2::default(),
442        }
443    }
444
445    #[allow(clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
446    fn finish(mut self) -> Tessellation {
447        self.points.truncate(self.num_points.max(0) as usize);
448        Tessellation {
449            points: self.points,
450            indices: self.index[..self.num_indices.max(0) as usize]
451                .iter()
452                .map(|&i| i as u32)
453                .collect(),
454        }
455    }
456
457    fn odd(&self) -> bool {
458        self.parity == Parity::Odd
459    }
460
461    /// Hardware does not tell integer from pow2 partitioning: both round to the next integer.
462    fn hw_integer(&self) -> bool {
463        matches!(self.partitioning, Partitioning::Integer | Partitioning::Pow2)
464    }
465
466    /// The factor range the original partitioning allows.
467    fn bounds(&self) -> (f32, f32) {
468        match self.original_partitioning {
469            Partitioning::Integer | Partitioning::Pow2 => (MIN_ODD, MAX_EVEN),
470            Partitioning::FractionalEven => (MIN_EVEN, MAX_EVEN),
471            Partitioning::FractionalOdd => (MIN_ODD, MAX_ODD),
472        }
473    }
474
475    #[allow(clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
476    fn define_point(&mut self, u: Fxp, v: Fxp, at: i32) {
477        self.points[at as usize] = [fixed_to_float(u), fixed_to_float(v)];
478    }
479
480    #[allow(clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
481    fn define_index(&mut self, index: i32, at: i32) {
482        self.index[at as usize] = self.patch_index_value(index);
483    }
484
485    /// A triangle given clockwise, stored clockwise or counter-clockwise as the output wants.
486    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
487    fn define_clockwise_triangle(&mut self, i0: i32, i1: i32, i2: i32, at: i32) {
488        self.define_index(i0, at);
489        if self.output == OutputPrimitive::TriangleCw {
490            self.define_index(i1, at + 1);
491            self.define_index(i2, at + 2);
492        } else {
493            self.define_index(i2, at + 1);
494            self.define_index(i1, at + 2);
495        }
496    }
497
498    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
499    fn dump_all_points(&mut self) {
500        for p in 0..self.num_points {
501            let at = self.num_indices;
502            self.num_indices += 1;
503            self.define_index(p, at);
504        }
505    }
506
507    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
508    fn dump_all_points_as_in_order_line_list(&mut self) {
509        for p in 1..self.num_points {
510            for index in [p - 1, p] {
511                let at = self.num_indices;
512                self.num_indices += 1;
513                self.define_index(index, at);
514            }
515        }
516    }
517
518    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
519    fn num_points_for_factor(&self, fxp: Fxp) -> i32 {
520        if self.odd() {
521            (fxp_ceil(FXP_ONE_HALF.wrapping_add(fxp.wrapping_add(1) / 2)).wrapping_mul(2) >> FRACTION_BITS)
522                as i32
523        } else {
524            ((fxp_ceil(fxp.wrapping_add(1) / 2).wrapping_mul(2) >> FRACTION_BITS) + 1) as i32
525        }
526    }
527
528    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
529    fn factor_context(&self, fxp: Fxp) -> FactorContext {
530        let mut half = fxp.wrapping_add(1) / 2;
531        if self.odd() || half == FXP_ONE_HALF {
532            // a factor of 1 is treated as even
533            half = half.wrapping_add(FXP_ONE_HALF);
534        }
535        let floor_half = fxp_floor(half);
536        let ceil_half = fxp_ceil(half);
537        let half_fraction = half - floor_half;
538        let num_half_points = (ceil_half >> FRACTION_BITS) as i32;
539        let split_point_on_floor = if ceil_half == floor_half {
540            // a value that causes the split to be ignored
541            num_half_points + 1
542        } else if self.odd() {
543            if floor_half == FXP_ONE {
544                0
545            } else {
546                (remove_msb(((floor_half >> FRACTION_BITS) as i32) - 1) << 1) + 1
547            }
548        } else {
549            (remove_msb((floor_half >> FRACTION_BITS) as i32) << 1) + 1
550        };
551        let mut floor_segments = ((floor_half.wrapping_mul(2)) >> FRACTION_BITS) as usize;
552        let mut ceil_segments = ((ceil_half.wrapping_mul(2)) >> FRACTION_BITS) as usize;
553        if self.odd() {
554            floor_segments -= 1;
555            ceil_segments -= 1;
556        }
557        FactorContext {
558            inv_segments_on_floor: FIXED_RECIPROCAL[floor_segments],
559            inv_segments_on_ceil: FIXED_RECIPROCAL[ceil_segments],
560            half_fraction,
561            num_half_points,
562            split_point_on_floor,
563        }
564    }
565
566    /// Where point `point` of an edge is, in 16.16: points are introduced in ruler-function order as the factor
567    /// grows, and an edge is mirrored about its middle.
568    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
569    fn place_point_in_1d(&self, ctx: &FactorContext, point: i32) -> Fxp {
570        let mut point = point;
571        let flip = point >= ctx.num_half_points;
572        if flip {
573            point = (ctx.num_half_points << 1) - point;
574            if self.odd() {
575                point -= 1;
576            }
577        }
578        if point == ctx.num_half_points {
579            return FXP_ONE_HALF;
580        }
581        let on_ceil = point as u32;
582        let mut on_floor = on_ceil;
583        if point > ctx.split_point_on_floor {
584            on_floor = on_floor.wrapping_sub(1);
585        }
586        let location_on_floor = on_floor.wrapping_mul(ctx.inv_segments_on_floor);
587        let location_on_ceil = on_ceil.wrapping_mul(ctx.inv_segments_on_ceil);
588        let mut location = location_on_floor
589            .wrapping_mul(FXP_ONE.wrapping_sub(ctx.half_fraction))
590            .wrapping_add(location_on_ceil.wrapping_mul(ctx.half_fraction));
591        location = location.wrapping_add(FXP_ONE_HALF) >> FRACTION_BITS;
592        if flip {
593            location = FXP_ONE.wrapping_sub(location);
594        }
595        location
596    }
597
598    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
599    fn patch_index_value(&self, mut index: i32) -> i32 {
600        if self.using_patch {
601            let p = &self.patch;
602            if index >= p.outside_base {
603                if index == p.outside_bad {
604                    index = p.outside_replacement;
605                } else {
606                    index += p.outside_delta;
607                }
608            } else if index == p.inside_bad {
609                index = p.inside_replacement;
610            } else {
611                index += p.inside_delta;
612            }
613        } else if self.using_patch2 {
614            let p = &self.patch2;
615            if index >= p.base_to_invert {
616                if index == p.corner_bad {
617                    index = p.corner_replacement;
618                } else {
619                    index = p.inversion_end - index;
620                }
621            } else if index == p.corner_bad {
622                index = p.corner_replacement;
623            }
624        }
625        index
626    }
627
628    /// Two parallel rows of points stitched as the same number of points on each side (a trapezoid at each end when
629    /// `trapezoid`), with the diagonals as `diagonals` says.
630    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
631    fn stitch_regular(
632        &mut self,
633        trapezoid: bool,
634        diagonals: Diagonals,
635        mut base: i32,
636        inside_points: i32,
637        inside_base: i32,
638        outside_base: i32,
639    ) {
640        let mut inside = inside_base;
641        let mut outside = outside_base;
642        if trapezoid {
643            self.define_clockwise_triangle(outside, outside + 1, inside, base);
644            base += 3;
645            outside += 1;
646        }
647        let mut p;
648        match diagonals {
649            Diagonals::InsideToOutside => {
650                for _ in 0..inside_points - 1 {
651                    self.define_clockwise_triangle(inside, outside, outside + 1, base);
652                    base += 3;
653                    self.define_clockwise_triangle(inside, outside + 1, inside + 1, base);
654                    base += 3;
655                    inside += 1;
656                    outside += 1;
657                }
658            }
659            Diagonals::InsideToOutsideExceptMiddle => {
660                // first half
661                p = 0;
662                while p < inside_points / 2 - 1 {
663                    self.define_clockwise_triangle(outside, outside + 1, inside, base);
664                    base += 3;
665                    self.define_clockwise_triangle(inside, outside + 1, inside + 1, base);
666                    base += 3;
667                    inside += 1;
668                    outside += 1;
669                    p += 1;
670                }
671                // middle
672                self.define_clockwise_triangle(outside, inside + 1, inside, base);
673                base += 3;
674                self.define_clockwise_triangle(outside, outside + 1, inside + 1, base);
675                base += 3;
676                inside += 1;
677                outside += 1;
678                p += 2;
679                // second half
680                while p < inside_points {
681                    self.define_clockwise_triangle(outside, outside + 1, inside, base);
682                    base += 3;
683                    self.define_clockwise_triangle(inside, outside + 1, inside + 1, base);
684                    base += 3;
685                    inside += 1;
686                    outside += 1;
687                    p += 1;
688                }
689            }
690            Diagonals::Mirrored => {
691                // first half, diagonals from the outside of the outside edge to the inside of the inside edge
692                p = 0;
693                while p < inside_points / 2 {
694                    self.define_clockwise_triangle(outside, inside + 1, inside, base);
695                    base += 3;
696                    self.define_clockwise_triangle(outside, outside + 1, inside + 1, base);
697                    base += 3;
698                    inside += 1;
699                    outside += 1;
700                    p += 1;
701                }
702                // second half, diagonals from the inside of the inside edge to the outside of the outside edge
703                while p < inside_points - 1 {
704                    self.define_clockwise_triangle(inside, outside, outside + 1, base);
705                    base += 3;
706                    self.define_clockwise_triangle(inside, outside + 1, inside + 1, base);
707                    base += 3;
708                    inside += 1;
709                    outside += 1;
710                    p += 1;
711                }
712            }
713        }
714        if trapezoid {
715            self.define_clockwise_triangle(outside, outside + 1, inside, base);
716        }
717    }
718
719    /// Two parallel rows of points with different factors stitched, in the order the points of an edge appear as its
720    /// factor grows (the ruler function), walking in from one end to the middle and back out of it.
721    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
722    fn stitch_transition(&mut self, mut base: i32, inside: Row, outside: Row) {
723        // where point i of the half edge ends up on the half edge at the largest tessellation
724        const FINAL_POINT_POSITION: [i32; 33] = [
725            0, 32, 16, 8, 17, 4, 18, 9, 19, 2, 20, 10, 21, 5, 22, 11, 23, 1, 24, 12, 25, 6, 26, 13, 27, 3,
726            28, 14, 29, 7, 30, 15, 31,
727        ];
728        // the first entry other than 0 of the table above under the half factor, and the last one
729        const LOOP_START: [i32; 33] = [
730            1, 1, 17, 9, 9, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
731            2,
732        ];
733        const LOOP_END: [i32; 33] = [
734            0, 0, 17, 17, 25, 25, 25, 25, 29, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 31, 31, 31,
735            31, 31, 31, 31, 31, 31, 31, 32,
736        ];
737        let (inside_parity, outside_parity) = (inside.parity, outside.parity);
738        let inside_half_points = inside.half_points - i32::from(inside_parity == Parity::Odd);
739        let outside_half_points = outside.half_points - i32::from(outside_parity == Parity::Odd);
740        let mut outside = outside.base;
741        let mut inside = inside.base;
742        let start = LOOP_START[inside_half_points as usize].min(LOOP_START[outside_half_points as usize]);
743        let end = LOOP_END[inside_half_points as usize].max(LOOP_END[outside_half_points as usize]);
744
745        // walk the first half
746        if FINAL_POINT_POSITION[0] < outside_half_points {
747            // advance the outside
748            self.define_clockwise_triangle(outside, outside + 1, inside, base);
749            base += 3;
750            outside += 1;
751        }
752        for i in start..=end {
753            if FINAL_POINT_POSITION[i as usize] < inside_half_points {
754                self.define_clockwise_triangle(inside, outside, inside + 1, base);
755                base += 3;
756                inside += 1;
757            }
758            if FINAL_POINT_POSITION[i as usize] < outside_half_points {
759                self.define_clockwise_triangle(outside, outside + 1, inside, base);
760                base += 3;
761                outside += 1;
762            }
763        }
764        if inside_parity != outside_parity || inside_parity == Parity::Odd {
765            if inside_parity == outside_parity {
766                // a quad in the middle
767                self.define_clockwise_triangle(inside, outside, inside + 1, base);
768                base += 3;
769                self.define_clockwise_triangle(inside + 1, outside, outside + 1, base);
770                base += 3;
771                inside += 1;
772                outside += 1;
773            } else if inside_parity == Parity::Even {
774                // a triangle pointing inside
775                self.define_clockwise_triangle(inside, outside, outside + 1, base);
776                base += 3;
777                outside += 1;
778            } else {
779                // a triangle pointing outside
780                self.define_clockwise_triangle(inside, outside, inside + 1, base);
781                base += 3;
782                inside += 1;
783            }
784        }
785        // walk the second half
786        for i in (start..=end).rev() {
787            if FINAL_POINT_POSITION[i as usize] < outside_half_points {
788                self.define_clockwise_triangle(outside, outside + 1, inside, base);
789                base += 3;
790                outside += 1;
791            }
792            if FINAL_POINT_POSITION[i as usize] < inside_half_points {
793                self.define_clockwise_triangle(inside, outside, inside + 1, base);
794                base += 3;
795                inside += 1;
796            }
797        }
798        if FINAL_POINT_POSITION[0] < outside_half_points {
799            self.define_clockwise_triangle(outside, outside + 1, inside, base);
800        }
801    }
802
803    // --- quads ---
804
805    fn quad(&mut self, u0: f32, v0: f32, u1: f32, v1: f32, inside_u: f32, inside_v: f32) {
806        let q = self.quad_process(u0, v0, u1, v1, inside_u, inside_v);
807        if q.culled {
808            self.num_points = 0;
809            self.num_indices = 0;
810            return;
811        }
812        if q.just_minimum {
813            self.define_point(0, 0, 0);
814            self.define_point(FXP_ONE, 0, 1);
815            self.define_point(FXP_ONE, FXP_ONE, 2);
816            self.define_point(0, FXP_ONE, 3);
817            self.num_points = 4;
818            match self.output {
819                OutputPrimitive::TriangleCw | OutputPrimitive::TriangleCcw => {
820                    self.define_clockwise_triangle(0, 1, 3, 0);
821                    self.define_clockwise_triangle(1, 2, 3, 3);
822                    self.num_indices = 6;
823                }
824                OutputPrimitive::Point => self.dump_all_points(),
825                OutputPrimitive::Line => self.dump_all_points_as_in_order_line_list(),
826            }
827            return;
828        }
829        self.quad_generate_points(&q);
830        match self.output {
831            OutputPrimitive::Point => self.dump_all_points(),
832            OutputPrimitive::Line => self.dump_all_points_as_in_order_line_list(),
833            _ => self.quad_generate_connectivity(&q),
834        }
835    }
836
837    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
838    fn quad_process(
839        &mut self,
840        mut u0: f32,
841        mut v0: f32,
842        mut u1: f32,
843        mut v1: f32,
844        mut in_u: f32,
845        mut in_v: f32,
846    ) -> Quad {
847        let mut q = Quad {
848            outside: [0; 4],
849            inside: [0; 2],
850            outside_parity: [Parity::Even; 4],
851            inside_parity: [Parity::Even; 2],
852            outside_ctx: [FactorContext::default(); 4],
853            inside_ctx: [FactorContext::default(); 2],
854            just_minimum: false,
855            culled: false,
856            points_for_outside_edge: [0; 4],
857            points_for_inside: [0; 2],
858            inside_edge_point_base: 0,
859        };
860        // a culled patch: any edge factor not above 0 (NaN too)
861        if culls(u0) || culls(v0) || culls(u1) || culls(v1) {
862            q.culled = true;
863            return q;
864        }
865        let (mut lower, upper) = self.bounds();
866        u0 = fmin(upper, fmax(lower, u0));
867        v0 = fmin(upper, fmax(lower, v0));
868        u1 = fmin(upper, fmax(lower, u1));
869        v1 = fmin(upper, fmax(lower, v1));
870        if self.hw_integer() {
871            u0 = u0.ceil();
872            v0 = v0.ceil();
873            u1 = u1.ceil();
874            v1 = v1.ceil();
875        }
876        if self.original_partitioning == Partitioning::FractionalOdd {
877            let limit = MIN_ODD + EPSILON / 2.0;
878            // if any factor ends up above 1 once it is fixed point, the inside factors must be too: a picture frame
879            if u0 > limit || v0 > limit || u1 > limit || v1 > limit || in_u > limit || in_v > limit {
880                lower = MIN_ODD + EPSILON;
881            }
882        }
883        in_u = fmin(upper, fmax(lower, in_u));
884        in_v = fmin(upper, fmax(lower, in_v));
885        if self.hw_integer() {
886            in_u = in_u.ceil();
887            in_v = in_v.ceil();
888        }
889        self.num_points = 0;
890        self.num_indices = 0;
891        let outside = [u0, v0, u1, v1];
892        let inside = [in_u, in_v];
893        if self.hw_integer() {
894            q.outside_parity = outside.map(|f| if is_even(f) { Parity::Even } else { Parity::Odd });
895            q.inside_parity = inside.map(|f| {
896                if is_even(f) || f == 1.0 {
897                    Parity::Even
898                } else {
899                    Parity::Odd
900                }
901            });
902        } else {
903            q.outside_parity = [self.original_parity; 4];
904            q.inside_parity = [self.original_parity; 2];
905        }
906        q.outside = outside.map(float_to_fixed);
907        q.inside = inside.map(float_to_fixed);
908        if self.hw_integer() || self.odd() {
909            // all factors 1
910            if q.inside.iter().chain(&q.outside).all(|&f| f == FXP_ONE) {
911                q.just_minimum = true;
912                return q;
913            }
914        }
915        q.just_minimum = false;
916        for e in 0..4 {
917            self.parity = q.outside_parity[e];
918            q.outside_ctx[e] = self.factor_context(q.outside[e]);
919        }
920        for a in 0..2 {
921            self.parity = q.inside_parity[a];
922            q.inside_ctx[a] = self.factor_context(q.inside[a]);
923        }
924        for e in 0..4 {
925            self.parity = q.outside_parity[e];
926            q.points_for_outside_edge[e] = self.num_points_for_factor(q.outside[e]);
927            self.num_points += q.points_for_outside_edge[e];
928        }
929        self.num_points -= 4;
930        for a in 0..2 {
931            self.parity = q.inside_parity[a];
932            q.points_for_inside[a] = self.num_points_for_factor(q.inside[a]);
933            let min = if q.inside_parity[a] == Parity::Odd { 4 } else { 3 };
934            // this allows degenerate transition regions when the inside factor is 1
935            q.points_for_inside[a] = min.max(q.points_for_inside[a]);
936        }
937        q.inside_edge_point_base = self.num_points;
938        self.num_points += (q.points_for_inside[0] - 2) * (q.points_for_inside[1] - 2);
939        q
940    }
941
942    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
943    fn quad_generate_points(&mut self, q: &Quad) {
944        // the exterior ring's edge points, clockwise from the top left
945        let mut offset = 0;
946        for edge in 0..4usize {
947            let parity = edge & 1;
948            let end = q.points_for_outside_edge[edge] - 1;
949            for p in 0..end {
950                let n = if edge == 1 || edge == 2 { p } else { end - p };
951                self.parity = q.outside_parity[edge];
952                let param = self.place_point_in_1d(&q.outside_ctx[edge], n);
953                if parity != 0 {
954                    self.define_point(param, if edge == 3 { FXP_ONE } else { 0 }, offset);
955                } else {
956                    self.define_point(if edge == 2 { FXP_ONE } else { 0 }, param, offset);
957                }
958                offset += 1;
959            }
960        }
961        // the interior rings, clockwise from (u = 0, v = 1) (the bottom left) spiralling in
962        let min_points = q.points_for_inside[0].min(q.points_for_inside[1]);
963        let rings = min_points >> 1; // for even tessellation the center point is not counted here
964        for ring in 1..rings {
965            let start = ring;
966            let end = [
967                q.points_for_inside[0] - 1 - start,
968                q.points_for_inside[1] - 1 - start,
969            ];
970            for edge in 0..4usize {
971                let parity = [edge & 1, (edge + 1) & 1];
972                let perpendicular_point = if edge < 2 { start } else { end[parity[0]] };
973                self.parity = q.inside_parity[parity[0]];
974                let perpendicular = self.place_point_in_1d(&q.inside_ctx[parity[0]], perpendicular_point);
975                self.parity = q.inside_parity[parity[1]];
976                for p in start..end[parity[1]] {
977                    let n = if edge == 1 || edge == 2 {
978                        p
979                    } else {
980                        end[parity[1]] - (p - start)
981                    };
982                    let param = self.place_point_in_1d(&q.inside_ctx[parity[1]], n);
983                    if parity[1] != 0 {
984                        self.define_point(perpendicular, param, offset);
985                    } else {
986                        self.define_point(param, perpendicular, offset);
987                    }
988                    offset += 1;
989                }
990            }
991        }
992        // for even tessellation the inner ring is degenerate: a row of points
993        if q.points_for_inside[0] > q.points_for_inside[1] && q.inside_parity[1] == Parity::Even {
994            let start = rings;
995            let end = q.points_for_inside[0] - 1 - start;
996            self.parity = q.inside_parity[0];
997            for p in start..=end {
998                let param = self.place_point_in_1d(&q.inside_ctx[0], p);
999                self.define_point(param, FXP_ONE_HALF, offset);
1000                offset += 1;
1001            }
1002        } else if q.points_for_inside[1] >= q.points_for_inside[0] && q.inside_parity[0] == Parity::Even {
1003            let start = rings;
1004            let end = q.points_for_inside[1] - 1 - start;
1005            self.parity = q.inside_parity[1];
1006            for p in (start..=end).rev() {
1007                let param = self.place_point_in_1d(&q.inside_ctx[1], p);
1008                self.define_point(FXP_ONE_HALF, param, offset);
1009                offset += 1;
1010            }
1011        }
1012    }
1013
1014    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1015    fn quad_generate_connectivity(&mut self, q: &Quad) {
1016        // the primitives of the concentric rings, one side at a time for each
1017        let rows_to_center = [
1018            (q.points_for_inside[0] + 1) >> 1,
1019            (q.points_for_inside[1] + 1) >> 1,
1020        ];
1021        let rings = rows_to_center[0].min(rows_to_center[1]);
1022        // even partitioning makes a degenerate row of points, an exception to the point ordering
1023        let degenerate_ring = [
1024            if q.inside_parity[1] == Parity::Even {
1025                rows_to_center[1] - 1
1026            } else {
1027                -1
1028            },
1029            if q.inside_parity[0] == Parity::Even {
1030                rows_to_center[0] - 1
1031            } else {
1032                -1
1033            },
1034        ];
1035        let mut outside_ctx = [
1036            q.outside_ctx[0],
1037            q.outside_ctx[1],
1038            q.outside_ctx[2],
1039            q.outside_ctx[3],
1040        ];
1041        let mut outside_parity = q.outside_parity;
1042        let mut points_for_outside_edge = q.points_for_outside_edge;
1043        let mut inside_edge_base = q.inside_edge_point_base;
1044        let mut outside_edge_base = 0;
1045        for ring in 1..rings {
1046            let points_for_inside_edge = [
1047                q.points_for_inside[0] - 2 * ring,
1048                q.points_for_inside[1] - 2 * ring,
1049            ];
1050            let edge0_inside_base = inside_edge_base;
1051            let edge0_outside_base = outside_edge_base;
1052            for edge in 0..4usize {
1053                let parity = (edge + 1) & 1;
1054                let triangles = points_for_inside_edge[parity] + points_for_outside_edge[edge] - 2;
1055                let inside_base;
1056                let outside_base;
1057                if edge == 3 {
1058                    // patch the indexing so the stitch sees two sequentially increasing rows of points even though
1059                    // the ring has wrapped around to its first point
1060                    if ring == degenerate_ring[parity] {
1061                        self.patch2.base_to_invert = inside_edge_base + 1;
1062                        self.patch2.corner_bad = outside_edge_base + points_for_outside_edge[edge] - 1;
1063                        self.patch2.corner_replacement = edge0_outside_base;
1064                        self.patch2.inversion_end = (self.patch2.base_to_invert << 1) - 1;
1065                        inside_base = self.patch2.base_to_invert;
1066                        outside_base = outside_edge_base;
1067                        self.using_patch2 = true;
1068                    } else {
1069                        self.patch.inside_delta = inside_edge_base;
1070                        self.patch.inside_bad = points_for_inside_edge[parity] - 1;
1071                        self.patch.inside_replacement = edge0_inside_base;
1072                        self.patch.outside_base = self.patch.inside_bad + 1;
1073                        self.patch.outside_delta = outside_edge_base - self.patch.outside_base;
1074                        self.patch.outside_bad = self.patch.outside_base + points_for_outside_edge[edge] - 1;
1075                        self.patch.outside_replacement = edge0_outside_base;
1076                        inside_base = 0;
1077                        outside_base = self.patch.outside_base;
1078                        self.using_patch = true;
1079                    }
1080                } else if edge == 2 && ring == degenerate_ring[parity] {
1081                    self.patch2.base_to_invert = inside_edge_base;
1082                    self.patch2.corner_bad = -1;
1083                    self.patch2.corner_replacement = -1;
1084                    self.patch2.inversion_end = self.patch2.base_to_invert << 1;
1085                    inside_base = self.patch2.base_to_invert;
1086                    outside_base = outside_edge_base;
1087                    self.using_patch2 = true;
1088                } else {
1089                    inside_base = inside_edge_base;
1090                    outside_base = outside_edge_base;
1091                }
1092                if ring == 1 {
1093                    self.stitch_transition(
1094                        self.num_indices,
1095                        Row {
1096                            base: inside_base,
1097                            half_points: q.inside_ctx[parity].num_half_points,
1098                            parity: q.inside_parity[parity],
1099                        },
1100                        Row {
1101                            base: outside_base,
1102                            half_points: outside_ctx[edge].num_half_points,
1103                            parity: outside_parity[edge],
1104                        },
1105                    );
1106                } else {
1107                    self.stitch_regular(
1108                        true,
1109                        Diagonals::Mirrored,
1110                        self.num_indices,
1111                        points_for_inside_edge[parity],
1112                        inside_base,
1113                        outside_base,
1114                    );
1115                }
1116                self.using_patch = false;
1117                self.using_patch2 = false;
1118                self.num_indices += triangles * 3;
1119                outside_edge_base += points_for_outside_edge[edge] - 1;
1120                if edge == 2 && ring == degenerate_ring[parity] {
1121                    inside_edge_base -= points_for_inside_edge[parity] - 1;
1122                } else {
1123                    inside_edge_base += points_for_inside_edge[parity] - 1;
1124                }
1125                points_for_outside_edge[edge] = points_for_inside_edge[parity];
1126            }
1127            if ring == 1 {
1128                for edge in 0..4usize {
1129                    outside_ctx[edge] = q.inside_ctx[edge & 1];
1130                    outside_parity[edge] = q.inside_parity[edge & 1];
1131                }
1132            }
1133        }
1134        // the center: a row of quads if odd
1135        if q.points_for_inside[0] > q.points_for_inside[1] && q.inside_parity[1] == Parity::Odd {
1136            self.using_patch2 = true;
1137            let strip_quads = (((q.points_for_inside[0] >> 1) - (q.points_for_inside[1] >> 1)) << 1)
1138                + if q.inside_parity[0] == Parity::Even { 2 } else { 1 };
1139            self.patch2.base_to_invert = outside_edge_base + strip_quads + 2;
1140            self.patch2.corner_bad = self.patch2.base_to_invert;
1141            self.patch2.corner_replacement = outside_edge_base;
1142            self.patch2.inversion_end = self.patch2.base_to_invert + self.patch2.base_to_invert + strip_quads;
1143            self.stitch_regular(
1144                false,
1145                Diagonals::InsideToOutside,
1146                self.num_indices,
1147                strip_quads + 1,
1148                self.patch2.base_to_invert,
1149                outside_edge_base + 1,
1150            );
1151            self.using_patch2 = false;
1152            self.num_indices += strip_quads * 6;
1153        } else if q.points_for_inside[1] >= q.points_for_inside[0] && q.inside_parity[0] == Parity::Odd {
1154            self.using_patch2 = true;
1155            let strip_quads = (((q.points_for_inside[1] >> 1) - (q.points_for_inside[0] >> 1)) << 1)
1156                + if q.inside_parity[1] == Parity::Even { 2 } else { 1 };
1157            self.patch2.base_to_invert = outside_edge_base + strip_quads + 1;
1158            self.patch2.corner_bad = -1;
1159            self.patch2.inversion_end = self.patch2.base_to_invert + self.patch2.base_to_invert + strip_quads;
1160            let diagonals = if q.inside_parity[1] == Parity::Even {
1161                Diagonals::InsideToOutside
1162            } else {
1163                Diagonals::InsideToOutsideExceptMiddle
1164            };
1165            self.stitch_regular(
1166                false,
1167                diagonals,
1168                self.num_indices,
1169                strip_quads + 1,
1170                self.patch2.base_to_invert,
1171                outside_edge_base,
1172            );
1173            self.using_patch2 = false;
1174            self.num_indices += strip_quads * 6;
1175        }
1176    }
1177
1178    // --- triangles ---
1179
1180    fn triangle(&mut self, u0: f32, v0: f32, w0: f32, inside: f32) {
1181        let t = self.tri_process(u0, v0, w0, inside);
1182        if t.culled {
1183            self.num_points = 0;
1184            self.num_indices = 0;
1185            return;
1186        }
1187        if t.just_minimum {
1188            self.define_point(0, FXP_ONE, 0);
1189            self.define_point(0, 0, 1);
1190            self.define_point(FXP_ONE, 0, 2);
1191            self.num_points = 3;
1192            match self.output {
1193                OutputPrimitive::TriangleCw | OutputPrimitive::TriangleCcw => {
1194                    self.define_clockwise_triangle(0, 1, 2, self.num_indices);
1195                    self.num_indices = 3;
1196                }
1197                OutputPrimitive::Point => self.dump_all_points(),
1198                OutputPrimitive::Line => self.dump_all_points_as_in_order_line_list(),
1199            }
1200            return;
1201        }
1202        self.tri_generate_points(&t);
1203        match self.output {
1204            OutputPrimitive::Point => self.dump_all_points(),
1205            OutputPrimitive::Line => self.dump_all_points_as_in_order_line_list(),
1206            _ => self.tri_generate_connectivity(&t),
1207        }
1208    }
1209
1210    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1211    fn tri_process(&mut self, mut u0: f32, mut v0: f32, mut w0: f32, mut inside: f32) -> Tri {
1212        let mut t = Tri {
1213            outside: [0; 3],
1214            inside: 0,
1215            outside_parity: [Parity::Even; 3],
1216            inside_parity: Parity::Even,
1217            outside_ctx: [FactorContext::default(); 3],
1218            inside_ctx: FactorContext::default(),
1219            just_minimum: false,
1220            culled: false,
1221            points_for_outside_edge: [0; 3],
1222            points_for_inside: 0,
1223            inside_edge_point_base: 0,
1224        };
1225        if culls(u0) || culls(v0) || culls(w0) {
1226            t.culled = true;
1227            return t;
1228        }
1229        let (mut lower, upper) = self.bounds();
1230        u0 = fmin(upper, fmax(lower, u0));
1231        v0 = fmin(upper, fmax(lower, v0));
1232        w0 = fmin(upper, fmax(lower, w0));
1233        if self.hw_integer() {
1234            u0 = u0.ceil();
1235            v0 = v0.ceil();
1236            w0 = w0.ceil();
1237        }
1238        if self.original_partitioning == Partitioning::FractionalOdd {
1239            let limit = MIN_ODD + EPSILON / 2.0;
1240            // (a triangle has only one inside factor, so the inside factor itself is not checked)
1241            if u0 > limit || v0 > limit || w0 > limit {
1242                lower = MIN_ODD + EPSILON;
1243            }
1244        }
1245        inside = fmin(upper, fmax(lower, inside));
1246        if self.hw_integer() {
1247            inside = inside.ceil();
1248        }
1249        self.num_points = 0;
1250        self.num_indices = 0;
1251        let outside = [u0, v0, w0];
1252        if self.hw_integer() {
1253            t.outside_parity = outside.map(|f| if is_even(f) { Parity::Even } else { Parity::Odd });
1254            t.inside_parity = if is_even(inside) || inside == 1.0 {
1255                Parity::Even
1256            } else {
1257                Parity::Odd
1258            };
1259        } else {
1260            t.outside_parity = [self.original_parity; 3];
1261            t.inside_parity = self.original_parity;
1262        }
1263        t.outside = outside.map(float_to_fixed);
1264        t.inside = float_to_fixed(inside);
1265        if (self.hw_integer() || self.odd()) && t.inside == FXP_ONE && t.outside.iter().all(|&f| f == FXP_ONE)
1266        {
1267            t.just_minimum = true;
1268            return t;
1269        }
1270        t.just_minimum = false;
1271        for e in 0..3 {
1272            self.parity = t.outside_parity[e];
1273            t.outside_ctx[e] = self.factor_context(t.outside[e]);
1274        }
1275        self.parity = t.inside_parity;
1276        t.inside_ctx = self.factor_context(t.inside);
1277        for e in 0..3 {
1278            self.parity = t.outside_parity[e];
1279            t.points_for_outside_edge[e] = self.num_points_for_factor(t.outside[e]);
1280            self.num_points += t.points_for_outside_edge[e];
1281        }
1282        self.num_points -= 3;
1283        self.parity = t.inside_parity;
1284        t.points_for_inside = self.num_points_for_factor(t.inside);
1285        let min = if self.odd() { 4 } else { 3 };
1286        t.points_for_inside = min.max(t.points_for_inside);
1287        t.inside_edge_point_base = self.num_points;
1288        let interior_rings = (t.points_for_inside >> 1) - 1;
1289        let interior_points = if self.odd() {
1290            3 * (interior_rings * (interior_rings + 1) - interior_rings)
1291        } else {
1292            3 * (interior_rings * (interior_rings + 1)) + 1
1293        };
1294        self.num_points += interior_points;
1295        t
1296    }
1297
1298    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1299    fn tri_generate_points(&mut self, t: &Tri) {
1300        // the exterior ring's edge points, clockwise from point V (edge VW, the U = 0 edge)
1301        let mut offset = 0;
1302        for edge in 0..3usize {
1303            let parity = edge & 1;
1304            let end = t.points_for_outside_edge[edge] - 1;
1305            for p in 0..end {
1306                // edge 0, VW, has V decreasing: reverse; edge 1, WU, has U increasing; edge 2, UV, has U decreasing
1307                let n = if parity != 0 { p } else { end - p };
1308                self.parity = t.outside_parity[edge];
1309                let param = self.place_point_in_1d(&t.outside_ctx[edge], n);
1310                if edge == 0 {
1311                    self.define_point(0, param, offset);
1312                } else {
1313                    self.define_point(
1314                        param,
1315                        if edge == 2 { FXP_ONE.wrapping_sub(param) } else { 0 },
1316                        offset,
1317                    );
1318                }
1319                offset += 1;
1320            }
1321        }
1322        // the interior rings, clockwise spiralling in
1323        self.parity = t.inside_parity;
1324        let rings = t.points_for_inside >> 1;
1325        for ring in 1..rings {
1326            let start = ring;
1327            let end = t.points_for_inside - 1 - start;
1328            for edge in 0..3usize {
1329                let parity = edge & 1;
1330                let mut perpendicular = self.place_point_in_1d(&t.inside_ctx, start);
1331                // maps the location to the right size in barycentric space
1332                perpendicular = perpendicular.wrapping_mul(FXP_TWO_THIRDS);
1333                perpendicular = perpendicular.wrapping_add(FXP_ONE_HALF) >> FRACTION_BITS;
1334                for p in start..end {
1335                    let n = if parity != 0 { p } else { end - (p - start) };
1336                    let param = self.place_point_in_1d(&t.inside_ctx, n);
1337                    // the rate of change of edge-parallel parameters as they are pushed into the triangle
1338                    let pushed = param.wrapping_sub(perpendicular.wrapping_add(1) / 2);
1339                    match edge {
1340                        0 => self.define_point(perpendicular, pushed, offset),
1341                        1 => self.define_point(pushed, perpendicular, offset),
1342                        _ => self.define_point(
1343                            pushed,
1344                            FXP_ONE.wrapping_sub(pushed).wrapping_sub(perpendicular),
1345                            offset,
1346                        ),
1347                    }
1348                    offset += 1;
1349                }
1350            }
1351        }
1352        if !self.odd() {
1353            // the last point is the center
1354            self.define_point(FXP_ONE_THIRD, FXP_ONE_THIRD, offset);
1355        }
1356    }
1357
1358    #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1359    fn tri_generate_connectivity(&mut self, t: &Tri) {
1360        let rings = (t.points_for_inside + 1) >> 1; // so that even tessellation includes the center point
1361        let mut outside_ctx = t.outside_ctx;
1362        let mut outside_parity = t.outside_parity;
1363        let mut points_for_outside_edge = t.points_for_outside_edge;
1364        let mut inside_edge_base = t.inside_edge_point_base;
1365        let mut outside_edge_base = 0;
1366        for ring in 1..rings {
1367            let points_for_inside_edge = t.points_for_inside - 2 * ring;
1368            let edge0_inside_base = inside_edge_base;
1369            let edge0_outside_base = outside_edge_base;
1370            for edge in 0..3usize {
1371                let triangles = points_for_inside_edge + points_for_outside_edge[edge] - 2;
1372                let (inside_base, outside_base) = if edge == 2 {
1373                    self.patch.inside_delta = inside_edge_base;
1374                    self.patch.inside_bad = points_for_inside_edge - 1;
1375                    self.patch.inside_replacement = edge0_inside_base;
1376                    self.patch.outside_base = self.patch.inside_bad + 1;
1377                    self.patch.outside_delta = outside_edge_base - self.patch.outside_base;
1378                    self.patch.outside_bad = self.patch.outside_base + points_for_outside_edge[edge] - 1;
1379                    self.patch.outside_replacement = edge0_outside_base;
1380                    self.using_patch = true;
1381                    (0, self.patch.outside_base)
1382                } else {
1383                    (inside_edge_base, outside_edge_base)
1384                };
1385                if ring == 1 {
1386                    self.stitch_transition(
1387                        self.num_indices,
1388                        Row {
1389                            base: inside_base,
1390                            half_points: t.inside_ctx.num_half_points,
1391                            parity: t.inside_parity,
1392                        },
1393                        Row {
1394                            base: outside_base,
1395                            half_points: outside_ctx[edge].num_half_points,
1396                            parity: outside_parity[edge],
1397                        },
1398                    );
1399                } else {
1400                    self.stitch_regular(
1401                        true,
1402                        Diagonals::Mirrored,
1403                        self.num_indices,
1404                        points_for_inside_edge,
1405                        inside_base,
1406                        outside_base,
1407                    );
1408                }
1409                if edge == 2 {
1410                    self.using_patch = false;
1411                }
1412                self.num_indices += triangles * 3;
1413                outside_edge_base += points_for_outside_edge[edge] - 1;
1414                inside_edge_base += points_for_inside_edge - 1;
1415                points_for_outside_edge[edge] = points_for_inside_edge;
1416            }
1417            if ring == 1 {
1418                for edge in 0..3usize {
1419                    outside_ctx[edge] = t.inside_ctx;
1420                    outside_parity[edge] = t.inside_parity;
1421                }
1422            }
1423        }
1424        if self.odd() {
1425            // the center is one triangle
1426            self.define_clockwise_triangle(
1427                outside_edge_base,
1428                outside_edge_base + 1,
1429                outside_edge_base + 2,
1430                self.num_indices,
1431            );
1432            self.num_indices += 3;
1433        }
1434    }
1435
1436    // --- isolines ---
1437
1438    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1439    fn isolines(&mut self, density: f32, detail: f32) {
1440        let l = self.isoline_process(density, detail);
1441        if l.culled {
1442            self.num_points = 0;
1443            self.num_indices = 0;
1444            return;
1445        }
1446        // the points: every line's points in order
1447        let mut offset = 0;
1448        for line in 0..l.lines {
1449            for point in 0..l.points_per_line {
1450                self.parity = l.line_density_parity;
1451                let v = self.place_point_in_1d(&l.line_density_ctx, line);
1452                self.parity = l.line_detail_parity;
1453                let u = self.place_point_in_1d(&l.line_detail_ctx, point);
1454                self.define_point(u, v, offset);
1455                offset += 1;
1456            }
1457        }
1458        // the connections
1459        let (mut point_offset, mut index_offset) = (0, 0);
1460        if self.output == OutputPrimitive::Point {
1461            for _ in 0..l.lines {
1462                for _ in 0..l.points_per_line {
1463                    self.define_index(point_offset, index_offset);
1464                    point_offset += 1;
1465                    index_offset += 1;
1466                }
1467            }
1468        } else {
1469            for _ in 0..l.lines {
1470                for point in 0..l.points_per_line {
1471                    if point > 0 {
1472                        self.define_index(point_offset - 1, index_offset);
1473                        index_offset += 1;
1474                        self.define_index(point_offset, index_offset);
1475                        index_offset += 1;
1476                    }
1477                    point_offset += 1;
1478                }
1479            }
1480        }
1481    }
1482
1483    #[allow(clippy::arithmetic_side_effects)] // indices and counters are bounded by the factors clamped to at most 64 points per edge and by the lengths of the tables
1484    fn isoline_process(&mut self, mut density: f32, mut detail: f32) -> Isolines {
1485        let mut l = Isolines {
1486            line_density_parity: Parity::Even,
1487            line_detail_parity: Parity::Even,
1488            line_density_ctx: FactorContext::default(),
1489            line_detail_ctx: FactorContext::default(),
1490            culled: false,
1491            points_per_line: 0,
1492            lines: 0,
1493        };
1494        if culls(density) || culls(detail) {
1495            l.culled = true;
1496            return l;
1497        }
1498        let (lower, upper) = self.bounds();
1499        density = fmin(MAX_ISOLINE_DENSITY, fmax(MIN_ISOLINE_DENSITY, density));
1500        detail = fmin(upper, fmax(lower, detail));
1501        self.num_points = 0;
1502        self.num_indices = 0;
1503        if self.hw_integer() {
1504            detail = detail.ceil();
1505            l.line_detail_parity = if is_even(detail) {
1506                Parity::Even
1507            } else {
1508                Parity::Odd
1509            };
1510        } else {
1511            l.line_detail_parity = self.original_parity;
1512        }
1513        let fxp_detail = float_to_fixed(detail);
1514        self.parity = l.line_detail_parity;
1515        l.line_detail_ctx = self.factor_context(fxp_detail);
1516        l.points_per_line = self.num_points_for_factor(fxp_detail);
1517        // the line density is always integer partitioned
1518        self.partitioning = Partitioning::Integer;
1519        density = density.ceil();
1520        l.line_density_parity = if is_even(density) {
1521            Parity::Even
1522        } else {
1523            Parity::Odd
1524        };
1525        self.parity = l.line_density_parity;
1526        let fxp_density = float_to_fixed(density);
1527        l.line_density_ctx = self.factor_context(fxp_density);
1528        // the last line, at v = 1, is not drawn
1529        l.lines = self.num_points_for_factor(fxp_density) - 1;
1530        self.partitioning = self.original_partitioning;
1531        self.num_points = l.points_per_line * l.lines;
1532        self.num_indices = if self.output == OutputPrimitive::Point {
1533            self.num_points
1534        } else {
1535            l.lines * (l.points_per_line - 1) * 2
1536        };
1537        l
1538    }
1539}