use std::io::{Write, Seek};
use crate::chunk_ids::*;
use crate::error::ImodResult;
use crate::model::*;
trait WriteImod: Write + Seek {
fn write_u32(&mut self, v: u32) -> ImodResult<()> {
self.write_all(&v.to_be_bytes())?;
Ok(())
}
fn write_i32(&mut self, v: i32) -> ImodResult<()> {
self.write_all(&v.to_be_bytes())?;
Ok(())
}
fn write_f32(&mut self, v: f32) -> ImodResult<()> {
self.write_all(&v.to_be_bytes())?;
Ok(())
}
fn write_u16(&mut self, v: u16) -> ImodResult<()> {
self.write_all(&v.to_be_bytes())?;
Ok(())
}
fn write_u8(&mut self, v: u8) -> ImodResult<()> {
self.write_all(&[v])?;
Ok(())
}
fn write_i32s(&mut self, v: &[i32]) -> ImodResult<()> {
for &val in v {
self.write_i32(val)?;
}
Ok(())
}
fn write_f32s(&mut self, v: &[f32]) -> ImodResult<()> {
for &val in v {
self.write_f32(val)?;
}
Ok(())
}
fn write_bytes(&mut self, data: &[u8]) -> ImodResult<()> {
self.write_all(data)?;
Ok(())
}
fn write_padded_string(&mut self, s: &str, len: usize) -> ImodResult<()> {
let mut buf = vec![0u8; len];
let bytes = s.as_bytes();
let copy_len = bytes.len().min(len);
buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
self.write_all(&buf)?;
Ok(())
}
}
impl<T: Write + Seek> WriteImod for T {}
pub fn write_binary<W: Write + Seek>(writer: &mut W, model: &Imod) -> ImodResult<()> {
let write_flags = model.flags
| IMODF_MAT1_IS_BYTES
| IMODF_MULTIPLE_CLIP
| IMODF_HAS_MESH_THICK;
writer.write_u32(ID_IMOD)?;
writer.write_u32(ID_VERSION_V12)?;
writer.write_padded_string(&model.name, IMOD_STRSIZE)?;
writer.write_i32s(&[
model.xmax,
model.ymax,
model.zmax,
model.objsize,
write_flags as i32,
model.drawmode,
model.mousemode,
model.blacklevel,
model.whitelevel,
])?;
writer.write_f32s(&[
model.xoffset,
model.yoffset,
model.zoffset,
model.xscale,
model.yscale,
model.zscale,
])?;
writer.write_i32s(&[
model.cindex.object,
model.cindex.contour,
model.cindex.point,
model.ctime,
model.tmax,
])?;
writer.write_f32(model.pixsize)?;
writer.write_i32(model.units)?;
writer.write_u32(model.csum)?;
writer.write_f32s(&[model.alpha, model.beta, model.gamma])?;
for obj in &model.obj {
write_object(writer, obj, model.flags)?;
}
for view in &model.view {
write_view(writer, view)?;
}
for slan in &model.slicer_angles {
write_slicer_angles(writer, slan)?;
}
if !model.store.items.is_empty() {
write_store(writer, ID_MOST, &model.store)?;
}
writer.write_u32(ID_IEOF)?;
writer.flush()?;
Ok(())
}
fn write_object<W: WriteImod>(writer: &mut W, obj: &Iobj, _flags: u32) -> ImodResult<()> {
writer.write_u32(ID_OBJT)?;
writer.write_padded_string(&obj.name, IOBJ_STRSIZE)?;
for &e in &obj.extra {
writer.write_i32(e)?;
}
writer.write_i32(obj.contsize)?;
writer.write_u32(obj.flags)?;
writer.write_i32(obj.axis)?;
writer.write_i32(obj.drawmode)?;
writer.write_f32(obj.red)?;
writer.write_f32(obj.green)?;
writer.write_f32(obj.blue)?;
writer.write_i32(obj.pdrawsize)?;
writer.write_u8(obj.symbol)?;
writer.write_u8(obj.symsize)?;
writer.write_u8(obj.linewidth2)?;
writer.write_u8(obj.linewidth)?;
writer.write_u8(obj.linesty)?;
writer.write_u8(obj.symflags)?;
writer.write_u8(obj.sympad)?;
writer.write_u8(obj.trans)?;
let num_real = obj.mesh.iter().filter(|m| m.flag & 0x80000000 == 0).count() as i32;
writer.write_i32(num_real)?;
writer.write_i32(obj.surfsize)?;
if let Some(ref label) = obj.label {
write_label(writer, ID_OLBL, label)?;
}
for cont in &obj.cont {
write_contour(writer, cont)?;
}
for mesh in &obj.mesh {
write_mesh(writer, mesh)?;
}
if obj.clips.count > 0 {
write_clip_planes(writer, &obj.clips)?;
}
writer.write_u32(ID_IMAT)?;
writer.write_u32(SIZE_IMAT)?;
writer.write_u8(obj.ambient)?;
writer.write_u8(obj.diffuse)?;
writer.write_u8(obj.specular)?;
writer.write_u8(obj.shininess)?;
writer.write_u8(obj.fillred)?;
writer.write_u8(obj.fillgreen)?;
writer.write_u8(obj.fillblue)?;
writer.write_u8(obj.quality)?;
writer.write_i32(obj.mat2)?;
writer.write_u8(obj.valblack)?;
writer.write_u8(obj.valwhite)?;
writer.write_u8(obj.matflags2 as u8)?;
writer.write_u8(obj.mesh_thickness)?;
if let Some(ref mp) = obj.mesh_param {
writer.write_u32(ID_MEPA)?;
writer.write_u32(9 * 4 + 10 * 4)?; for &f in &mp.flags {
writer.write_i32(f)?;
}
for &f in &mp.overlap {
writer.write_f32(f)?;
}
if !mp.cap_skip_zlist.is_empty() {
writer.write_u32(ID_SKLI)?;
writer.write_u32((mp.cap_skip_zlist.len() * 4) as u32)?;
for &v in &mp.cap_skip_zlist {
writer.write_i32(v)?;
}
}
}
if !obj.store.items.is_empty() {
write_store(writer, ID_OBST, &obj.store)?;
}
Ok(())
}
fn write_contour<W: WriteImod>(writer: &mut W, cont: &Icont) -> ImodResult<()> {
writer.write_u32(ID_CONT)?;
writer.write_i32(cont.psize)?;
writer.write_u32(cont.flags)?;
writer.write_i32(cont.time)?;
writer.write_i32(cont.surf)?;
for pt in &cont.pts {
writer.write_f32(pt.x)?;
writer.write_f32(pt.y)?;
writer.write_f32(pt.z)?;
}
if let Some(ref label) = cont.label {
write_label(writer, ID_LABL, label)?;
}
if let Some(ref sizes) = cont.sizes {
writer.write_u32(ID_SIZE)?;
writer.write_u32((cont.psize as u32) * 4)?;
for &s in sizes {
writer.write_f32(s)?;
}
}
if !cont.store.items.is_empty() {
write_store(writer, ID_COST, &cont.store)?;
}
Ok(())
}
fn write_mesh<W: WriteImod>(writer: &mut W, mesh: &Imesh) -> ImodResult<()> {
writer.write_u32(ID_MESH)?;
writer.write_i32(mesh.vsize)?;
writer.write_i32(mesh.lsize)?;
writer.write_u32(mesh.flag)?;
writer.write_u16(mesh.time)?;
writer.write_u16(mesh.surf)?;
for pt in &mesh.vert {
writer.write_f32(pt.x)?;
writer.write_f32(pt.y)?;
writer.write_f32(pt.z)?;
}
for &idx in &mesh.list {
writer.write_i32(idx)?;
}
if !mesh.store.items.is_empty() {
write_store(writer, ID_MEST, &mesh.store)?;
}
Ok(())
}
fn write_label<W: WriteImod>(writer: &mut W, tag: u32, label: &Ilabel) -> ImodResult<()> {
writer.write_u32(tag)?;
let name_bytes = label.name.as_deref().unwrap_or("").as_bytes();
let name_len_actual = name_bytes.len() + 1; let name_len_padded = ((name_len_actual + 3) / 4) * 4;
let name_pad = name_len_padded - name_len_actual;
let mut total = 4 + 4; total += name_len_padded as u32;
for item in &label.items {
let item_bytes = item.name.as_bytes();
let item_actual = item_bytes.len() + 1;
let item_padded = ((item_actual + 3) / 4) * 4;
total += 4 + 4; total += item_padded as u32;
}
writer.write_u32(total)?;
writer.write_i32(label.items.len() as i32)?;
writer.write_i32(name_len_padded as i32)?;
writer.write_bytes(name_bytes)?;
writer.write_u8(0)?;
for _ in 0..name_pad {
writer.write_u8(0)?;
}
for item in &label.items {
writer.write_i32(item.index)?;
let item_bytes = item.name.as_bytes();
let item_actual = item_bytes.len() + 1;
let item_padded = ((item_actual + 3) / 4) * 4;
let item_pad = item_padded - item_actual;
writer.write_i32(item_padded as i32)?;
writer.write_bytes(item_bytes)?;
writer.write_u8(0)?;
for _ in 0..item_pad {
writer.write_u8(0)?;
}
}
Ok(())
}
fn write_clip_planes<W: WriteImod>(writer: &mut W, clips: &IclipPlanes) -> ImodResult<()> {
writer.write_u32(ID_CLIP)?;
let data_size = SIZE_CLIP + 24 * (clips.count.max(1) as u32 - 1);
writer.write_u32(data_size)?;
writer.write_u8(clips.count as u8)?;
let flags_bytes = clips.flags.to_le_bytes();
writer.write_bytes(&flags_bytes[..3])?;
for i in 0..clips.count.max(1) as usize {
let idx = i.min(clips.normal.len().saturating_sub(1));
writer.write_f32(clips.normal[idx].x)?;
writer.write_f32(clips.normal[idx].y)?;
writer.write_f32(clips.normal[idx].z)?;
writer.write_f32(clips.point[idx].x)?;
writer.write_f32(clips.point[idx].y)?;
writer.write_f32(clips.point[idx].z)?;
}
Ok(())
}
fn write_view<W: WriteImod>(writer: &mut W, view: &Iview) -> ImodResult<()> {
writer.write_u32(ID_VIEW)?;
let size = 4 + 4 + 4 + 4 + 4 + VIEW_STRSIZE as u32 + 4 + 4 + 4 + 4 + 4 + 16 * 4 + 3 * 4 + 3 * 4 + 3 * 4 + 4 + 4 + 4 + 4 + (view.clips.count.max(0) as u32) * 24;
writer.write_u32(size)?;
writer.write_f32(view.fovy)?;
writer.write_f32(view.rad)?;
writer.write_f32(view.aspect)?;
writer.write_f32(view.cnear)?;
writer.write_f32(view.cfar)?;
writer.write_padded_string(&view.label, VIEW_STRSIZE)?;
writer.write_f32(view.lightx)?;
writer.write_f32(view.lighty)?;
writer.write_f32(view.dcstart)?;
writer.write_f32(view.dcend)?;
writer.write_u32(view.world)?;
for &m in &view.mat {
writer.write_f32(m)?;
}
writer.write_f32(view.scale.x)?;
writer.write_f32(view.scale.y)?;
writer.write_f32(view.scale.z)?;
writer.write_f32(view.trans.x)?;
writer.write_f32(view.trans.y)?;
writer.write_f32(view.trans.z)?;
writer.write_f32(view.rot.x)?;
writer.write_f32(view.rot.y)?;
writer.write_f32(view.rot.z)?;
writer.write_i32(view.clips.count)?;
writer.write_u32(view.clips.flags)?;
writer.write_u32(view.clips.trans)?;
writer.write_u32(view.clips.plane)?;
if view.clips.count > 0 {
let n = view.clips.count as usize;
for i in 0..n {
writer.write_f32(view.clips.normal[i].x)?;
writer.write_f32(view.clips.normal[i].y)?;
writer.write_f32(view.clips.normal[i].z)?;
}
for i in 0..n {
writer.write_f32(view.clips.point[i].x)?;
writer.write_f32(view.clips.point[i].y)?;
writer.write_f32(view.clips.point[i].z)?;
}
}
Ok(())
}
fn write_slicer_angles<W: WriteImod>(writer: &mut W, slan: &SlicerAngles) -> ImodResult<()> {
writer.write_u32(ID_SLAN)?;
writer.write_u32(SIZE_SLAN)?;
writer.write_i32(slan.time)?;
writer.write_f32(slan.angles[0])?;
writer.write_f32(slan.angles[1])?;
writer.write_f32(slan.angles[2])?;
writer.write_f32(slan.center.x)?;
writer.write_f32(slan.center.y)?;
writer.write_f32(slan.center.z)?;
writer.write_padded_string(&slan.label, ANGLE_STRSIZE)?;
Ok(())
}
fn write_store<W: WriteImod>(writer: &mut W, tag: u32, store: &Istore) -> ImodResult<()> {
if store.items.is_empty() {
return Ok(());
}
writer.write_u32(tag)?;
let data_size = 4 + (store.items.len() * 16) as u32; writer.write_u32(data_size)?;
writer.write_i32(store.items.len() as i32)?;
for item in &store.items {
writer.write_u32(item.flags)?;
writer.write_i32(item.index)?;
writer.write_f32(item.value_f)?;
writer.write_i32(item.value_i)?;
}
Ok(())
}