1const PI1: u64 = 2_654_435_761;
23const PI2: u64 = 805_459_861;
24const PI3: u64 = 3_674_653_429;
25
26const LCG_A: u64 = 6_364_136_223_846_793_005;
29const LCG_C: u64 = 1_442_695_040_888_963_407;
30
31struct Lcg(u64);
32
33impl Lcg {
34 fn new(seed: u64) -> Self {
35 Self(seed.wrapping_add(1))
36 }
37 fn next_f64(&mut self) -> f64 {
38 self.0 = self.0.wrapping_mul(LCG_A).wrapping_add(LCG_C);
39 (self.0 >> 11) as f64 / (1u64 << 53) as f64
40 }
41 fn next_uniform(&mut self, half: f64) -> f64 {
43 self.next_f64() * 2.0 * half - half
44 }
45}
46
47pub fn hash_coords(x: i32, y: i32, z: i32, table_size: usize) -> usize {
61 let hx = (x as i64 as u64).wrapping_mul(PI1);
62 let hy = (y as i64 as u64).wrapping_mul(PI2);
63 let hz = (z as i64 as u64).wrapping_mul(PI3);
64 ((hx ^ hy ^ hz) as usize) % table_size
65}
66
67pub struct HashEncoder {
80 pub hash_tables: Vec<Vec<Vec<f64>>>,
82 pub n_levels: usize,
84 pub n_features_per_level: usize,
86 pub base_resolution: usize,
88 pub resolution_growth: f64,
90 pub table_size: usize,
92}
93
94impl HashEncoder {
95 pub fn new(
108 n_levels: usize,
109 n_features_per_level: usize,
110 log2_hashmap_size: usize,
111 base_resolution: usize,
112 finest_resolution: usize,
113 seed: u64,
114 ) -> Self {
115 let table_size = 1_usize << log2_hashmap_size.min(30);
116 let resolution_growth = if n_levels > 1 {
117 (finest_resolution as f64 / base_resolution as f64).powf(1.0 / (n_levels - 1) as f64)
118 } else {
119 1.0
120 };
121
122 let mut rng = Lcg::new(seed);
123 let hash_tables: Vec<Vec<Vec<f64>>> = (0..n_levels)
124 .map(|_| {
125 (0..table_size)
126 .map(|_| {
127 (0..n_features_per_level)
128 .map(|_| rng.next_uniform(0.0001))
129 .collect()
130 })
131 .collect()
132 })
133 .collect();
134
135 Self {
136 hash_tables,
137 n_levels,
138 n_features_per_level,
139 base_resolution,
140 resolution_growth,
141 table_size,
142 }
143 }
144
145 #[inline]
147 pub fn n_output_features(&self) -> usize {
148 self.n_levels * self.n_features_per_level
149 }
150
151 fn level_resolution(&self, l: usize) -> f64 {
153 self.base_resolution as f64 * self.resolution_growth.powi(l as i32)
154 }
155
156 fn get_feature(&self, l: usize, idx: usize) -> &[f64] {
158 &self.hash_tables[l][idx % self.table_size]
159 }
160
161 pub fn lookup_features(&self, pos: &[f64; 3], level: usize) -> Vec<f64> {
166 let res = self.level_resolution(level);
167 let px = pos[0] * res;
169 let py = pos[1] * res;
170 let pz = pos[2] * res;
171
172 let x0 = px.floor() as i32;
174 let y0 = py.floor() as i32;
175 let z0 = pz.floor() as i32;
176
177 let wx = px - x0 as f64;
179 let wy = py - y0 as f64;
180 let wz = pz - z0 as f64;
181
182 let x1 = x0 + 1;
183 let y1 = y0 + 1;
184 let z1 = z0 + 1;
185
186 let corners = [
188 (x0, y0, z0, (1.0 - wx) * (1.0 - wy) * (1.0 - wz)),
190 (x1, y0, z0, wx * (1.0 - wy) * (1.0 - wz)),
191 (x0, y1, z0, (1.0 - wx) * wy * (1.0 - wz)),
192 (x1, y1, z0, wx * wy * (1.0 - wz)),
193 (x0, y0, z1, (1.0 - wx) * (1.0 - wy) * wz),
194 (x1, y0, z1, wx * (1.0 - wy) * wz),
195 (x0, y1, z1, (1.0 - wx) * wy * wz),
196 (x1, y1, z1, wx * wy * wz),
197 ];
198
199 let mut out = vec![0.0_f64; self.n_features_per_level];
200 for (cx, cy, cz, w) in &corners {
201 let idx = hash_coords(*cx, *cy, *cz, self.table_size);
202 let feat = self.get_feature(level, idx);
203 for (o, &f) in out.iter_mut().zip(feat.iter()) {
204 *o += w * f;
205 }
206 }
207 out
208 }
209
210 pub fn encode(&self, pos: &[f64; 3]) -> Vec<f64> {
215 let mut out = Vec::with_capacity(self.n_output_features());
216 for l in 0..self.n_levels {
217 out.extend_from_slice(&self.lookup_features(pos, l));
218 }
219 out
220 }
221}
222
223pub struct InstantNgpMlp {
230 w0: Vec<Vec<f64>>, b0: Vec<f64>,
232 w1: Vec<Vec<f64>>, b1: Vec<f64>,
234 encoder: HashEncoder,
235 n_freq_dir: usize,
236 hidden_dim: usize,
237}
238
239impl InstantNgpMlp {
240 pub fn new(encoder: HashEncoder, n_freq_dir: usize, hidden_dim: usize, seed: u64) -> Self {
249 use super::positional_encoding::encoding_dim;
250
251 let dir_dim = encoding_dim(n_freq_dir);
252 let in_dim = encoder.n_output_features() + dir_dim;
253 let out_dim = 4; let mut rng = Lcg::new(seed ^ 0x1234_5678);
256
257 let scale0 = (2.0 / in_dim as f64).sqrt();
258 let w0: Vec<Vec<f64>> = (0..hidden_dim)
259 .map(|_| {
260 (0..in_dim)
261 .map(|_| rng.next_f64() * 2.0 * scale0 - scale0)
262 .collect()
263 })
264 .collect();
265 let b0 = vec![0.0_f64; hidden_dim];
266
267 let scale1 = (2.0 / hidden_dim as f64).sqrt();
268 let w1: Vec<Vec<f64>> = (0..out_dim)
269 .map(|_| {
270 (0..hidden_dim)
271 .map(|_| rng.next_f64() * 2.0 * scale1 - scale1)
272 .collect()
273 })
274 .collect();
275 let b1 = vec![0.0_f64; out_dim];
276
277 Self {
278 w0,
279 b0,
280 w1,
281 b1,
282 encoder,
283 n_freq_dir,
284 hidden_dim,
285 }
286 }
287
288 pub fn forward(&self, pos: &[f64; 3], dir: &[f64; 3]) -> (f64, [f64; 3]) {
297 use super::positional_encoding::encode_direction;
298
299 let hash_feat = self.encoder.encode(pos);
301 let dir_feat = encode_direction(dir, self.n_freq_dir);
302
303 let mut inp: Vec<f64> = Vec::with_capacity(hash_feat.len() + dir_feat.len());
304 inp.extend_from_slice(&hash_feat);
305 inp.extend_from_slice(&dir_feat);
306
307 let h: Vec<f64> = (0..self.hidden_dim)
309 .map(|i| {
310 let raw: f64 = self.b0[i]
311 + self.w0[i]
312 .iter()
313 .zip(inp.iter())
314 .map(|(w, x)| w * x)
315 .sum::<f64>();
316 raw.max(0.0)
317 })
318 .collect();
319
320 let raw_out: Vec<f64> = (0..4)
322 .map(|i| {
323 self.b1[i]
324 + self.w1[i]
325 .iter()
326 .zip(h.iter())
327 .map(|(w, x)| w * x)
328 .sum::<f64>()
329 })
330 .collect();
331
332 let density = raw_out[0].max(0.0); let rgb = [
334 sigmoid(raw_out[1]),
335 sigmoid(raw_out[2]),
336 sigmoid(raw_out[3]),
337 ];
338 (density, rgb)
339 }
340}
341
342#[inline]
343fn sigmoid(x: f64) -> f64 {
344 1.0 / (1.0 + (-x).exp())
345}
346
347#[cfg(test)]
350mod tests {
351 use super::*;
352
353 fn build_encoder() -> HashEncoder {
354 HashEncoder::new(4, 2, 12, 16, 128, 42)
355 }
356
357 #[test]
358 fn test_hash_function_deterministic() {
359 let table_size = 1 << 16;
361 let h1 = hash_coords(3, -7, 11, table_size);
362 let h2 = hash_coords(3, -7, 11, table_size);
363 assert_eq!(h1, h2);
364
365 let h3 = hash_coords(3, -7, 12, table_size);
367 let h4 = hash_coords(4, -7, 11, table_size);
368 assert!(
370 h1 != h3 || h1 != h4,
371 "suspicious: three distinct coords all hash to {h1}"
372 );
373 }
374
375 #[test]
376 fn test_hash_table_lookup_shape() {
377 let enc = build_encoder();
378 let feat = enc.lookup_features(&[0.25, 0.5, 0.75], 0);
379 assert_eq!(feat.len(), enc.n_features_per_level);
380 }
381
382 #[test]
383 fn test_hash_encoder_output_dim() {
384 let enc = build_encoder();
385 let out = enc.encode(&[0.1, 0.2, 0.3]);
386 assert_eq!(out.len(), enc.n_output_features());
387 assert_eq!(
388 enc.n_output_features(),
389 enc.n_levels * enc.n_features_per_level
390 );
391 }
392
393 #[test]
394 fn test_trilinear_interpolation_corners() {
395 let enc = build_encoder();
400 let level = 0;
401 let res = enc.level_resolution(level);
402
403 let pos_corner = [0.0_f64, 0.0, 0.0];
405
406 let expected_idx = hash_coords(0, 0, 0, enc.table_size);
408 let expected_feat = enc.get_feature(level, expected_idx).to_vec();
409
410 let result = enc.lookup_features(&pos_corner, level);
411 for (i, (&r, &e)) in result.iter().zip(expected_feat.iter()).enumerate() {
412 assert!(
413 (r - e).abs() < 1e-12,
414 "feature[{i}] mismatch at corner: got {r}, expected {e}"
415 );
416 }
417
418 let _ = res;
420 }
421
422 #[test]
423 fn test_instant_ngp_mlp_forward() {
424 let encoder = HashEncoder::new(4, 2, 12, 16, 128, 77);
425 let mlp = InstantNgpMlp::new(encoder, 2, 32, 99);
426 let pos = [0.3, 0.4, 0.5];
427 let dir = [0.0, 1.0, 0.0];
428 let (density, rgb) = mlp.forward(&pos, &dir);
429
430 assert!(density >= 0.0, "density must be >= 0, got {density}");
431 for (c, &ch) in rgb.iter().enumerate() {
432 assert!(
433 (0.0..=1.0).contains(&ch),
434 "rgb[{c}] = {ch} must be in [0,1]"
435 );
436 }
437 }
438}