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}