pbrt_r3/core/spectrum/
utils.rs

1include!(concat!(env!("OUT_DIR"), "/spectrum_utils.rs"));
2
3use crate::core::base::*;
4
5pub fn spectrum_samples_sorted(lambda: &[Float], _vals: &[Float]) -> bool {
6    for i in 0..(lambda.len() - 1) {
7        if lambda[i] > lambda[i + 1] {
8            return false;
9        }
10    }
11    return true;
12}
13
14pub fn sort_spectrum_samples(lambda: &mut [Float], vals: &mut [Float]) {
15    let n = lambda.len();
16    let mut sort_vec: Vec<(Float, Float)> = Vec::with_capacity(n);
17    for i in 0..n {
18        sort_vec.push((lambda[i], vals[i]));
19    }
20    sort_vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
21    for i in 0..n {
22        lambda[i] = sort_vec[i].0;
23        vals[i] = sort_vec[i].1;
24    }
25}
26
27pub fn interpolate_spectrum_samples(lambda: &[Float], vals: &[Float], l: Float) -> Float {
28    //for (int i = 0; i < n - 1; ++i) CHECK_GT(lambda[i + 1], lambda[i]);
29    let n = lambda.len();
30    if l <= lambda[0] {
31        return vals[0];
32    }
33    if l >= lambda[n - 1] {
34        return vals[n - 1];
35    }
36    let offset = find_interval(lambda, &|v, index| -> bool {
37        return v[index] <= l;
38    });
39    //CHECK(l >= lambda[offset] && l <= lambda[offset + 1]);
40    let t = (l - lambda[offset]) / (lambda[offset + 1] - lambda[offset]);
41    return lerp(t, vals[offset], vals[offset + 1]);
42}
43
44// Given a piecewise-linear SPD with values in vIn[] at corresponding
45// wavelengths lambdaIn[], where lambdaIn is assumed to be sorted but may
46// be irregularly spaced, resample the spectrum over the range of
47// wavelengths [lambdaMin, lambdaMax], with a total of nOut wavelength
48// samples between lambdaMin and lamdbaMax (including those at
49// endpoints). The resampled spectrum values are written to vOut.
50//
51// In general, this is a standard sampling and reconstruction problem, with
52// the complication that for any given invocation, some of the
53// reconstruction points may involve upsampling the input distribution and
54// others may involve downsampling. For upsampling, we just point-sample,
55// and for downsampling, we apply a box filter centered around the
56// destination wavelength with total width equal to the sample spacing.
57pub fn resample_linear_spectrum(
58    lambda_in: &[Float],
59    v_in: &[Float],
60    lambda_min: Float,
61    lambda_max: Float,
62    v_out: &mut [Float],
63) {
64    let n_in = lambda_in.len();
65    let n_out = v_out.len();
66    assert!(n_out > 2);
67    for i in 0..(n_in - 1) {
68        assert!(lambda_in[i] <= lambda_in[i + 1]);
69    }
70    assert!(lambda_min < lambda_max);
71
72    // Spacing between samples in the output distribution.
73    let delta = (lambda_max - lambda_min) / (n_out - 1) as Float;
74
75    // We assume that the SPD is constant outside of the specified
76    // wavelength range, taking on the respectively first/last SPD value
77    // for out-of-range wavelengths.
78    //
79    // To make this convention fit cleanly into the code below, we create
80    // virtual samples in the input distribution with index -1 for the
81    // sample before the first valid sample and index nIn for the sample
82    // after the last valid sample. In turn, can place those virtual
83    // samples beyond the endpoints of the target range so that we can
84    // always assume that the source range is broader than the target
85    // range, which in turn lets us not worry about various boundary cases
86    // below.
87
88    // The wavelengths of the virtual samples at the endpoints are set so
89    // that they are one destination sample spacing beyond the destination
90    // endpoints.  (Note that this potentially means that if we swept along
91    // indices from -1 to nIn, we wouldn't always see a monotonically
92    // increasing set of wavelength values. However, this isn't a problem
93    // since we only access these virtual samples if the destination range
94    // is wider than the source range.)
95    let lambda_in_clamped = |index: i32| -> Float {
96        assert!(index >= -1 && index <= n_in as i32);
97        if index == -1 {
98            assert!(lambda_min - delta < lambda_in[0]);
99            return lambda_min - delta;
100        } else if index == n_in as i32 {
101            assert!(lambda_max + delta > lambda_in[n_in - 1]);
102            return lambda_max + delta;
103        } else {
104            return lambda_in[index as usize];
105        }
106    };
107
108    // Due to the piecewise-constant assumption, the SPD values outside the
109    // specified range are given by the valid endpoints.
110    let v_in_clamped = |index: i32| -> Float {
111        assert!(index >= -1 && index <= n_in as i32);
112        return v_in[index.clamp(0, n_in as i32 - 1) as usize];
113    };
114
115    // Helper that upsamples ors downsample the given SPD at the given
116    // wavelength lambda.
117    let resample = |lambda: Float| -> Float {
118        // Handle the edge cases first so that we don't need to worry about
119        // them in the following.
120        //
121        // First, if the entire filtering range for the destination is
122        // outside of the range of valid samples, we can just return the
123        // endpoint value.
124
125        if lambda + delta / 2.0 <= lambda_in[0] {
126            return v_in[0];
127        }
128        if lambda - delta / 2.0 >= lambda_in[n_in - 1] {
129            return v_in[n_in - 1];
130        }
131        // Second, if there's only one sample, then the SPD has the same
132        // value everywhere, and we're done.
133        if n_in == 1 {
134            return v_in[0];
135        }
136
137        // Otherwise, find indices into the input SPD that bracket the
138        // wavelength range [lambda-delta, lambda+delta]. Note that this is
139        // a 2x wider range than we will actually filter over in the end.
140        let start;
141        let mut end;
142        if lambda - delta < lambda_in[0] {
143            // Virtual sample at the start, as described above.
144            start = -1;
145        } else {
146            start = find_interval(lambda_in, &|v, index| -> bool {
147                return v[index] <= lambda - delta;
148            }) as i32;
149            assert!(start >= 0 && start < n_in as i32);
150        }
151
152        if lambda + delta > lambda_in[n_in - 1] {
153            // Virtual sample at the end, as described above.
154            end = n_in as i32;
155        } else {
156            // Linear search from the starting point. (Presumably more
157            // efficient than a binary search from scratch, or doesn't
158            // matter either way.)
159            end = if start > 0 { start } else { 0 };
160            while end < n_in as i32 && lambda + delta > lambda_in[end as usize] {
161                end += 1;
162            }
163        }
164
165        if end - start == 2
166            && lambda_in_clamped(start) <= lambda - delta
167            && lambda_in[(start + 1) as usize] == lambda
168            && lambda_in_clamped(end) >= lambda + delta
169        {
170            // Downsampling: special case where the input and output
171            // wavelengths line up perfectly, so just return the
172            // corresponding point sample at lambda.
173            return v_in[(start + 1) as usize];
174        } else if end - start == 1 {
175            // Downsampling: evaluate the piecewise-linear function at
176            // lambda.
177            let t = (lambda - lambda_in_clamped(start))
178                / (lambda_in_clamped(end) - lambda_in_clamped(start));
179            assert!(t >= 0.0 && t <= 1.0);
180            return lerp(t, v_in_clamped(start), v_in_clamped(end));
181        } else {
182            // Upsampling: use a box filter and average all values in the
183            // input spectrum from lambda +/- delta / 2.
184            return average_spectrum_samples(
185                lambda_in,
186                v_in,
187                lambda - delta / 2.0,
188                lambda + delta / 2.0,
189            );
190        }
191    };
192
193    // For each destination sample, compute the wavelength lambda for the
194    // sample and then resample the source SPD distribution at that point.
195    for out_offset in 0..n_out {
196        // TODO: Currently, resample() does a binary search each time,
197        // even though we could do a single sweep across the input array,
198        // since we're resampling it at a regular and increasing set of
199        // lambdas. It would be nice to polish that up.
200        let lambda = lerp(
201            out_offset as Float / (n_out as Float - 1.0),
202            lambda_min,
203            lambda_max,
204        );
205        v_out[out_offset] = resample(lambda);
206    }
207}