use std::io::Cursor;
use imodfile::*;
fn create_test_model() -> Imod {
let mut model = Imod::default();
model.name = "TestModel".to_string();
model.xmax = 100;
model.ymax = 200;
model.zmax = 50;
model.objsize = 1;
let mut obj = Iobj::default();
obj.name = "Object1".to_string();
obj.contsize = 2;
obj.red = 1.0;
obj.green = 0.5;
obj.blue = 0.0;
obj.flags = IMOD_OBJFLAG_DRAW_LABEL | IMOD_OBJFLAG_SCALE_WDTH | IMOD_OBJFLAG_FILL;
let mut cont1 = Icont::default();
cont1.psize = 3;
cont1.pts = vec![
Ipoint { x: 0.0, y: 0.0, z: 0.0 },
Ipoint { x: 10.0, y: 0.0, z: 0.0 },
Ipoint { x: 0.0, y: 10.0, z: 0.0 },
];
cont1.surf = 0;
let mut cont2 = Icont::default();
cont2.psize = 4;
cont2.pts = vec![
Ipoint { x: 0.0, y: 0.0, z: 10.0 },
Ipoint { x: 20.0, y: 0.0, z: 10.0 },
Ipoint { x: 20.0, y: 20.0, z: 10.0 },
Ipoint { x: 0.0, y: 20.0, z: 10.0 },
];
cont2.surf = 0;
cont2.flags = 1;
cont2.time = 5;
obj.cont = vec![cont1, cont2];
let mut mesh = Imesh::default();
mesh.vsize = 4;
mesh.lsize = 5;
mesh.vert = vec![
Ipoint { x: 0.0, y: 0.0, z: 0.0 },
Ipoint { x: 10.0, y: 0.0, z: 0.0 },
Ipoint { x: 10.0, y: 10.0, z: 0.0 },
Ipoint { x: 0.0, y: 10.0, z: 0.0 },
];
mesh.list = vec![0, 1, 2, 3, -1]; mesh.flag = 0;
obj.mesh = vec![mesh];
obj.meshsize = 1;
let mut view = Iview::default();
view.label = "Default View".to_string();
view.fovy = 30.0;
model.obj = vec![obj];
model.view = vec![view];
model.viewsize = 1;
model
}
#[test]
fn test_binary_roundtrip() {
let model = create_test_model();
let mut buf = Vec::new();
let mut cursor = Cursor::new(&mut buf);
write_binary(&mut cursor, &model).expect("write_binary failed");
cursor.set_position(0);
let model2 = read_binary(&mut cursor).expect("read_binary failed");
assert_eq!(model.name, model2.name);
assert_eq!(model.obj.len(), model2.obj.len());
assert_eq!(model.xmax, model2.xmax);
assert_eq!(model.ymax, model2.ymax);
assert_eq!(model.zmax, model2.zmax);
let obj = &model.obj[0];
let obj2 = &model2.obj[0];
assert_eq!(obj.name, obj2.name);
assert_eq!(obj.contsize, obj2.contsize);
assert_eq!(obj.red, obj2.red);
assert_eq!(obj.green, obj2.green);
assert_eq!(obj.blue, obj2.blue);
assert_eq!(obj.flags, obj2.flags);
assert_eq!(obj.meshsize, obj2.meshsize);
for i in 0..obj.contsize as usize {
assert_eq!(obj.cont[i].psize, obj2.cont[i].psize);
assert_eq!(obj.cont[i].surf, obj2.cont[i].surf);
assert_eq!(obj.cont[i].flags, obj2.cont[i].flags);
assert_eq!(obj.cont[i].time, obj2.cont[i].time);
for j in 0..obj.cont[i].psize as usize {
let a = obj.cont[i].pts[j];
let b = obj2.cont[i].pts[j];
assert!((a.x - b.x).abs() < 1e-5, "point {} cont {} x: {} != {}", j, i, a.x, b.x);
assert!((a.y - b.y).abs() < 1e-5);
assert!((a.z - b.z).abs() < 1e-5);
}
}
let mesh = &obj.mesh[0];
let mesh2 = &obj2.mesh[0];
assert_eq!(mesh.vsize, mesh2.vsize);
assert_eq!(mesh.lsize, mesh2.lsize);
assert_eq!(mesh.list, mesh2.list);
for j in 0..mesh.vsize as usize {
assert!(
(mesh.vert[j].x - mesh2.vert[j].x).abs() < 1e-5,
"mesh vert {} x: {} != {}",
j,
mesh.vert[j].x,
mesh2.vert[j].x
);
}
}
#[test]
fn test_ascii_roundtrip() {
let model = create_test_model();
let mut buf = Vec::new();
write_ascii(&mut buf, &model).expect("write_ascii failed");
let ascii_text = String::from_utf8_lossy(&buf);
println!("ASCII output:\n{}", ascii_text);
let model2 = read_ascii(&mut Cursor::new(&buf)).expect("read_ascii failed");
assert_eq!(model.obj.len(), model2.obj.len());
assert_eq!(model.xmax, model2.xmax);
assert_eq!(model.ymax, model2.ymax);
if !model2.obj.is_empty() {
let obj2 = &model2.obj[0];
assert_eq!(obj2.name, "Object1");
assert!((obj2.red - 1.0).abs() < 0.01);
assert!((obj2.green - 0.5).abs() < 0.01);
assert_eq!(obj2.contsize, 2);
assert_eq!(obj2.cont.len(), 2);
if obj2.cont.len() > 0 {
assert_eq!(obj2.cont[0].psize, 3);
assert!(obj2.cont[0].pts.len() >= 3);
}
}
}
#[test]
fn test_binary_to_ascii() {
let model = create_test_model();
let mut bin_buf = Vec::new();
write_binary(&mut Cursor::new(&mut bin_buf), &model).expect("write_binary");
let model2 = read_binary(&mut Cursor::new(&bin_buf)).expect("read_binary");
let mut ascii_buf = Vec::new();
write_ascii(&mut ascii_buf, &model2).expect("write_ascii");
let ascii_text = String::from_utf8_lossy(&ascii_buf);
assert!(ascii_text.contains("imod 1"));
assert!(ascii_text.contains("Object1"));
assert!(ascii_text.contains("contour 0 0 3"));
assert!(ascii_text.contains("contour 1 0 4"));
}
#[test]
fn test_create_empty_model() {
let model = Imod::default();
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write_binary empty");
let model2 = read_binary(&mut Cursor::new(&buf)).expect("read_binary empty");
assert_eq!(model2.objsize, 0);
assert!(model2.obj.is_empty());
}
#[test]
fn test_model_with_labels() {
let mut model = Imod::default();
model.objsize = 1;
let mut obj = Iobj::default();
obj.name = "LabelTest".to_string();
obj.contsize = 1;
let mut cont = Icont::default();
cont.psize = 2;
cont.pts = vec![
Ipoint { x: 0.0, y: 0.0, z: 0.0 },
Ipoint { x: 1.0, y: 1.0, z: 1.0 },
];
let mut label = Ilabel::default();
label.name = Some("ContourLabel".to_string());
label.items.push(IlabelItem {
name: "PointA".to_string(),
index: 0,
});
label.items.push(IlabelItem {
name: "PointB".to_string(),
index: 1,
});
cont.label = Some(label);
obj.cont = vec![cont];
model.obj = vec![obj];
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write_binary with labels");
let model2 = read_binary(&mut Cursor::new(&buf)).expect("read_binary with labels");
let cont2 = &model2.obj[0].cont[0];
assert!(cont2.label.is_some());
let label2 = cont2.label.as_ref().unwrap();
assert_eq!(label2.name.as_deref(), Some("ContourLabel"));
assert_eq!(label2.items.len(), 2);
assert_eq!(label2.items[0].name, "PointA");
assert_eq!(label2.items[1].name, "PointB");
}
#[test]
fn test_model_with_point_sizes() {
let mut model = Imod::default();
model.objsize = 1;
let mut obj = Iobj::default();
obj.name = "SizeTest".to_string();
obj.contsize = 1;
let mut cont = Icont::default();
cont.psize = 3;
cont.pts = vec![
Ipoint { x: 0.0, y: 0.0, z: 0.0 },
Ipoint { x: 1.0, y: 0.0, z: 0.0 },
Ipoint { x: 0.0, y: 1.0, z: 0.0 },
];
cont.sizes = Some(vec![1.5, 2.0, 0.5]);
obj.cont = vec![cont];
model.obj = vec![obj];
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write_binary with sizes");
let model2 = read_binary(&mut Cursor::new(&buf)).expect("read_binary with sizes");
let cont2 = &model2.obj[0].cont[0];
assert!(cont2.sizes.is_some());
let sizes = cont2.sizes.as_ref().unwrap();
assert_eq!(sizes.len(), 3);
assert!((sizes[0] - 1.5).abs() < 1e-5);
assert!((sizes[1] - 2.0).abs() < 1e-5);
assert!((sizes[2] - 0.5).abs() < 1e-5);
}
#[test]
fn test_clip_planes() {
let mut model = Imod::default();
model.objsize = 1;
let mut obj = Iobj::default();
obj.name = "ClipTest".to_string();
obj.contsize = 1;
obj.clips.count = 1;
obj.clips.flags = 1; obj.clips.normal[0] = Ipoint { x: 0.0, y: 0.0, z: 1.0 };
obj.clips.point[0] = Ipoint { x: 0.0, y: 0.0, z: 50.0 };
let mut cont = Icont::default();
cont.psize = 1;
cont.pts = vec![Ipoint { x: 10.0, y: 10.0, z: 10.0 }];
obj.cont = vec![cont];
model.obj = vec![obj];
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write_binary with clips");
let model2 = read_binary(&mut Cursor::new(&buf)).expect("read_binary with clips");
let obj2 = &model2.obj[0];
assert_eq!(obj2.clips.count, 1);
assert!((obj2.clips.normal[0].x - 0.0).abs() < 1e-5);
assert!((obj2.clips.normal[0].z - 1.0).abs() < 1e-5);
assert!((obj2.clips.point[0].z - 50.0).abs() < 1e-5);
}
#[test]
fn test_file_read_write() {
let tmp = std::env::temp_dir().join("test_imod_roundtrip.mod");
let model = create_test_model();
{
let file = std::fs::File::create(&tmp).expect("create temp file");
let mut writer = std::io::BufWriter::new(file);
write_binary(&mut writer, &model).expect("write to file");
}
let file = std::fs::File::open(&tmp).expect("open temp file");
let mut reader = std::io::BufReader::new(file);
let model2 = read_binary(&mut reader).expect("read from file");
assert_eq!(model2.obj.len(), 1);
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_binary_writer_raw_bytes() {
let mut model = Imod::default();
model.objsize = 1;
let mut obj = Iobj::default();
obj.name = "Test".to_string();
obj.contsize = 1;
let cont = Icont {
psize: 2,
pts: vec![Ipoint { x: 1.0, y: 2.0, z: 3.0 }, Ipoint { x: 4.0, y: 5.0, z: 6.0 }],
..Icont::default()
};
obj.cont = vec![cont];
model.obj = vec![obj];
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write_binary");
assert_eq!(&buf[0..4], b"IMOD", "magic");
assert_eq!(&buf[4..8], b"V1.2", "version");
let obj_tag_pos = 240; assert_eq!(&buf[obj_tag_pos..obj_tag_pos + 4], b"OBJT", "OBJT tag");
let cont_tag_pos = obj_tag_pos + 4 + 176;
assert_eq!(&buf[cont_tag_pos..cont_tag_pos + 4], b"CONT", "CONT tag");
let psize = i32::from_be_bytes([buf[cont_tag_pos + 4], buf[cont_tag_pos + 5],
buf[cont_tag_pos + 6], buf[cont_tag_pos + 7]]);
assert_eq!(psize, 2, "contour psize");
let x1 = f32::from_be_bytes([buf[cont_tag_pos + 20], buf[cont_tag_pos + 21],
buf[cont_tag_pos + 22], buf[cont_tag_pos + 23]]);
assert!((x1 - 1.0).abs() < 1e-5, "first x = {}", x1);
let imat_tag_pos = cont_tag_pos + 4 + 16 + 24; assert_eq!(&buf[imat_tag_pos..imat_tag_pos + 4], b"IMAT", "IMAT tag");
let imat_size = i32::from_be_bytes([buf[imat_tag_pos + 4], buf[imat_tag_pos + 5],
buf[imat_tag_pos + 6], buf[imat_tag_pos + 7]]);
assert_eq!(imat_size, 16, "IMAT size");
assert_eq!(&buf[buf.len() - 4..], b"IEOF", "IEOF");
}
#[test]
fn test_ascii_writer_format() {
let mut model = Imod::default();
model.objsize = 1;
model.xmax = 100;
model.ymax = 200;
model.zmax = 50;
let mut obj = Iobj::default();
obj.name = "TestObj".to_string();
obj.contsize = 2;
obj.red = 1.0;
obj.green = 0.5;
obj.blue = 0.0;
obj.cont = vec![
Icont { psize: 2, pts: vec![Ipoint { x: 0.0, y: 0.0, z: 0.0 }, Ipoint { x: 1.0, y: 0.0, z: 0.0 }], ..Icont::default() },
Icont { psize: 3, pts: vec![Ipoint { x: 0.0, y: 0.0, z: 1.0 }, Ipoint { x: 1.0, y: 0.0, z: 1.0 }, Ipoint { x: 0.0, y: 1.0, z: 1.0 }], ..Icont::default() },
];
model.obj = vec![obj];
let mut buf = Vec::new();
write_ascii(&mut buf, &model).expect("write_ascii");
let text = String::from_utf8(buf).expect("valid UTF-8");
assert!(text.starts_with("# imod ascii file version 2.0\n"), "ASCII header");
assert!(text.contains("imod 1\n"), "object count");
assert!(text.contains("max 100 200 50\n"), "dimensions");
assert!(text.contains("object 0 2 0\n"), "object header");
assert!(text.contains("name TestObj\n"), "object name");
assert!(text.contains("color 1 0.5 0 0\n"), "color");
assert!(text.contains("contour 0 0 2\n"), "first contour");
assert!(text.contains("contour 1 0 3\n"), "second contour");
assert!(text.contains("0 0 0\n"), "point 0");
assert!(text.contains("1 0 0\n"), "point 1");
assert!(text.ends_with("# end of IMOD model\n"), "footer");
}
#[test]
fn test_real_file_roundtrip() {
let original = read_binary(&mut Cursor::new(&std::fs::read("tests/test_001.mod").unwrap()))
.expect("read real file");
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &original).expect("write real file");
let reloaded = read_binary(&mut Cursor::new(&buf)).expect("re-read real file");
assert_eq!(original.obj.len(), reloaded.obj.len());
assert_eq!(original.xmax, reloaded.xmax);
assert_eq!(original.ymax, reloaded.ymax);
assert_eq!(original.zmax, reloaded.zmax);
assert_eq!(original.name, reloaded.name);
for (oi, (o1, o2)) in original.obj.iter().zip(reloaded.obj.iter()).enumerate() {
assert_eq!(o1.name, o2.name, "obj {oi} name");
assert_eq!(o1.contsize, o2.contsize, "obj {oi} contsize");
assert_eq!(o1.cont.len(), o2.cont.len(), "obj {oi} cont len");
for (ci, (c1, c2)) in o1.cont.iter().zip(o2.cont.iter()).enumerate() {
assert_eq!(c1.psize, c2.psize, "obj {oi} cont {ci} psize");
assert_eq!(c1.flags, c2.flags, "obj {oi} cont {ci} flags");
assert_eq!(c1.time, c2.time, "obj {oi} cont {ci} time");
assert_eq!(c1.surf, c2.surf, "obj {oi} cont {ci} surf");
for (pi, (p1, p2)) in c1.pts.iter().zip(c2.pts.iter()).enumerate() {
assert!((p1.x - p2.x).abs() < 1e-4, "obj {oi} cont {ci} pt {pi} x");
assert!((p1.y - p2.y).abs() < 1e-4, "obj {oi} cont {ci} pt {pi} y");
assert!((p1.z - p2.z).abs() < 1e-4, "obj {oi} cont {ci} pt {pi} z");
}
}
}
}
#[test]
fn test_write_then_read_identical_model() {
let mut model = Imod::default();
model.objsize = 3;
model.name = "RoundTrip".to_string();
model.xmax = 512;
model.ymax = 512;
model.zmax = 100;
model.obj.push(Iobj::default());
let mut obj1 = Iobj::default();
obj1.name = "empty-cont".to_string();
obj1.contsize = 1;
obj1.cont.push(Icont::default());
model.obj.push(obj1);
let mut obj2 = Iobj::default();
obj2.name = "with-pts".to_string();
obj2.contsize = 1;
obj2.red = 0.0;
obj2.green = 1.0;
obj2.blue = 0.0;
obj2.cont.push(Icont {
psize: 3,
pts: vec![
Ipoint { x: 100.0, y: 200.0, z: 300.0 },
Ipoint { x: 400.0, y: 500.0, z: 600.0 },
Ipoint { x: 700.0, y: 800.0, z: 900.0 },
],
..Icont::default()
});
model.obj.push(obj2);
let mut buf = Vec::new();
write_binary(&mut Cursor::new(&mut buf), &model).expect("write");
let model2 = read_binary(&mut Cursor::new(&buf)).expect("read");
assert_eq!(model2.objsize, 3);
assert_eq!(model2.obj.len(), 3);
assert_eq!(model2.obj[0].contsize, 0);
assert_eq!(model2.obj[1].contsize, 1);
assert_eq!(model2.obj[1].cont.len(), 1);
assert_eq!(model2.obj[1].cont[0].psize, 0);
assert_eq!(model2.obj[2].cont[0].psize, 3);
assert!((model2.obj[2].cont[0].pts[2].z - 900.0).abs() < 1e-5);
assert!((model2.obj[2].green - 1.0).abs() < 1e-5);
}