1#![allow(dead_code)]
4
5#[derive(Clone, Debug)]
10pub struct NffLight {
11 pub position: [f32; 3],
12 pub color: [f32; 3],
13}
14
15#[derive(Clone, Debug)]
17pub struct NffSurface {
18 pub color: [f32; 3],
19 pub kd: f32,
20 pub ks: f32,
21 pub shine: f32,
22 pub transmittance: f32,
23 pub ior: f32,
24}
25
26impl Default for NffSurface {
27 fn default() -> Self {
28 Self {
29 color: [0.8, 0.8, 0.8],
30 kd: 1.0,
31 ks: 0.0,
32 shine: 0.0,
33 transmittance: 0.0,
34 ior: 1.0,
35 }
36 }
37}
38
39#[derive(Clone, Debug)]
41pub struct NffPolygon {
42 pub vertices: Vec<[f32; 3]>,
43 pub surface: NffSurface,
44}
45
46#[derive(Clone, Debug)]
48pub struct NffSphere {
49 pub center: [f32; 3],
50 pub radius: f32,
51 pub surface: NffSurface,
52}
53
54#[derive(Clone, Debug, Default)]
56pub struct NffDocument {
57 pub background: [f32; 3],
58 pub lights: Vec<NffLight>,
59 pub polygons: Vec<NffPolygon>,
60 pub spheres: Vec<NffSphere>,
61}
62
63pub fn new_nff_document() -> NffDocument {
65 NffDocument {
66 background: [1.0, 1.0, 1.0],
67 ..Default::default()
68 }
69}
70
71pub fn nff_set_background(doc: &mut NffDocument, color: [f32; 3]) {
73 doc.background = color;
74}
75
76pub fn nff_add_light(doc: &mut NffDocument, position: [f32; 3], color: [f32; 3]) {
78 doc.lights.push(NffLight { position, color });
79}
80
81pub fn nff_add_polygon(doc: &mut NffDocument, vertices: Vec<[f32; 3]>, surface: NffSurface) {
83 doc.polygons.push(NffPolygon { vertices, surface });
84}
85
86pub fn nff_add_sphere(doc: &mut NffDocument, center: [f32; 3], radius: f32, surface: NffSurface) {
88 doc.spheres.push(NffSphere {
89 center,
90 radius,
91 surface,
92 });
93}
94
95pub fn nff_add_mesh(
97 doc: &mut NffDocument,
98 positions: &[[f32; 3]],
99 indices: &[u32],
100 surface: NffSurface,
101) {
102 for tri in indices.chunks(3) {
103 if tri.len() == 3 {
104 let verts = vec![
105 positions[tri[0] as usize],
106 positions[tri[1] as usize],
107 positions[tri[2] as usize],
108 ];
109 doc.polygons.push(NffPolygon {
110 vertices: verts,
111 surface: surface.clone(),
112 });
113 }
114 }
115}
116
117pub fn nff_light_count(doc: &NffDocument) -> usize {
119 doc.lights.len()
120}
121
122pub fn nff_polygon_count(doc: &NffDocument) -> usize {
124 doc.polygons.len()
125}
126
127pub fn nff_sphere_count(doc: &NffDocument) -> usize {
129 doc.spheres.len()
130}
131
132pub fn nff_primitive_count(doc: &NffDocument) -> usize {
134 doc.polygons.len() + doc.spheres.len()
135}
136
137fn render_nff_surface(s: &NffSurface) -> String {
138 format!(
139 "f {:.4} {:.4} {:.4} {:.4} {:.4} {:.4} {:.4} {:.4}\n",
140 s.color[0], s.color[1], s.color[2], s.kd, s.ks, s.shine, s.transmittance, s.ior
141 )
142}
143
144pub fn render_nff(doc: &NffDocument) -> String {
146 let mut out = String::from("# NFF scene — generated by oxihuman\n");
147 out.push_str(&format!(
149 "b {:.4} {:.4} {:.4}\n",
150 doc.background[0], doc.background[1], doc.background[2]
151 ));
152 for light in &doc.lights {
154 let [lx, ly, lz] = light.position;
155 let [lr, lg, lb] = light.color;
156 out.push_str(&format!(
157 "l {lx:.4} {ly:.4} {lz:.4} {lr:.4} {lg:.4} {lb:.4}\n"
158 ));
159 }
160 for sph in &doc.spheres {
162 out.push_str(&render_nff_surface(&sph.surface));
163 let [cx, cy, cz] = sph.center;
164 out.push_str(&format!("s {cx:.4} {cy:.4} {cz:.4} {:.4}\n", sph.radius));
165 }
166 for poly in &doc.polygons {
168 out.push_str(&render_nff_surface(&poly.surface));
169 out.push_str(&format!("p {}\n", poly.vertices.len()));
170 for v in &poly.vertices {
171 out.push_str(&format!("{:.6} {:.6} {:.6}\n", v[0], v[1], v[2]));
172 }
173 }
174 out
175}
176
177pub fn nff_size_estimate(doc: &NffDocument) -> usize {
179 render_nff(doc).len()
180}
181
182pub fn validate_nff(doc: &NffDocument) -> bool {
184 let polys_ok = doc.polygons.iter().all(|p| p.vertices.len() >= 3);
185 let spheres_ok = doc.spheres.iter().all(|s| s.radius > 0.0);
186 polys_ok && spheres_ok
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 fn simple_doc() -> NffDocument {
194 let mut doc = new_nff_document();
195 nff_add_light(&mut doc, [1.0, 2.0, 3.0], [1.0, 1.0, 1.0]);
196 nff_add_polygon(
197 &mut doc,
198 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
199 NffSurface::default(),
200 );
201 nff_add_sphere(&mut doc, [0.0, 0.0, 0.0], 1.0, NffSurface::default());
202 doc
203 }
204
205 #[test]
206 fn light_count() {
207 assert_eq!(nff_light_count(&simple_doc()), 1);
208 }
209
210 #[test]
211 fn polygon_count() {
212 assert_eq!(nff_polygon_count(&simple_doc()), 1);
213 }
214
215 #[test]
216 fn sphere_count() {
217 assert_eq!(nff_sphere_count(&simple_doc()), 1);
218 }
219
220 #[test]
221 fn primitive_count() {
222 assert_eq!(nff_primitive_count(&simple_doc()), 2);
223 }
224
225 #[test]
226 fn render_starts_with_comment() {
227 let s = render_nff(&simple_doc());
228 assert!(s.starts_with("# NFF"));
229 }
230
231 #[test]
232 fn render_contains_background() {
233 let s = render_nff(&simple_doc());
234 assert!(s.contains("\nb "));
235 }
236
237 #[test]
238 fn render_contains_polygon_marker() {
239 let s = render_nff(&simple_doc());
240 assert!(s.contains("\np 3"));
241 }
242
243 #[test]
244 fn render_contains_sphere_marker() {
245 let s = render_nff(&simple_doc());
246 assert!(s.contains("\ns "));
247 }
248
249 #[test]
250 fn validate_valid_doc() {
251 assert!(validate_nff(&simple_doc()));
252 }
253
254 #[test]
255 fn add_mesh_creates_polygons() {
256 let mut doc = new_nff_document();
257 nff_add_mesh(
258 &mut doc,
259 &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
260 &[0, 1, 2],
261 NffSurface::default(),
262 );
263 assert_eq!(nff_polygon_count(&doc), 1);
264 }
265
266 #[test]
267 fn size_estimate_positive() {
268 assert!(nff_size_estimate(&simple_doc()) > 0);
269 }
270}