#![allow(dead_code)]
#[derive(Clone, Debug)]
pub struct NffLight {
pub position: [f32; 3],
pub color: [f32; 3],
}
#[derive(Clone, Debug)]
pub struct NffSurface {
pub color: [f32; 3],
pub kd: f32,
pub ks: f32,
pub shine: f32,
pub transmittance: f32,
pub ior: f32,
}
impl Default for NffSurface {
fn default() -> Self {
Self {
color: [0.8, 0.8, 0.8],
kd: 1.0,
ks: 0.0,
shine: 0.0,
transmittance: 0.0,
ior: 1.0,
}
}
}
#[derive(Clone, Debug)]
pub struct NffPolygon {
pub vertices: Vec<[f32; 3]>,
pub surface: NffSurface,
}
#[derive(Clone, Debug)]
pub struct NffSphere {
pub center: [f32; 3],
pub radius: f32,
pub surface: NffSurface,
}
#[derive(Clone, Debug, Default)]
pub struct NffDocument {
pub background: [f32; 3],
pub lights: Vec<NffLight>,
pub polygons: Vec<NffPolygon>,
pub spheres: Vec<NffSphere>,
}
pub fn new_nff_document() -> NffDocument {
NffDocument {
background: [1.0, 1.0, 1.0],
..Default::default()
}
}
pub fn nff_set_background(doc: &mut NffDocument, color: [f32; 3]) {
doc.background = color;
}
pub fn nff_add_light(doc: &mut NffDocument, position: [f32; 3], color: [f32; 3]) {
doc.lights.push(NffLight { position, color });
}
pub fn nff_add_polygon(doc: &mut NffDocument, vertices: Vec<[f32; 3]>, surface: NffSurface) {
doc.polygons.push(NffPolygon { vertices, surface });
}
pub fn nff_add_sphere(doc: &mut NffDocument, center: [f32; 3], radius: f32, surface: NffSurface) {
doc.spheres.push(NffSphere {
center,
radius,
surface,
});
}
pub fn nff_add_mesh(
doc: &mut NffDocument,
positions: &[[f32; 3]],
indices: &[u32],
surface: NffSurface,
) {
for tri in indices.chunks(3) {
if tri.len() == 3 {
let verts = vec![
positions[tri[0] as usize],
positions[tri[1] as usize],
positions[tri[2] as usize],
];
doc.polygons.push(NffPolygon {
vertices: verts,
surface: surface.clone(),
});
}
}
}
pub fn nff_light_count(doc: &NffDocument) -> usize {
doc.lights.len()
}
pub fn nff_polygon_count(doc: &NffDocument) -> usize {
doc.polygons.len()
}
pub fn nff_sphere_count(doc: &NffDocument) -> usize {
doc.spheres.len()
}
pub fn nff_primitive_count(doc: &NffDocument) -> usize {
doc.polygons.len() + doc.spheres.len()
}
fn render_nff_surface(s: &NffSurface) -> String {
format!(
"f {:.4} {:.4} {:.4} {:.4} {:.4} {:.4} {:.4} {:.4}\n",
s.color[0], s.color[1], s.color[2], s.kd, s.ks, s.shine, s.transmittance, s.ior
)
}
pub fn render_nff(doc: &NffDocument) -> String {
let mut out = String::from("# NFF scene — generated by oxihuman\n");
out.push_str(&format!(
"b {:.4} {:.4} {:.4}\n",
doc.background[0], doc.background[1], doc.background[2]
));
for light in &doc.lights {
let [lx, ly, lz] = light.position;
let [lr, lg, lb] = light.color;
out.push_str(&format!(
"l {lx:.4} {ly:.4} {lz:.4} {lr:.4} {lg:.4} {lb:.4}\n"
));
}
for sph in &doc.spheres {
out.push_str(&render_nff_surface(&sph.surface));
let [cx, cy, cz] = sph.center;
out.push_str(&format!("s {cx:.4} {cy:.4} {cz:.4} {:.4}\n", sph.radius));
}
for poly in &doc.polygons {
out.push_str(&render_nff_surface(&poly.surface));
out.push_str(&format!("p {}\n", poly.vertices.len()));
for v in &poly.vertices {
out.push_str(&format!("{:.6} {:.6} {:.6}\n", v[0], v[1], v[2]));
}
}
out
}
pub fn nff_size_estimate(doc: &NffDocument) -> usize {
render_nff(doc).len()
}
pub fn validate_nff(doc: &NffDocument) -> bool {
let polys_ok = doc.polygons.iter().all(|p| p.vertices.len() >= 3);
let spheres_ok = doc.spheres.iter().all(|s| s.radius > 0.0);
polys_ok && spheres_ok
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_doc() -> NffDocument {
let mut doc = new_nff_document();
nff_add_light(&mut doc, [1.0, 2.0, 3.0], [1.0, 1.0, 1.0]);
nff_add_polygon(
&mut doc,
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
NffSurface::default(),
);
nff_add_sphere(&mut doc, [0.0, 0.0, 0.0], 1.0, NffSurface::default());
doc
}
#[test]
fn light_count() {
assert_eq!(nff_light_count(&simple_doc()), 1);
}
#[test]
fn polygon_count() {
assert_eq!(nff_polygon_count(&simple_doc()), 1);
}
#[test]
fn sphere_count() {
assert_eq!(nff_sphere_count(&simple_doc()), 1);
}
#[test]
fn primitive_count() {
assert_eq!(nff_primitive_count(&simple_doc()), 2);
}
#[test]
fn render_starts_with_comment() {
let s = render_nff(&simple_doc());
assert!(s.starts_with("# NFF"));
}
#[test]
fn render_contains_background() {
let s = render_nff(&simple_doc());
assert!(s.contains("\nb "));
}
#[test]
fn render_contains_polygon_marker() {
let s = render_nff(&simple_doc());
assert!(s.contains("\np 3"));
}
#[test]
fn render_contains_sphere_marker() {
let s = render_nff(&simple_doc());
assert!(s.contains("\ns "));
}
#[test]
fn validate_valid_doc() {
assert!(validate_nff(&simple_doc()));
}
#[test]
fn add_mesh_creates_polygons() {
let mut doc = new_nff_document();
nff_add_mesh(
&mut doc,
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
&[0, 1, 2],
NffSurface::default(),
);
assert_eq!(nff_polygon_count(&doc), 1);
}
#[test]
fn size_estimate_positive() {
assert!(nff_size_estimate(&simple_doc()) > 0);
}
}