meshopt_rs/
stripify.rs

1//! Mesh triangle list ↔ triangle strip conversion
2
3use crate::INVALID_INDEX;
4
5fn find_strip_first(buffer: &[[u32; 3]], valence: &[u32]) -> usize {
6    let mut index = 0;
7    let mut iv = INVALID_INDEX;
8
9    for (i, b) in buffer.iter().enumerate() {
10        let va = valence[b[0] as usize];
11        let vb = valence[b[1] as usize];
12        let vc = valence[b[2] as usize];
13
14        let v = va.min(vb).min(vc);
15
16        if v < iv {
17            index = i;
18            iv = v;
19        }
20    }
21
22    index
23}
24
25fn find_strip_next(buffer: &[[u32; 3]], e: (u32, u32)) -> i32 {
26    for (i, abc) in buffer.iter().enumerate() {
27        if e.0 == abc[0] && e.1 == abc[1] {
28            return ((i as i32) << 2) | 2;
29        } else if e.0 == abc[1] && e.1 == abc[2] {
30            return ((i as i32) << 2) | 0;
31        } else if e.0 == abc[2] && e.1 == abc[0] {
32            return ((i as i32) << 2) | 1;
33        }
34    }
35
36    -1
37}
38
39/// Converts a previously vertex cache optimized triangle list to triangle strip, stitching strips using restart index or degenerate triangles.
40///
41/// Returns the number of indices in the resulting strip, with destination containing new index data.
42/// For maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
43/// Using restart indices can result in ~10% smaller index buffers, but on some GPUs restart indices may result in decreased performance.
44///
45/// # Arguments
46///
47/// * `destination`: must contain enough space for the target index buffer, worst case can be computed with [stripify_bound]
48/// * `restart_index`: should be `INVALID_INDEX` or `0` to use degenerate triangles
49pub fn stripify(destination: &mut [u32], indices: &[u32], vertex_count: usize, restart_index: u32) -> usize {
50    assert!(indices.len() % 3 == 0);
51
52    const BUFFER_CAPACITY: usize = 8;
53
54    let mut buffer = [[0; 3]; BUFFER_CAPACITY];
55    let mut buffer_size = 0;
56
57    let mut index_offset = 0;
58
59    let mut strip = [0; 2];
60    let mut parity = false;
61
62    let mut strip_size = 0;
63
64    // compute vertex valence; this is used to prioritize starting triangle for strips
65    let mut valence = vec![0; vertex_count];
66
67    for index in indices {
68        valence[*index as usize] += 1;
69    }
70
71    let mut next: i32 = -1;
72
73    let index_count = indices.len();
74
75    while buffer_size > 0 || index_offset < index_count {
76        assert!(next < 0 || (((next >> 2) as usize) < buffer_size && (next & 3) < 3));
77
78        // fill triangle buffer
79        while buffer_size < buffer.len() && index_offset < index_count {
80            buffer[buffer_size].copy_from_slice(&indices[index_offset..index_offset + 3]);
81
82            buffer_size += 1;
83            index_offset += 3;
84        }
85
86        assert!(buffer_size > 0 && buffer_size <= buffer.len());
87
88        if next >= 0 {
89            let i = (next >> 2) as usize;
90            let abc = buffer[i];
91            let v = buffer[i][(next & 3) as usize];
92
93            // ordered removal from the buffer
94            let buffer_length = buffer.len();
95            buffer.copy_within(i + 1..buffer_length, i);
96            buffer_size -= 1;
97
98            // update vertex valences for strip start heuristic
99            valence[abc[0] as usize] -= 1;
100            valence[abc[1] as usize] -= 1;
101            valence[abc[2] as usize] -= 1;
102
103            // find next triangle (note that edge order flips on every iteration)
104            // in some cases we need to perform a swap to pick a different outgoing triangle edge
105            // for [a b c], the default strip edge is [b c], but we might want to use [a c]
106            let cont = find_strip_next(
107                &buffer[0..buffer_size],
108                if parity { (strip[1], v) } else { (v, strip[1]) },
109            );
110            let swap = if cont < 0 {
111                find_strip_next(
112                    &buffer[0..buffer_size],
113                    if parity { (v, strip[0]) } else { (strip[0], v) },
114                )
115            } else {
116                -1
117            };
118
119            if cont < 0 && swap >= 0 {
120                // [a b c] => [a b a c]
121                destination[strip_size] = strip[0];
122                strip_size += 1;
123                destination[strip_size] = v;
124                strip_size += 1;
125
126                // next strip has same winding
127                // ? a b => b a v
128                strip[1] = v;
129
130                next = swap;
131            } else {
132                // emit the next vertex in the strip
133                destination[strip_size] = v;
134                strip_size += 1;
135
136                // next strip has flipped winding
137                strip[0] = strip[1];
138                strip[1] = v;
139                parity ^= true;
140
141                next = cont;
142            }
143        } else {
144            // if we didn't find anything, we need to find the next new triangle
145            // we use a heuristic to maximize the strip length
146            let i = find_strip_first(&buffer[0..buffer_size], &valence);
147            let mut abc = buffer[i];
148
149            // ordered removal from the buffer
150            buffer.copy_within(i + 1.., i);
151            buffer_size -= 1;
152
153            // update vertex valences for strip start heuristic
154            valence[abc[0] as usize] -= 1;
155            valence[abc[1] as usize] -= 1;
156            valence[abc[2] as usize] -= 1;
157
158            // we need to pre-rotate the triangle so that we will find a match in the existing buffer on the next iteration
159            let ea = find_strip_next(&buffer[0..buffer_size], (abc[2], abc[1]));
160            let eb = find_strip_next(&buffer[0..buffer_size], (abc[0], abc[2]));
161            let ec = find_strip_next(&buffer[0..buffer_size], (abc[1], abc[0]));
162
163            // in some cases we can have several matching edges; since we can pick any edge, we pick the one with the smallest
164            // triangle index in the buffer. this reduces the effect of stripification on ACMR and additionally - for unclear
165            // reasons - slightly improves the stripification efficiency
166            let mut mine = i32::MAX;
167            mine = if ea >= 0 && mine > ea { ea } else { mine };
168            mine = if eb >= 0 && mine > eb { eb } else { mine };
169            mine = if ec >= 0 && mine > ec { ec } else { mine };
170
171            match mine {
172                _ if mine == ea => {
173                    // keep abc
174                    next = ea;
175                }
176                _ if mine == eb => {
177                    // abc -> bca
178                    abc.rotate_left(1);
179                    next = eb;
180                }
181                _ if mine == ec => {
182                    // abc -> cab
183                    abc.rotate_right(1);
184                    next = ec;
185                }
186                _ => {}
187            }
188
189            if restart_index != 0 {
190                if strip_size != 0 {
191                    destination[strip_size] = restart_index;
192                    strip_size += 1;
193                }
194
195                destination[strip_size..strip_size + 3].copy_from_slice(&abc);
196                strip_size += 3;
197
198                // new strip always starts with the same edge winding
199                strip[0] = abc[1];
200                strip[1] = abc[2];
201                parity = true;
202            } else {
203                if strip_size != 0 {
204                    // connect last strip using degenerate triangles
205                    destination[strip_size] = strip[1];
206                    strip_size += 1;
207                    destination[strip_size] = abc[0];
208                    strip_size += 1;
209                }
210
211                // note that we may need to flip the emitted triangle based on parity
212                // we always end up with outgoing edge "cb" in the end
213                let (e0, e1) = if parity { (abc[2], abc[1]) } else { (abc[1], abc[2]) };
214
215                destination[strip_size..strip_size + 3].copy_from_slice(&[abc[0], e0, e1]);
216                strip_size += 3;
217
218                strip[0] = e0;
219                strip[1] = e1;
220                parity ^= true;
221            }
222        }
223    }
224
225    strip_size
226}
227
228/// Returns worst case size requirement for [stripify].
229pub fn stripify_bound(index_count: usize) -> usize {
230    assert!(index_count % 3 == 0);
231
232    // worst case without restarts is 2 degenerate indices and 3 indices per triangle
233    // worst case with restarts is 1 restart index and 3 indices per triangle
234    (index_count / 3) * 5
235}
236
237/// Converts a triangle strip to a triangle list.
238///
239/// Returns the number of indices in the resulting list, with destination containing new index data.
240///
241/// # Arguments
242///
243/// * `destination`: must contain enough space for the target index buffer, worst case can be computed with [unstripify_bound]
244pub fn unstripify(destination: &mut [u32], indices: &[u32], restart_index: u32) -> usize {
245    let mut offset = 0;
246    let mut start = 0;
247
248    for (i, index) in indices.iter().enumerate() {
249        if restart_index != 0 && *index == restart_index {
250            start = i + 1;
251        } else if i - start >= 2 {
252            let mut a = indices[i - 2];
253            let mut b = indices[i - 1];
254            let c = indices[i - 0];
255
256            // flip winding for odd triangles
257            if ((i - start) & 1) != 0 {
258                std::mem::swap(&mut a, &mut b);
259            }
260
261            // although we use restart indices, strip swaps still produce degenerate triangles, so skip them
262            if a != b && a != c && b != c {
263                destination[offset + 0] = a;
264                destination[offset + 1] = b;
265                destination[offset + 2] = c;
266                offset += 3;
267            }
268        }
269    }
270
271    offset
272}
273
274pub fn unstripify_bound(index_count: usize) -> usize {
275    assert!(index_count == 0 || index_count >= 3);
276
277    if index_count == 0 {
278        0
279    } else {
280        (index_count - 2) * 3
281    }
282}