moxcms/conversions/
md_lut.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::math::{FusedMultiplyAdd, FusedMultiplyNegAdd};
30use crate::mlaf::{mlaf, neg_mlaf};
31use crate::nd_array::{ArrayFetch, lerp};
32use crate::{Vector3f, Vector3i};
33use num_traits::MulAdd;
34use std::array::from_fn;
35use std::marker::PhantomData;
36use std::ops::{Add, Mul, Neg, Sub};
37
38pub(crate) struct MultidimensionalLut {
39    pub(crate) grid_strides: [u32; 16],
40    pub(crate) grid_filling_size: [u32; 16],
41    pub(crate) grid_scale: [f32; 16],
42    pub(crate) output_inks: usize,
43}
44
45struct FastCube<T, F: ArrayFetch<T>> {
46    fetch: F,
47    _phantom: PhantomData<T>,
48}
49
50struct ArrayFetchVectorN<'a> {
51    array: &'a [f32],
52    x_stride: u32,
53    y_stride: u32,
54    z_stride: u32,
55    output_inks: usize,
56}
57
58#[repr(transparent)]
59#[derive(Copy, Clone, Debug)]
60pub(crate) struct NVector<T, const N: usize> {
61    pub(crate) v: [T; N],
62}
63
64impl<T: Copy, const N: usize> NVector<T, N> {
65    pub(crate) fn from_slice(v: &[T; N]) -> Self {
66        Self { v: *v }
67    }
68}
69
70impl<T: Copy, const N: usize> From<T> for NVector<T, N> {
71    #[inline]
72    fn from(value: T) -> Self {
73        Self { v: [value; N] }
74    }
75}
76
77impl<T: Copy + Add<T, Output = T> + Mul<T, Output = T> + MulAdd<T, Output = T>, const N: usize>
78    FusedMultiplyAdd<NVector<T, N>> for NVector<T, N>
79{
80    #[inline]
81    fn mla(&self, b: NVector<T, N>, c: NVector<T, N>) -> NVector<T, N> {
82        Self {
83            v: from_fn(|i| mlaf(self.v[i], b.v[i], c.v[i])),
84        }
85    }
86}
87
88impl<
89    T: Copy + Add<T, Output = T> + Mul<T, Output = T> + MulAdd<T, Output = T> + Neg<Output = T>,
90    const N: usize,
91> FusedMultiplyNegAdd<NVector<T, N>> for NVector<T, N>
92{
93    #[inline]
94    fn neg_mla(&self, b: NVector<T, N>, c: NVector<T, N>) -> NVector<T, N> {
95        Self {
96            v: from_fn(|i| neg_mlaf(self.v[i], b.v[i], c.v[i])),
97        }
98    }
99}
100
101impl<T: Sub<Output = T> + Default + Copy, const N: usize> Sub<NVector<T, N>> for NVector<T, N> {
102    type Output = Self;
103
104    #[inline]
105    fn sub(self, rhs: NVector<T, N>) -> Self::Output {
106        Self {
107            v: from_fn(|i| self.v[i] - rhs.v[i]),
108        }
109    }
110}
111
112impl<T: Add<Output = T> + Default + Copy, const N: usize> Add<NVector<T, N>> for NVector<T, N> {
113    type Output = Self;
114
115    #[inline]
116    fn add(self, rhs: NVector<T, N>) -> Self::Output {
117        Self {
118            v: from_fn(|i| self.v[i] + rhs.v[i]),
119        }
120    }
121}
122
123impl<T: Mul<Output = T> + Default + Copy, const N: usize> Mul<NVector<T, N>> for NVector<T, N> {
124    type Output = Self;
125
126    #[inline]
127    fn mul(self, rhs: NVector<T, N>) -> Self::Output {
128        Self {
129            v: from_fn(|i| self.v[i] * rhs.v[i]),
130        }
131    }
132}
133
134impl<const N: usize> ArrayFetch<NVector<f32, N>> for ArrayFetchVectorN<'_> {
135    #[inline(always)]
136    fn fetch(&self, x: i32, y: i32, z: i32) -> NVector<f32, N> {
137        let start = (x as u32 * self.x_stride + y as u32 * self.y_stride + z as u32 * self.z_stride)
138            as usize
139            * self.output_inks;
140        let k = &self.array[start..start + N];
141        NVector::<f32, N>::from_slice(k.try_into().unwrap())
142    }
143}
144
145impl<T, F: ArrayFetch<T>> FastCube<T, F>
146where
147    T: Copy
148        + From<f32>
149        + Sub<T, Output = T>
150        + Mul<T, Output = T>
151        + Add<T, Output = T>
152        + FusedMultiplyNegAdd<T>
153        + FusedMultiplyAdd<T>,
154{
155    #[inline(always)]
156    fn tetra(&self, src: Vector3i, src_next: Vector3i, w: Vector3f) -> T {
157        let x = src.v[0];
158        let y = src.v[1];
159        let z = src.v[2];
160
161        let x_n = src_next.v[0];
162        let y_n = src_next.v[1];
163        let z_n = src_next.v[2];
164
165        let rx = w.v[0];
166        let ry = w.v[1];
167        let rz = w.v[2];
168
169        let c0 = self.fetch.fetch(x, y, z);
170        let c2;
171        let c1;
172        let c3;
173        if rx >= ry {
174            if ry >= rz {
175                //rx >= ry && ry >= rz
176                c1 = self.fetch.fetch(x_n, y, z) - c0;
177                c2 = self.fetch.fetch(x_n, y_n, z) - self.fetch.fetch(x_n, y, z);
178                c3 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y_n, z);
179            } else if rx >= rz {
180                //rx >= rz && rz >= ry
181                c1 = self.fetch.fetch(x_n, y, z) - c0;
182                c2 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y, z_n);
183                c3 = self.fetch.fetch(x_n, y, z_n) - self.fetch.fetch(x_n, y, z);
184            } else {
185                //rz > rx && rx >= ry
186                c1 = self.fetch.fetch(x_n, y, z_n) - self.fetch.fetch(x, y, z_n);
187                c2 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y, z_n);
188                c3 = self.fetch.fetch(x, y, z_n) - c0;
189            }
190        } else if rx >= rz {
191            //ry > rx && rx >= rz
192            c1 = self.fetch.fetch(x_n, y_n, z) - self.fetch.fetch(x, y_n, z);
193            c2 = self.fetch.fetch(x, y_n, z) - c0;
194            c3 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x_n, y_n, z);
195        } else if ry >= rz {
196            //ry >= rz && rz > rx
197            c1 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x, y_n, z_n);
198            c2 = self.fetch.fetch(x, y_n, z) - c0;
199            c3 = self.fetch.fetch(x, y_n, z_n) - self.fetch.fetch(x, y_n, z);
200        } else {
201            //rz > ry && ry > rx
202            c1 = self.fetch.fetch(x_n, y_n, z_n) - self.fetch.fetch(x, y_n, z_n);
203            c2 = self.fetch.fetch(x, y_n, z_n) - self.fetch.fetch(x, y, z_n);
204            c3 = self.fetch.fetch(x, y, z_n) - c0;
205        }
206        let s0 = c0.mla(c1, T::from(rx));
207        let s1 = s0.mla(c2, T::from(ry));
208        s1.mla(c3, T::from(rz))
209    }
210}
211
212impl MultidimensionalLut {
213    pub(crate) fn new(grid_size: [u8; 16], input_inks: usize, output_inks: usize) -> Self {
214        assert!(input_inks <= 16);
215        let mut grid_strides = [1u32; 16];
216        let mut grid_filling_size = [1u32; 16];
217
218        for (ink, dst_stride) in grid_strides.iter_mut().take(input_inks - 1).enumerate() {
219            let mut stride = 1u32;
220            let how_many = input_inks.saturating_sub(ink).saturating_sub(1);
221            for &grid_stride in grid_size.iter().take(how_many) {
222                stride *= grid_stride as u32;
223            }
224            *dst_stride = stride;
225        }
226
227        for (ink, dst_stride) in grid_filling_size.iter_mut().take(input_inks).enumerate() {
228            let mut stride = output_inks as u32;
229            let how_many = input_inks.saturating_sub(ink).saturating_sub(1);
230            for &grid_stride in grid_size.iter().take(how_many) {
231                stride *= grid_stride as u32;
232            }
233            *dst_stride = stride;
234        }
235
236        let mut grid_strides_f = [0f32; 16];
237
238        for (dst, src) in grid_strides_f
239            .iter_mut()
240            .zip(grid_size.iter())
241            .take(input_inks)
242        {
243            *dst = (*src - 1) as f32;
244        }
245
246        Self {
247            grid_strides,
248            grid_scale: grid_strides_f,
249            grid_filling_size,
250            output_inks,
251        }
252    }
253}
254
255pub(crate) fn linear_4i_vec3f_direct<const N: usize>(
256    lut: &MultidimensionalLut,
257    arr: &[f32],
258    lx: f32,
259    ly: f32,
260    lz: f32,
261    lw: f32,
262) -> NVector<f32, N> {
263    let lin_x = lx.max(0.0).min(1.0);
264    let lin_y = ly.max(0.0).min(1.0);
265    let lin_z = lz.max(0.0).min(1.0);
266    let lin_w = lw.max(0.0).min(1.0);
267
268    let scale_x = lut.grid_scale[0];
269    let scale_y = lut.grid_scale[1];
270    let scale_z = lut.grid_scale[2];
271    let scale_w = lut.grid_scale[3];
272
273    let lx = lin_x * scale_x;
274    let ly = lin_y * scale_y;
275    let lz = lin_z * scale_z;
276    let lw = lin_w * scale_w;
277
278    let x = lx.floor() as i32;
279    let y = ly.floor() as i32;
280    let z = lz.floor() as i32;
281    let w = lw.floor() as i32;
282
283    let src_x = Vector3i { v: [x, y, z] };
284
285    let x_n = lx.ceil() as i32;
286    let y_n = ly.ceil() as i32;
287    let z_n = lz.ceil() as i32;
288    let w_n = lw.ceil() as i32;
289
290    let src_next = Vector3i { v: [x_n, y_n, z_n] };
291
292    let x_w = lx - x as f32;
293    let y_w = ly - y as f32;
294    let z_w = lz - z as f32;
295    let w_w = lw - w as f32;
296
297    let weights = Vector3f { v: [x_w, y_w, z_w] };
298
299    let cube0 = &arr[(w as usize * lut.grid_filling_size[3] as usize)..];
300    let cube1 = &arr[(w_n as usize * lut.grid_filling_size[3] as usize)..];
301
302    let fast_cube0 = FastCube {
303        fetch: ArrayFetchVectorN {
304            array: cube0,
305            x_stride: lut.grid_strides[0],
306            y_stride: lut.grid_strides[1],
307            z_stride: lut.grid_strides[2],
308            output_inks: lut.output_inks,
309        },
310        _phantom: PhantomData,
311    };
312    let fast_cube1 = FastCube {
313        fetch: ArrayFetchVectorN {
314            array: cube1,
315            x_stride: lut.grid_strides[0],
316            y_stride: lut.grid_strides[1],
317            z_stride: lut.grid_strides[2],
318            output_inks: lut.output_inks,
319        },
320        _phantom: PhantomData,
321    };
322    let w0 = fast_cube0.tetra(src_x, src_next, weights);
323    let w1 = fast_cube1.tetra(src_x, src_next, weights);
324    lerp(w0, w1, NVector::<f32, N>::from(w_w))
325}
326
327pub(crate) fn linear_3i_vec3f_direct<const N: usize>(
328    lut: &MultidimensionalLut,
329    arr: &[f32],
330    inputs: &[f32],
331) -> NVector<f32, N> {
332    linear_3i_vec3f(lut, arr, inputs[0], inputs[1], inputs[2])
333}
334
335fn linear_3i_vec3f<const N: usize>(
336    lut: &MultidimensionalLut,
337    arr: &[f32],
338    x: f32,
339    y: f32,
340    z: f32,
341) -> NVector<f32, N> {
342    let lin_x = x.max(0.0).min(1.0);
343    let lin_y = y.max(0.0).min(1.0);
344    let lin_z = z.max(0.0).min(1.0);
345
346    let scale_x = lut.grid_scale[0];
347    let scale_y = lut.grid_scale[1];
348    let scale_z = lut.grid_scale[2];
349
350    let lx = lin_x * scale_x;
351    let ly = lin_y * scale_y;
352    let lz = lin_z * scale_z;
353
354    let x = lx.floor() as i32;
355    let y = ly.floor() as i32;
356    let z = lz.floor() as i32;
357
358    let src_x = Vector3i { v: [x, y, z] };
359
360    let x_n = lx.ceil() as i32;
361    let y_n = ly.ceil() as i32;
362    let z_n = lz.ceil() as i32;
363
364    let src_next = Vector3i { v: [x_n, y_n, z_n] };
365
366    let x_w = lx - x as f32;
367    let y_w = ly - y as f32;
368    let z_w = lz - z as f32;
369
370    let weights = Vector3f { v: [x_w, y_w, z_w] };
371
372    let fast_cube = FastCube {
373        fetch: ArrayFetchVectorN {
374            array: arr,
375            x_stride: lut.grid_strides[0],
376            y_stride: lut.grid_strides[1],
377            z_stride: lut.grid_strides[2],
378            output_inks: lut.output_inks,
379        },
380        _phantom: PhantomData,
381    };
382
383    fast_cube.tetra(src_x, src_next, weights)
384}
385
386pub(crate) fn linear_1i_vec3f<const N: usize>(
387    lut: &MultidimensionalLut,
388    arr: &[f32],
389    inputs: &[f32],
390) -> NVector<f32, N> {
391    let lin_x = inputs[0].max(0.0).min(1.0);
392
393    let scale_x = lut.grid_scale[0];
394
395    let lx = lin_x * scale_x;
396
397    let x = lx.floor() as i32;
398
399    let x_n = lx.ceil() as i32;
400
401    let x_w = lx - x as f32;
402
403    let x_stride = lut.grid_strides[0];
404
405    let offset = |xi: i32| -> usize { (xi as u32 * x_stride) as usize * lut.output_inks };
406
407    // Sample 2 corners
408    let a = NVector::<f32, N>::from_slice(&arr[offset(x)..][..N].try_into().unwrap());
409    let b = NVector::<f32, N>::from_slice(&arr[offset(x_n)..][..N].try_into().unwrap());
410
411    a * NVector::<f32, N>::from(1.0 - x_w) + b * NVector::<f32, N>::from(x_w)
412}
413
414pub(crate) fn linear_2i_vec3f_direct<const N: usize>(
415    lut: &MultidimensionalLut,
416    arr: &[f32],
417    inputs: &[f32],
418) -> NVector<f32, N> {
419    linear_2i_vec3f(lut, arr, inputs[0], inputs[1])
420}
421
422fn linear_2i_vec3f<const N: usize>(
423    lut: &MultidimensionalLut,
424    arr: &[f32],
425    x: f32,
426    y: f32,
427) -> NVector<f32, N> {
428    let lin_x = x.max(0.0).min(1.0);
429    let lin_y = y.max(0.0).min(1.0);
430
431    let scale_x = lut.grid_scale[0];
432    let scale_y = lut.grid_scale[1];
433
434    let lx = lin_x * scale_x;
435    let ly = lin_y * scale_y;
436
437    let x = lx.floor() as i32;
438    let y = ly.floor() as i32;
439
440    let x_n = lx.ceil() as i32;
441    let y_n = ly.ceil() as i32;
442
443    let x_w = lx - x as f32;
444    let y_w = ly - y as f32;
445
446    let x_stride = lut.grid_strides[0];
447    let y_stride = lut.grid_strides[1];
448
449    let offset = |xi: i32, yi: i32| -> usize {
450        (xi as u32 * x_stride + yi as u32 * y_stride) as usize * lut.output_inks
451    };
452
453    // Sample 4 corners
454    let a = NVector::<f32, N>::from_slice(&arr[offset(x, y)..][..N].try_into().unwrap());
455    let b = NVector::<f32, N>::from_slice(&arr[offset(x_n, y)..][..N].try_into().unwrap());
456    let c = NVector::<f32, N>::from_slice(&arr[offset(x, y_n)..][..N].try_into().unwrap());
457    let d = NVector::<f32, N>::from_slice(&arr[offset(x_n, y_n)..][..N].try_into().unwrap());
458
459    let ab = a * NVector::<f32, N>::from(1.0 - x_w) + b * NVector::<f32, N>::from(x_w);
460    let cd = c * NVector::<f32, N>::from(1.0 - x_w) + d * NVector::<f32, N>::from(x_w);
461
462    ab * NVector::<f32, N>::from(1.0 - y_w) + cd * NVector::<f32, N>::from(y_w)
463}
464
465pub(crate) fn linear_4i_vec3f<const N: usize>(
466    lut: &MultidimensionalLut,
467    arr: &[f32],
468    inputs: &[f32],
469) -> NVector<f32, N> {
470    linear_4i_vec3f_direct(lut, arr, inputs[0], inputs[1], inputs[2], inputs[3])
471}
472
473type FHandle<const N: usize> = fn(&MultidimensionalLut, &[f32], &[f32]) -> NVector<f32, N>;
474
475#[inline(never)]
476pub(crate) fn linear_n_i_vec3f<
477    const N: usize,
478    const I: usize,
479    Handle: Fn(&MultidimensionalLut, &[f32], &[f32]) -> NVector<f32, N>,
480>(
481    lut: &MultidimensionalLut,
482    arr: &[f32],
483    inputs: &[f32],
484    handle: Handle,
485) -> NVector<f32, N> {
486    let lin_w = inputs[I];
487
488    let w_c = lin_w.max(0.).min(1.);
489    let scale_p = lut.grid_scale[I];
490    let wf = w_c * scale_p;
491    let w0 = wf.min(scale_p) as usize;
492    let w1 = (wf + 1.).min(scale_p) as usize;
493    let w = wf - w0 as f32;
494
495    let cube0 = &arr[(w0 * lut.grid_filling_size[I] as usize)..];
496    let cube1 = &arr[(w1 * lut.grid_filling_size[I] as usize)..];
497
498    let inputs_sliced = &inputs[0..I];
499    let w0 = handle(lut, cube0, inputs_sliced);
500    let w1 = handle(lut, cube1, inputs_sliced);
501    lerp(w0, w1, NVector::<f32, N>::from(w))
502}
503
504#[inline(never)]
505pub(crate) fn linear_5i_vec3f<const N: usize>(
506    lut: &MultidimensionalLut,
507    arr: &[f32],
508    inputs: &[f32],
509) -> NVector<f32, N> {
510    let lin_w = inputs[4];
511
512    let w_c = lin_w.max(0.).min(1.);
513    let scale_p = lut.grid_scale[4];
514    let wf = w_c * scale_p;
515    let w0 = wf.min(scale_p) as usize;
516    let w1 = (wf + 1.).min(scale_p) as usize;
517    let w = wf - w0 as f32;
518
519    let cube0 = &arr[(w0 * lut.grid_filling_size[4] as usize)..];
520    let cube1 = &arr[(w1 * lut.grid_filling_size[4] as usize)..];
521
522    let w0 = linear_4i_vec3f_direct(lut, cube0, inputs[0], inputs[1], inputs[2], inputs[3]);
523    let w1 = linear_4i_vec3f_direct(lut, cube1, inputs[0], inputs[1], inputs[2], inputs[3]);
524    lerp(w0, w1, NVector::<f32, N>::from(w))
525}
526
527#[inline(never)]
528pub(crate) fn linear_6i_vec3f<const N: usize>(
529    lut: &MultidimensionalLut,
530    arr: &[f32],
531    inputs: &[f32],
532) -> NVector<f32, N> {
533    let f = linear_5i_vec3f::<N>;
534    linear_n_i_vec3f::<N, 5, FHandle<N>>(lut, arr, inputs, f)
535}
536
537#[inline(never)]
538pub(crate) fn linear_7i_vec3f<const N: usize>(
539    lut: &MultidimensionalLut,
540    arr: &[f32],
541    inputs: &[f32],
542) -> NVector<f32, N> {
543    let f = linear_6i_vec3f::<N>;
544    linear_n_i_vec3f::<N, 6, FHandle<N>>(lut, arr, inputs, f)
545}
546
547#[inline(never)]
548pub(crate) fn linear_8i_vec3f<const N: usize>(
549    lut: &MultidimensionalLut,
550    arr: &[f32],
551    inputs: &[f32],
552) -> NVector<f32, N> {
553    let f = linear_7i_vec3f::<N>;
554    linear_n_i_vec3f::<N, 7, FHandle<N>>(lut, arr, inputs, f)
555}
556
557#[inline(never)]
558pub(crate) fn linear_9i_vec3f<const N: usize>(
559    lut: &MultidimensionalLut,
560    arr: &[f32],
561    inputs: &[f32],
562) -> NVector<f32, N> {
563    let f = linear_8i_vec3f::<N>;
564    linear_n_i_vec3f::<N, 8, FHandle<N>>(lut, arr, inputs, f)
565}
566
567#[inline(never)]
568pub(crate) fn linear_10i_vec3f<const N: usize>(
569    lut: &MultidimensionalLut,
570    arr: &[f32],
571    inputs: &[f32],
572) -> NVector<f32, N> {
573    let f = linear_9i_vec3f::<N>;
574    linear_n_i_vec3f::<N, 9, FHandle<N>>(lut, arr, inputs, f)
575}
576
577#[inline(never)]
578pub(crate) fn linear_11i_vec3f<const N: usize>(
579    lut: &MultidimensionalLut,
580    arr: &[f32],
581    inputs: &[f32],
582) -> NVector<f32, N> {
583    let f = linear_10i_vec3f::<N>;
584    linear_n_i_vec3f::<N, 10, FHandle<N>>(lut, arr, inputs, f)
585}
586
587#[inline(never)]
588pub(crate) fn linear_12i_vec3f<const N: usize>(
589    lut: &MultidimensionalLut,
590    arr: &[f32],
591    inputs: &[f32],
592) -> NVector<f32, N> {
593    let f = linear_11i_vec3f::<N>;
594    linear_n_i_vec3f::<N, 11, FHandle<N>>(lut, arr, inputs, f)
595}
596
597#[inline(never)]
598pub(crate) fn linear_13i_vec3f<const N: usize>(
599    lut: &MultidimensionalLut,
600    arr: &[f32],
601    inputs: &[f32],
602) -> NVector<f32, N> {
603    let f = linear_12i_vec3f::<N>;
604    linear_n_i_vec3f::<N, 12, FHandle<N>>(lut, arr, inputs, f)
605}
606
607#[inline(never)]
608pub(crate) fn linear_14i_vec3f<const N: usize>(
609    lut: &MultidimensionalLut,
610    arr: &[f32],
611    inputs: &[f32],
612) -> NVector<f32, N> {
613    let f = linear_13i_vec3f::<N>;
614    linear_n_i_vec3f::<N, 13, FHandle<N>>(lut, arr, inputs, f)
615}
616
617#[inline(never)]
618pub(crate) fn linear_15i_vec3f<const N: usize>(
619    lut: &MultidimensionalLut,
620    arr: &[f32],
621    inputs: &[f32],
622) -> NVector<f32, N> {
623    let f = linear_14i_vec3f::<N>;
624    linear_n_i_vec3f::<N, 14, FHandle<N>>(lut, arr, inputs, f)
625}
626
627#[inline(never)]
628pub(crate) fn tetra_3i_to_any_vec(
629    lut: &MultidimensionalLut,
630    arr: &[f32],
631    x: f32,
632    y: f32,
633    z: f32,
634    dst: &mut [f32],
635    inks: usize,
636) {
637    match inks {
638        1 => {
639            let vec3 = linear_3i_vec3f::<1>(lut, arr, x, y, z);
640            dst[0] = vec3.v[0];
641        }
642        2 => {
643            let vec3 = linear_3i_vec3f::<2>(lut, arr, x, y, z);
644            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
645                *dst = *src;
646            }
647        }
648        3 => {
649            let vec3 = linear_3i_vec3f::<3>(lut, arr, x, y, z);
650            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
651                *dst = *src;
652            }
653        }
654        4 => {
655            let vec3 = linear_3i_vec3f::<4>(lut, arr, x, y, z);
656            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
657                *dst = *src;
658            }
659        }
660        5 => {
661            let vec3 = linear_3i_vec3f::<5>(lut, arr, x, y, z);
662            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
663                *dst = *src;
664            }
665        }
666        6 => {
667            let vec3 = linear_3i_vec3f::<6>(lut, arr, x, y, z);
668            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
669                *dst = *src;
670            }
671        }
672        7 => {
673            let vec3 = linear_3i_vec3f::<7>(lut, arr, x, y, z);
674            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
675                *dst = *src;
676            }
677        }
678        8 => {
679            let vec3 = linear_3i_vec3f::<8>(lut, arr, x, y, z);
680            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
681                *dst = *src;
682            }
683        }
684        9 => {
685            let vec3 = linear_3i_vec3f::<9>(lut, arr, x, y, z);
686            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
687                *dst = *src;
688            }
689        }
690        10 => {
691            let vec3 = linear_3i_vec3f::<10>(lut, arr, x, y, z);
692            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
693                *dst = *src;
694            }
695        }
696        11 => {
697            let vec3 = linear_3i_vec3f::<11>(lut, arr, x, y, z);
698            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
699                *dst = *src;
700            }
701        }
702        12 => {
703            let vec3 = linear_3i_vec3f::<12>(lut, arr, x, y, z);
704            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
705                *dst = *src;
706            }
707        }
708        13 => {
709            let vec3 = linear_3i_vec3f::<13>(lut, arr, x, y, z);
710            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
711                *dst = *src;
712            }
713        }
714        14 => {
715            let vec3 = linear_3i_vec3f::<14>(lut, arr, x, y, z);
716            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
717                *dst = *src;
718            }
719        }
720        15 => {
721            let vec3 = linear_3i_vec3f::<15>(lut, arr, x, y, z);
722            for (dst, src) in dst.iter_mut().zip(vec3.v.iter()) {
723                *dst = *src;
724            }
725        }
726        _ => unreachable!(),
727    }
728}